[
  {
    "path": ".gitattributes",
    "content": "# Set the default behavior of genesis.json, in case core.autocrlf is set incorrectly\ngenesis.json eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: Create a detailed report about a deficiency in the BitShares Core implementation.\n\n---\n\n**Instructions**\nPlease include a detailed Title above. Next, please complete the following sections below:\n* Bug Description\n* Impacts\n* Steps To Reproduce\n* Expected Behavior\n* Screenshots (optional)\n* Host Environment (optional)\n* Additional Context (optional)\n\nFinally, press the 'Submit new issue' button. The Core Team will evaluate and prioritize your Bug Report for future development. \n\n**Bug Description**\nA clear and concise description of what the bug is.\n\n**Impacts**\nDescribe which portion(s) of BitShares Core may be impacted by this bug. Please tick at least one box.\n- [ ] API (the application programming interface)\n- [ ] Build (the build process or something prior to compiled code)\n- [ ] CLI (the command line wallet)\n- [ ] Deployment (the deployment process after building such as Docker, Travis, etc.)\n- [ ] DEX (the Decentralized EXchange, market engine, etc.)\n- [ ] P2P (the peer-to-peer network for transaction/block propagation)\n- [ ] Performance (system or user efficiency, etc.)\n- [ ] Protocol (the blockchain logic, consensus, validation, etc.)\n- [ ] Security (the security of system or user data, etc.)\n- [ ] UX (the User Experience)\n- [ ] Other (please add below)\n\n**Steps To Reproduce**\nSteps to reproduce the behavior (example outlined below):\n1. Execute API call '...'\n2. Using JSON payload '...'\n3. Received response '...'\n4. See error in screenshot\n\n**Expected Behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots (optional)**\nIf applicable, add screenshots to help explain process flow and behavior.\n\n**Host Environment**\nPlease provide details about the host environment. Much of this information can be found running: `witness_node --version`. \n - Host OS:             [e.g. Ubuntu 18.04 LTS]\n - Host Physical RAM    [e.g. 4GB]\n - BitShares Version:   [e.g. 2.0.180425]\n - OpenSSL Version:     [e.g. 1.1.0g]\n - Boost Version:       [e.g. 1.65.1]\n \n**Additional Context (optional)**\nAdd any other context about the problem here.\n\n## CORE TEAM TASK LIST\n- [ ] Evaluate / Prioritize Bug Report\n- [ ] Refine User Stories / Requirements\n- [ ] Define Test Cases\n- [ ] Design / Develop Solution\n- [ ] Perform QA/Testing\n- [ ] Update Documentation\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/build_error.md",
    "content": "---\nname: Build Error\nabout: Create a detailed report about an error encountered during the BitShares Core build process.\n\n---\n\n**Instructions**\nPlease include a detailed Title above. Next, please complete the following sections below:\n* Build Error\n* Build Environment\n* Steps To Reproduce\n* Console Logs (optional)\n\nFinally, press the 'Submit new issue' button. The Core Team will evaluate and prioritize your Bug Report for future development. \n\n**Build Error Description**\nA clear and concise description of what the build error is.\n\n**Build Environment**\nDetails about the build environment, including the relevant required libraries. Much of this information can be found in the `CMakeFiles/CMakeOutput.log`. \n - Host OS:             [e.g. Ubuntu 18.04 LTS]\n - Host Physical RAM    [e.g. 4GB]\n - Source Branch/Tag:   [e.g. master or 2.0.180425]\n - OpenSSL Version:     [e.g. 1.1.0g]\n - Boost Version:       [e.g. 1.65.1]\n - C++ Compiler:        [e.g. gcc version 4.8.5]\n\n**Steps To Reproduce**\nSteps to reproduce the behavior (example outlined below):\n1. Using installation guide from this URL...\n2. This is my complete build script...\n3. It fails at this step with the following output...\n4. See the error in the console log below...\n\n**Console Logs (optional)**\nPlease provide the full console log, including all commands entered and their output. This will allow detailed troubleshooting.\n\n## CORE TEAM TASK LIST\n- [ ] Evaluate `Build Error`\n- [ ] Provide build guidance\n- [ ] <OR> Create `Bug Report`\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: Suggest an idea for the BitShares Core Team to evaluate and prioritize for development.\n\n---\n\n**Instructions**\nPlease include a detailed Title above. Next, please complete the following sections below:\n* User Story\n* Impacts\n* Additional Context (optional)\n\nFinally, press the 'Submit new issue' button. The Core Team will evaluate and prioritize your Feature Request for future development. \n\n**User Story**\nPlease tell us about your feature request using the User Story format:\nAs a `<persona>` I want `<some functionality>` so that `<some benefit is realized>`.\n\nAt minimum, please define the `<who>`, `<what>` and `<why>` for your feature request. The `<who>` may be the system software, a component thereof, the end user, etc.; please be specific describing the context. The `<what>` details the solution your feature will provide; please describe the process flow for the functionality. The `<why>` details the benefits the feature will deliver; consider referencing alternative implementations for context.\n\n**Impacts**\nDescribe which portion(s) of BitShares Core may be impacted by your request. Please tick at least one box.\n- [ ] API (the application programming interface)\n- [ ] Build (the build process or something prior to compiled code)\n- [ ] CLI (the command line wallet)\n- [ ] Deployment (the deployment process after building such as Docker, Travis, etc.)\n- [ ] DEX (the Decentralized EXchange, market engine, etc.)\n- [ ] P2P (the peer-to-peer network for transaction/block propagation)\n- [ ] Performance (system or user efficiency, etc.)\n- [ ] Protocol (the blockchain logic, consensus, validation, etc.)\n- [ ] Security (the security of system or user data, etc.)\n- [ ] UX (the User Experience)\n- [ ] Other (please add below)\n\n**Additional Context (optional)**\nAdd any other context about your request here.\n\n## CORE TEAM TASK LIST\n- [ ] Evaluate / Prioritize Feature Request\n- [ ] Refine User Stories / Requirements\n- [ ] Define Test Cases\n- [ ] Design / Develop Solution\n- [ ] Perform QA/Testing\n- [ ] Update Documentation\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    target-branch: \"develop\"\n"
  },
  {
    "path": ".github/workflows/build-and-test.mac.yml",
    "content": "name: macOS\non: [ push, pull_request ]\nenv:\n  CCACHE_COMPRESS: exists means true\n  CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime,time_macros\njobs:\n  build-osx:\n    name: Build and test in macOS\n    strategy:\n      matrix:\n        os: [macos-11]\n    runs-on: ${{ matrix.os }}\n    steps:\n    - name: Install dependencies\n      run: |\n        brew install autoconf automake libtool\n        brew install ccache\n        brew install parallel\n        brew install bitshares/boost/boost@1.69\n    - uses: actions/checkout@v3\n      with:\n        submodules: recursive\n    - name: Configure\n      run: |\n        mkdir -p _build\n        pushd _build\n        cmake -D CMAKE_BUILD_TYPE=Release \\\n              -D CMAKE_C_COMPILER_LAUNCHER=ccache \\\n              -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \\\n              -D BOOST_ROOT=/usr/local/opt/boost@1.69 \\\n              -D OPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 \\\n              ..\n    - name: Load Cache\n      uses: actions/cache@v3\n      with:\n        path: ccache\n        key: ccache-${{ matrix.os }}-${{ github.ref }}-${{ github.sha }}\n        restore-keys: |\n          ccache-${{ matrix.os }}-${{ github.ref }}-\n          ccache-${{ matrix.os }}-\n    - name: Build\n      run: |\n        export CCACHE_DIR=\"$GITHUB_WORKSPACE/ccache\"\n        mkdir -p \"$CCACHE_DIR\"\n        make -j 3 -C _build witness_node cli_wallet app_test cli_test chain_test\n        df -h\n    - name: Unit-Tests\n      timeout-minutes: 15\n      run: |\n        _build/tests/app_test -l test_suite\n        libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite\n        _build/tests/cli_test -l test_suite\n        df -h\n    - name: Quick test for program arguments\n      run: |\n        _build/programs/witness_node/witness_node --version\n        _build/programs/witness_node/witness_node --help\n        if _build/programs/witness_node/witness_node --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --plugins \"account_history elasticsearch\" ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --rpc-endpoint --plugins \"witness\"; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        _build/programs/cli_wallet/cli_wallet --version\n        _build/programs/cli_wallet/cli_wallet --help\n        _build/programs/cli_wallet/cli_wallet --suggest-brain-key\n        if _build/programs/cli_wallet/cli_wallet --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n    - name: Node-Test\n      run: |\n        df -h\n        pushd _build\n        ../programs/build_helpers/run-node-test\n"
  },
  {
    "path": ".github/workflows/build-and-test.ubuntu-debug.yml",
    "content": "name: Ubuntu Debug\non: [ push, pull_request ]\nenv:\n  CCACHE_COMPRESS: exists means true\n  CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime,time_macros\njobs:\n  test-debug:\n    name: Build and test in Debug mode\n    strategy:\n      matrix:\n        os: [ ubuntu-20.04 ]\n    runs-on: ${{ matrix.os }}\n    services:\n      elasticsearch8:\n        image: elastic/elasticsearch:8.11.1\n        options: >-\n          --env ES_JAVA_OPTS=\"-Xms512m -Xmx512m\"\n          --env discovery.type=single-node\n          --env xpack.security.enabled=false\n          --env xpack.security.http.ssl.enabled=false\n          --env action.destructive_requires_name=false\n          --env cluster.routing.allocation.disk.threshold_enabled=false\n          --publish 9200:9200\n      elasticsearch7:\n        image: elastic/elasticsearch:7.17.15\n        options: >-\n          --env ES_JAVA_OPTS=\"-Xms512m -Xmx512m\"\n          --env discovery.type=single-node\n          --env cluster.routing.allocation.disk.threshold_enabled=false\n          --publish 9201:9200\n    steps:\n    - name: Install dependencies\n      run: |\n        df -h\n        sudo apt-get update\n        openssl_ver=`sudo apt-cache madison openssl | grep xenial-updates | awk '{print $3}'`\n        libssl_ver=`sudo apt-cache madison libssl-dev | grep xenial-updates | awk '{print $3}'`\n        [ -n \"${openssl_ver}\" ] && [ -n \"${libssl_ver}\" ] && \\\n          sudo apt-get install -y --allow-downgrades openssl=${openssl_ver} libssl-dev=${libssl_ver}\n        sudo apt-get install -y \\\n                     ccache \\\n                     parallel \\\n                     libboost-thread-dev \\\n                     libboost-iostreams-dev \\\n                     libboost-date-time-dev \\\n                     libboost-system-dev \\\n                     libboost-filesystem-dev \\\n                     libboost-program-options-dev \\\n                     libboost-chrono-dev \\\n                     libboost-test-dev \\\n                     libboost-context-dev \\\n                     libboost-regex-dev \\\n                     libboost-coroutine-dev \\\n                     libcurl4-openssl-dev\n        sudo apt-get auto-remove -y\n        sudo apt-get clean -y\n        df -h\n        sudo du -hs /mnt/*\n        sudo ls -alr /mnt/\n    - uses: actions/checkout@v3\n      with:\n        submodules: recursive\n    - name: Configure\n      run: |\n        pwd\n        df -h .\n        free\n        mkdir -p _build\n        sudo mkdir -p /_build/libraries /_build/programs /mnt/_build/tests\n        sudo chmod a+rwx /_build/libraries /_build/programs /mnt/_build/tests\n        ln -s /_build/libraries _build/libraries\n        ln -s /_build/programs _build/programs\n        ln -s /mnt/_build/tests _build/tests\n        sudo ln -s /_build/libraries /mnt/_build/libraries\n        sudo ln -s /_build/programs /mnt/_build/programs\n        sudo ln -s /mnt/_build/tests /_build/tests\n        ls -al _build\n        pushd _build\n        export -n BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR\n        cmake -D CMAKE_BUILD_TYPE=Debug \\\n              -D CMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON \\\n              -D CMAKE_C_COMPILER=gcc \\\n              -D CMAKE_C_COMPILER_LAUNCHER=ccache \\\n              -D CMAKE_CXX_COMPILER=g++ \\\n              -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \\\n              ..\n        popd\n    - name: Load Cache\n      uses: actions/cache@v3\n      with:\n        path: ccache\n        key: ccache-debug-${{ matrix.os }}-${{ github.ref }}-${{ github.sha }}\n        restore-keys: |\n          ccache-debug-${{ matrix.os }}-${{ github.ref }}-\n          ccache-debug-${{ matrix.os }}-\n    - name: Build\n      run: |\n        export CCACHE_DIR=\"$GITHUB_WORKSPACE/ccache\"\n        mkdir -p \"$CCACHE_DIR\"\n        df -h\n        make -j 2 -C _build chain_test\n        make -j 2 -C _build cli_test\n        make -j 2 -C _build app_test\n        make -j 2 -C _build es_test\n        make -j 2 -C _build cli_wallet\n        make -j 2 -C _build witness_node\n        make -j 2 -C _build\n        df -h\n        du -hs _build/libraries/* _build/programs/* _build/tests/*\n        du -hs _build/*\n        du -hs /_build/*\n    - name: Unit-Tests\n      timeout-minutes: 15\n      run: |\n        _build/tests/app_test -l test_suite\n        df -h\n        rm -rf /tmp/graphene*\n        _build/tests/es_test -l test_suite\n        export GRAPHENE_TESTING_ES_URL=http://127.0.0.1:9201/\n        _build/tests/es_test -l test_suite\n        df -h\n        rm -rf /tmp/graphene*\n        libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite\n        df -h\n        rm -rf /tmp/graphene*\n        _build/tests/cli_test -l test_suite\n        df -h\n        rm -rf /tmp/graphene*\n    - name: Quick test for program arguments\n      run: |\n        _build/programs/witness_node/witness_node --version\n        _build/programs/witness_node/witness_node --help\n        if _build/programs/witness_node/witness_node --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --plugins \"account_history elasticsearch\" ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --rpc-endpoint --plugins \"witness\"; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        _build/programs/cli_wallet/cli_wallet --version\n        _build/programs/cli_wallet/cli_wallet --help\n        _build/programs/cli_wallet/cli_wallet --suggest-brain-key\n        if _build/programs/cli_wallet/cli_wallet --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n    - name: Node-Test\n      run: |\n        df -h\n        pushd _build\n        ../programs/build_helpers/run-node-test\n        df -h\n"
  },
  {
    "path": ".github/workflows/build-and-test.ubuntu-release.yml",
    "content": "name: Ubuntu Release\non: [ push, pull_request ]\nenv:\n  CCACHE_COMPRESS: exists means true\n  CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime,time_macros\njobs:\n  test-release:\n    name: Build and test in Release mode\n    strategy:\n      matrix:\n        os: [ ubuntu-20.04 ]\n    runs-on: ${{ matrix.os }}\n    services:\n      elasticsearch8:\n        image: elastic/elasticsearch:8.11.1\n        options: >-\n          --env ES_JAVA_OPTS=\"-Xms512m -Xmx512m\"\n          --env discovery.type=single-node\n          --env xpack.security.enabled=false\n          --env xpack.security.http.ssl.enabled=false\n          --env action.destructive_requires_name=false\n          --env cluster.routing.allocation.disk.threshold_enabled=false\n          --publish 9200:9200\n      elasticsearch7:\n        image: elastic/elasticsearch:7.17.15\n        options: >-\n          --env ES_JAVA_OPTS=\"-Xms512m -Xmx512m\"\n          --env discovery.type=single-node\n          --env cluster.routing.allocation.disk.threshold_enabled=false\n          --publish 9201:9200\n    steps:\n    - name: Install dependencies\n      run: |\n        sudo apt-get update\n        openssl_ver=`sudo apt-cache madison openssl | grep xenial-updates | awk '{print $3}'`\n        libssl_ver=`sudo apt-cache madison libssl-dev | grep xenial-updates | awk '{print $3}'`\n        [ -n \"${openssl_ver}\" ] && [ -n \"${libssl_ver}\" ] && \\\n          sudo apt-get install -y --allow-downgrades openssl=${openssl_ver} libssl-dev=${libssl_ver}\n        sudo apt-get install -y \\\n                     ccache \\\n                     parallel \\\n                     libboost-thread-dev \\\n                     libboost-iostreams-dev \\\n                     libboost-date-time-dev \\\n                     libboost-system-dev \\\n                     libboost-filesystem-dev \\\n                     libboost-program-options-dev \\\n                     libboost-chrono-dev \\\n                     libboost-test-dev \\\n                     libboost-context-dev \\\n                     libboost-regex-dev \\\n                     libboost-coroutine-dev \\\n                     libcurl4-openssl-dev\n        sudo apt-get auto-remove -y\n        sudo apt-get clean -y\n        df -h\n    - uses: actions/checkout@v3\n      with:\n        submodules: recursive\n    - name: Configure\n      run: |\n        df -h\n        free\n        mkdir -p _build\n        pushd _build\n        export -n BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR\n        cmake -D CMAKE_BUILD_TYPE=Release \\\n              -D CMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON \\\n              -D CMAKE_C_COMPILER=gcc \\\n              -D CMAKE_C_COMPILER_LAUNCHER=ccache \\\n              -D CMAKE_CXX_COMPILER=g++ \\\n              -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \\\n              ..\n        popd\n    - name: Load Cache\n      uses: actions/cache@v3\n      with:\n        path: ccache\n        key: ccache-release-${{ matrix.os }}-${{ github.ref }}-${{ github.sha }}\n        restore-keys: |\n          ccache-release-${{ matrix.os }}-${{ github.ref }}-\n          ccache-release-${{ matrix.os }}-\n    - name: Build\n      run: |\n        export CCACHE_DIR=\"$GITHUB_WORKSPACE/ccache\"\n        mkdir -p \"$CCACHE_DIR\"\n        make -j 2 -C _build\n        df -h\n    - name: Unit-Tests\n      timeout-minutes: 15\n      run: |\n        _build/tests/app_test -l test_suite\n        _build/tests/es_test -l test_suite\n        export GRAPHENE_TESTING_ES_URL=http://127.0.0.1:9201/\n        _build/tests/es_test -l test_suite\n        libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite\n        _build/tests/cli_test -l test_suite\n        df -h\n    - name: Quick test for program arguments\n      run: |\n        _build/programs/witness_node/witness_node --version\n        _build/programs/witness_node/witness_node --help\n        if _build/programs/witness_node/witness_node --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --plugins \"account_history elasticsearch\" ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --rpc-endpoint --plugins \"witness\"; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        _build/programs/cli_wallet/cli_wallet --version\n        _build/programs/cli_wallet/cli_wallet --help\n        _build/programs/cli_wallet/cli_wallet --suggest-brain-key\n        if _build/programs/cli_wallet/cli_wallet --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n    - name: Node-Test\n      run: |\n        df -h\n        pushd _build\n        ../programs/build_helpers/run-node-test\n        df -h\n"
  },
  {
    "path": ".github/workflows/build-and-test.win.yml",
    "content": "name: Windows MinGW64\non: [ push, pull_request ]\nenv:\n  CCACHE_COMPRESS: exists means true\n  CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime,time_macros\n  # The following are for windows cross-build only:\n  BOOST_VERSION: 1_69_0\n  BOOST_DOTTED_VERSION: 1.69.0\n  CURL_VERSION: 8.4.0\n  OPENSSL_VERSION: 1.1.1w\n  ZLIB_VERSION: 1.3.1\njobs:\n  prepare-mingw64-libs:\n    name: Build required 3rd-party libraries\n    runs-on: ubuntu-latest\n    steps:\n    # Get OS version to be used in cache key - see https://github.com/actions/cache/issues/543\n    - run: |\n        echo \"OS_VERSION=`lsb_release -sr`\" >> $GITHUB_ENV\n    - name: Load Cache\n      id: cache-libs\n      uses: actions/cache@v3\n      with:\n        path: libs\n        key: mingw64-libs-${{ env.OS_VERSION }}-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }}\n    - name: Install dependencies\n      if: steps.cache-libs.outputs.cache-hit != 'true'\n      run: |\n        sudo apt-get update\n        sudo apt-get install -y \\\n                     g++-mingw-w64-x86-64 \\\n                     mingw-w64-tools\n    - name: Download library sources\n      if: steps.cache-libs.outputs.cache-hit != 'true'\n      run: |\n        curl -LO https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_DOTTED_VERSION }}/source/boost_${{ env.BOOST_VERSION }}.tar.bz2\n        curl -LO https://curl.haxx.se/download/curl-${{ env.CURL_VERSION }}.tar.bz2\n        curl -LO https://www.openssl.org/source/openssl-${{ env.OPENSSL_VERSION }}.tar.gz\n        curl -LO https://zlib.net/zlib-${{ env.ZLIB_VERSION }}.tar.gz\n    - name: Build zlib\n      if: steps.cache-libs.outputs.cache-hit != 'true'\n      run: |\n        LIBS=\"`pwd`/libs\"\n        ZLIB=\"`echo zlib-*`\"\n        tar xfz \"$ZLIB\"\n        pushd \"${ZLIB%.tar.gz}\"\n        CROSS_PREFIX=x86_64-w64-mingw32- ./configure --prefix=\"$LIBS\" --static --64\n        make install\n    - name: Build openssl\n      if: steps.cache-libs.outputs.cache-hit != 'true'\n      run: |\n        LIBS=\"`pwd`/libs\"\n        OPENSSL=\"`echo openssl-*`\"\n        tar xfz \"$OPENSSL\"\n        pushd \"${OPENSSL%.tar.gz}\"\n        ./Configure --prefix=\"$LIBS\" --cross-compile-prefix=x86_64-w64-mingw32- \\\n                    no-shared zlib threads \\\n                    mingw64\n        make CPPFLAGS=\"-I$LIBS/include\" LDFLAGS=\"-L$LIBS/lib\" build_libs\n        make -j 2 install_dev\n    - name: Build curl\n      if: steps.cache-libs.outputs.cache-hit != 'true'\n      run: |\n        LIBS=\"`pwd`/libs\"\n        CURL=\"`echo curl-*`\"\n        tar xfj \"$CURL\"\n        pushd \"${CURL%.tar.bz2}\"\n        sed -i 's=-lgdi32=-lcrypt32 \\0='  configure\n        PKG_CONFIG_PATH=\"$LIBS/lib/pkgconfig\" ./configure --host=x86_64-w64-mingw32 \\\n                                                          --prefix=\"$LIBS\" \\\n                                                          --disable-shared \\\n                                                          --disable-tftpf \\\n                                                          --disable-ldap \\\n                                                          --with-zlib \\\n                                                          --without-ssl --with-winssl \\\n                                                          --disable-tftp \\\n                                                          --disable-ldap\n        make -j 2 install\n    - name: Build boost\n      if: steps.cache-libs.outputs.cache-hit != 'true'\n      run: |\n        LIBS=\"`pwd`/libs\"\n        BOOST=\"`echo boost_*`\"\n        tar xfj \"$BOOST\"\n        pushd \"${BOOST%.tar.bz2}\"\n        # See https://github.com/boostorg/context/issues/101\n        sed -i '/os.\\(name\\|platform\\)/d;/local tmp = /s=elf=pe=;/local tmp = /s=sysv=ms=' libs/context/build/Jamfile.v2\n        ./bootstrap.sh --prefix=$LIBS\n        echo \"using gcc : mingw32 : x86_64-w64-mingw32-g++ ;\" > user-config.jam\n        ./b2 --user-config=user-config.jam \\\n             --without-python \\\n             toolset=gcc-mingw32 \\\n             target-os=windows \\\n             variant=release \\\n             link=static \\\n             threading=multi \\\n             runtime-link=static \\\n             address-model=64 \\\n             abi=ms \\\n             install\n  build-mingw64:\n    name: Cross-build for windows using mingw\n    runs-on: ubuntu-latest\n    needs: prepare-mingw64-libs\n    steps:\n    - name: Install dependencies\n      run: |\n        sudo apt-get update\n        sudo apt-get install -y \\\n                     ccache \\\n                     g++-mingw-w64-x86-64 \\\n                     mingw-w64-tools\n        sudo apt-get auto-remove -y\n        sudo apt-get clean -y\n        df -h\n    - uses: actions/checkout@v3\n      with:\n        submodules: recursive\n    - run: |\n        echo \"OS_VERSION=`lsb_release -sr`\" >> $GITHUB_ENV\n    - name: Load external libraries\n      uses: actions/cache@v3\n      with:\n        path: libs\n        key: mingw64-libs-${{ env.OS_VERSION }}-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }}\n    - name: Configure\n      run: |\n        LIBS=\"`pwd`/libs\"\n        mkdir -p _build\n        pushd _build\n        cmake -D CMAKE_BUILD_TYPE=Release \\\n              -D CMAKE_C_COMPILER=/usr/bin/x86_64-w64-mingw32-gcc-posix \\\n              -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \\\n              -D CMAKE_CXX_COMPILER=/usr/bin/x86_64-w64-mingw32-g++-posix \\\n              -D CMAKE_CXX_FLAGS=-Wa,-mbig-obj \\\n              -D CMAKE_SYSTEM_NAME=Windows \\\n              -D CURL_STATICLIB=ON \\\n              -D CMAKE_EXE_LINKER_FLAGS=--static \\\n              -D CMAKE_FIND_ROOT_PATH=\"/usr/lib/gcc/x86_64-w64-mingw32/7.3-win32/;$LIBS\" \\\n              -D CMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER \\\n              -D CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \\\n              -D CMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \\\n              -D GRAPHENE_DISABLE_UNITY_BUILD=ON \\\n              ..\n    - name: Load Cache\n      uses: actions/cache@v3\n      with:\n        path: ccache\n        key: ccache-mingw64-${{ env.OS_VERSION }}-${{ github.ref }}-${{ github.sha }}\n        restore-keys: |\n          ccache-mingw64-${{ env.OS_VERSION }}-${{ github.ref }}-\n          ccache-mingw64-${{ env.OS_VERSION }}-\n    - name: Build\n      run: |\n        export CCACHE_DIR=\"$GITHUB_WORKSPACE/ccache\"\n        mkdir -p \"$CCACHE_DIR\"\n        make VERBOSE=1 -j 2 -C _build witness_node cli_wallet\n"
  },
  {
    "path": ".github/workflows/build-docker.yml",
    "content": "name: Docker\non: [ push, pull_request ]\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Decide whether to push to DockerHub, and set tag\n      if: |\n        github.event_name == 'push' &&\n        ( startsWith( github.ref, 'refs/tags/' ) ||\n          contains( fromJSON('[\"master\",\"develop\",\"testnet\",\"hardfork\"]'), github.ref_name ) )\n      run: |\n        if [ \"${GITHUB_REF_NAME}\" == \"master\" ] ; then\n          DOCKER_PUSH_TAG=latest\n        else\n          DOCKER_PUSH_TAG=${GITHUB_REF_NAME}\n        fi\n        echo \"DOCKER_PUSH_TAG=${DOCKER_PUSH_TAG}\"\n        echo \"DOCKER_PUSH_TAG=${DOCKER_PUSH_TAG}\" >> $GITHUB_ENV\n        VERSION_MAJOR=`echo \"${DOCKER_PUSH_TAG}\" | cut -f1 -d'.'`\n        if [ \"${VERSION_MAJOR}\" != \"${DOCKER_PUSH_TAG}\" ]; then\n          VERSION_MINOR=`echo \"${DOCKER_PUSH_TAG}\" | cut -f2 -d'.'`\n          DOCKER_PUSH_TAG_SHORT=${VERSION_MAJOR}.${VERSION_MINOR}\n          if [ \"${DOCKER_PUSH_TAG_SHORT}\" != \"${DOCKER_PUSH_TAG}\" ]; then\n            echo \"DOCKER_PUSH_TAG_SHORT=${DOCKER_PUSH_TAG_SHORT}\"\n            echo \"DOCKER_PUSH_TAG_SHORT=${DOCKER_PUSH_TAG_SHORT}\" >> $GITHUB_ENV\n          fi\n        fi\n    - name: Test tag\n      if: env.DOCKER_PUSH_TAG != ''\n      run: echo \"${DOCKER_PUSH_TAG}\"\n    - name: Checkout\n      uses: actions/checkout@v3\n    - name: Set up Docker Buildx\n      uses: docker/setup-buildx-action@v2\n    - name: Build only\n      uses: docker/build-push-action@v4\n      with:\n        context: .\n        load: true\n    - name: Login to DockerHub\n      if: env.DOCKER_PUSH_TAG != ''\n      uses: docker/login-action@v2\n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n    - name: Push to DockerHub (for branches)\n      if: env.DOCKER_PUSH_TAG != '' && env.DOCKER_PUSH_TAG_SHORT == ''\n      uses: docker/build-push-action@v4\n      with:\n        context: .\n        push: true\n        tags: ${{ secrets.DOCKERHUB_REPO_PATH }}:${{ env.DOCKER_PUSH_TAG }}\n    - name: Push to DockerHub (for tags)\n      if: env.DOCKER_PUSH_TAG != '' && env.DOCKER_PUSH_TAG_SHORT != ''\n      uses: docker/build-push-action@v4\n      with:\n        context: .\n        push: true\n        tags: |\n          ${{ secrets.DOCKERHUB_REPO_PATH }}:${{ env.DOCKER_PUSH_TAG }}\n          ${{ secrets.DOCKERHUB_REPO_PATH }}:${{ env.DOCKER_PUSH_TAG_SHORT }}\n"
  },
  {
    "path": ".github/workflows/sonar-scan.yml",
    "content": "name: Scan with SonarScanner\non: [ push, pull_request ]\nenv:\n  CCACHE_COMPRESS: exists means true\n  CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime,time_macros\njobs:\n  sonar-scan:\n    name: Scan with SonarScanner\n    strategy:\n      matrix:\n        os: [ ubuntu-20.04 ]\n    runs-on: ${{ matrix.os }}\n    services:\n      elasticsearch8:\n        image: elastic/elasticsearch:8.11.1\n        options: >-\n          --env ES_JAVA_OPTS=\"-Xms512m -Xmx512m\"\n          --env discovery.type=single-node\n          --env xpack.security.enabled=false\n          --env xpack.security.http.ssl.enabled=false\n          --env action.destructive_requires_name=false\n          --env cluster.routing.allocation.disk.threshold_enabled=false\n          --publish 9200:9200\n    steps:\n    - name: Download and install latest SonarScanner CLI tool\n      run: |\n        SONAR_SCANNER_VERSION=`curl -w %{redirect_url} \\\n             https://github.com/SonarSource/sonar-scanner-cli/releases/latest \\\n             2>/dev/null | cut -f8 -d'/'`\n        SONAR_DOWNLOAD_PATH=https://binaries.sonarsource.com/Distribution/sonar-scanner-cli\n        curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip \\\n             $SONAR_DOWNLOAD_PATH/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip\n        unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/\n        curl --create-dirs -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip \\\n             https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip\n        unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/\n        SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux\n        echo \"SONAR_SCANNER_VERSION=$SONAR_SCANNER_VERSION\" >> $GITHUB_ENV\n        echo \"SONAR_SCANNER_HOME=$SONAR_SCANNER_HOME\" >> $GITHUB_ENV\n        echo \"SONAR_SCANNER_OPTS=-server\" >> $GITHUB_ENV\n        echo \"$SONAR_SCANNER_HOME/bin\" >> $GITHUB_PATH\n        echo \"$HOME/.sonar/build-wrapper-linux-x86\" >> $GITHUB_PATH\n    - name: Install dependencies\n      run: |\n        df -h\n        sudo apt-get update\n        openssl_ver=`sudo apt-cache madison openssl | grep xenial-updates | awk '{print $3}'`\n        libssl_ver=`sudo apt-cache madison libssl-dev | grep xenial-updates | awk '{print $3}'`\n        [ -n \"${openssl_ver}\" ] && [ -n \"${libssl_ver}\" ] && \\\n          sudo apt-get install -y --allow-downgrades openssl=${openssl_ver} libssl-dev=${libssl_ver}\n        sudo apt-get install -y \\\n                     ccache \\\n                     gcovr \\\n                     parallel \\\n                     libboost-thread-dev \\\n                     libboost-iostreams-dev \\\n                     libboost-date-time-dev \\\n                     libboost-system-dev \\\n                     libboost-filesystem-dev \\\n                     libboost-program-options-dev \\\n                     libboost-chrono-dev \\\n                     libboost-test-dev \\\n                     libboost-context-dev \\\n                     libboost-regex-dev \\\n                     libboost-coroutine-dev \\\n                     libcurl4-openssl-dev\n        sudo apt-get auto-remove -y\n        sudo apt-get clean -y\n        df -h\n        sudo du -hs /mnt/*\n        sudo ls -alr /mnt/\n    - uses: actions/checkout@v3\n      with:\n        fetch-depth: 0\n        submodules: recursive\n    - name: Configure\n      run: |\n        pwd\n        df -h .\n        free\n        mkdir -p _build\n        sudo mkdir -p /_build/libraries /_build/programs /mnt/_build/tests\n        sudo chmod a+rwx /_build/libraries /_build/programs /mnt/_build/tests\n        ln -s /_build/libraries _build/libraries\n        ln -s /_build/programs _build/programs\n        ln -s /mnt/_build/tests _build/tests\n        sudo ln -s /_build/libraries /mnt/_build/libraries\n        sudo ln -s /_build/programs /mnt/_build/programs\n        sudo ln -s /mnt/_build/tests /_build/tests\n        ls -al _build\n        sed -i '/tests/d' libraries/fc/CMakeLists.txt\n        pushd _build\n        export -n BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR\n        cmake -D CMAKE_BUILD_TYPE=Debug \\\n              -D CMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON \\\n              -D CMAKE_C_COMPILER=gcc \\\n              -D CMAKE_C_COMPILER_LAUNCHER=ccache \\\n              -D CMAKE_CXX_COMPILER=g++ \\\n              -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \\\n              -D CMAKE_C_FLAGS=--coverage \\\n              -D CMAKE_CXX_FLAGS=--coverage \\\n              -D Boost_USE_STATIC_LIBS=OFF \\\n              ..\n        popd\n    # Get OS version to be used in cache key - see https://github.com/actions/cache/issues/543\n    - run: |\n        echo \"OS_VERSION=`lsb_release -sr`\" >> $GITHUB_ENV\n    - name: Load Cache\n      uses: actions/cache@v3\n      with:\n        path: |\n          ccache\n        key: sonar-${{ env.OS_VERSION }}-${{ github.ref }}-${{ github.sha }}\n        restore-keys: |\n          sonar-${{ env.OS_VERSION }}-${{ github.ref }}-\n          sonar-${{ env.OS_VERSION }}-\n    - name: Build\n      run: |\n        export CCACHE_DIR=\"$GITHUB_WORKSPACE/ccache\"\n        mkdir -p \"$CCACHE_DIR\"\n        df -h\n        programs/build_helpers/make_with_sonar bw-output -j 2 -C _build \\\n          witness_node cli_wallet js_operation_serializer get_dev_key network_mapper \\\n          app_test chain_test cli_test es_test\n        df -h\n        du -hs _build/libraries/* _build/programs/* _build/tests/*\n        du -hs _build/*\n        du -hs /_build/*\n    - name: Quick test for program arguments\n      run: |\n        _build/programs/witness_node/witness_node --version\n        _build/programs/witness_node/witness_node --help\n        if _build/programs/witness_node/witness_node --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --plugins \"account_history elasticsearch\" ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        if _build/programs/witness_node/witness_node --rpc-endpoint --plugins \"witness\"; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n        _build/programs/cli_wallet/cli_wallet --version\n        _build/programs/cli_wallet/cli_wallet --help\n        _build/programs/cli_wallet/cli_wallet --suggest-brain-key\n        if _build/programs/cli_wallet/cli_wallet --bad-arg ; then \\\n          echo \"Fail: did not get expected error.\"; false; \\\n        else \\\n          echo \"Pass: got expected error.\"; \\\n        fi\n    - name: Remove binaries that we no longer need\n      run: |\n        df -h\n        echo \"Cleanup\"\n        rm -rf _build/programs/witness_node/witness_node\n        rm -rf _build/programs/cli_wallet/cli_wallet\n        rm -rf _build/programs/network_mapper/network_mapper\n        rm -rf _build/programs/js_operation_serializer/js_operation_serializer\n        rm -rf _build/programs/genesis_util/get_dev_key\n        df -h\n    - name: Unit-Tests\n      timeout-minutes: 15\n      run: |\n        _build/tests/app_test -l test_suite\n        df -h\n        echo \"Cleanup\"\n        rm -rf /tmp/graphene*\n        _build/tests/es_test -l test_suite\n        df -h\n        echo \"Cleanup\"\n        rm -rf /tmp/graphene*\n        libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite\n        df -h\n        echo \"Cleanup\"\n        rm -rf /tmp/graphene*\n        _build/tests/cli_test -l test_suite\n        df -h\n        echo \"Cleanup\"\n        rm -rf /tmp/graphene*\n        df -h\n    - name: Prepare for scanning with SonarScanner\n      run: |\n        programs/build_helpers/set_sonar_branch_for_github_actions sonar-project.properties\n        pushd _build\n        gcovr --version\n        gcovr --exclude-unreachable-branches --exclude-throw-branches \\\n              --exclude '\\.\\./programs/' \\\n              --exclude '\\.\\./tests/' \\\n              --sonarqube ../coverage.xml -r ..\n        popd\n    - name: Scan with SonarScanner\n      env:\n        # to get access to secrets.SONAR_TOKEN, provide GITHUB_TOKEN\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: |\n        sonar-scanner \\\n           -Dsonar.login=${{ secrets.SONAR_TOKEN }}\n    - name: Cleanup\n      run: |\n        df -h\n        echo \"Final cleanup\"\n        rm -rf _build/tests/app_test\n        rm -rf _build/tests/chain_test\n        rm -rf _build/tests/cli_test\n        rm -rf _build/tests/es_test\n        df -h\n"
  },
  {
    "path": ".gitignore",
    "content": "*.a\n*.sw*\n\n*.cmake\nCMakeCache.txt\nCMakeFiles\nMakefile\ncompile_commands.json\nmoc_*\n*.moc\n\ngenesis.json\nhardfork.hpp\n\nlibraries/utilities/git_revision.cpp\n\nlibraries/wallet/Doxyfile\nlibraries/wallet/api_documentation.cpp\nlibraries/wallet/doxygen\n\nprograms/cli_wallet/cli_wallet\nprograms/js_operation_serializer/js_operation_serializer\nprograms/witness_node/witness_node\n\ntests/app_test\ntests/chain_bench\ntests/chain_test\ntests/intense_test\ntests/performance_test\n\ndoxygen\n\nwallet.json\nwitness_node_data_dir\n\n*.wallet\n\nprograms/witness_node/object_database/*\n\nobject_database/*\n\n*.pyc\n*.pyo\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"docs\"]\n    path = docs\n    url = https://github.com/bitshares/bitshares-core.wiki.git\n    ignore = dirty\n[submodule \"libraries/fc\"]\n    path = libraries/fc\n    url = https://github.com/bitshares/bitshares-fc.git\n    ignore = dirty\n"
  },
  {
    "path": ".mailmap",
    "content": "Alexey Frolov <alexey.frolov@aetsoft.by> <a.frolov@aetsoft.by>\nAlfredo Garcia <oxarbitrage@gmail.com> <oxarbitrage@gmail.com>\nAlfredo Garcia <oxarbitrage@gmail.com> <root@NC-PH-1346-07.web-hosting.com>\nAnonymous <you@example.com>\nAnton Autushka <antoninus.liberalis@gmail.com> <a.autushka@aetsoft.by>\nBrownBear <-> <->\nChristopher Sanborn <23085117+christophersanborn@users.noreply.github.com>\nChronos <chronos.crypto@gmail.com>\nDan Notestein <dan@syncad.com> <dan@syncad.com>\nDan Notestein <dan@syncad.com> <syncad@syncad.com>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dlarimer@gmail.com>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dan.larimer@block.one>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dan@bitshares.org>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dan@invictus-innovations.com>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dlarimer@invictus-innovations.com>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dan@steemit.com>\nDaniel Larimer <bytemaster@users.noreply.github.com> <dan.larimer@8riverscapital.com>\nEric Frias <efrias@syncad.com>\nFabian Schuh <Fabian@chainsquad.com> <mail@xeroc.org>\nFabian Schuh <Fabian@chainsquad.com> <xeroc@chainsquad.com>\nJohn M. Jones <jmjatlanta@gmail.com>\nKen Code <ken@BitShares-Munich.de>\nMatias Romeo <matias.romeo@gmail.com>\nMichael Vandeberg <mvandeberg@users.noreply.github.com> <vandeberg@cryptonomex.com>\nMichael Vandeberg <mvandeberg@users.noreply.github.com> <vandeberg@steemit.com>\nNathan Hourt <themodprobe@protonmail.com> <nat.hourt@gmail.com>\nNathan Hourt <themodprobe@protonmail.com> <nathan@followmyvote.com>\nNathan Hourt <themodprobe@protonmail.com> <nathan@bitshares.org>\nNikolai Mushegian <nikolai.mushegian@gmail.com> <nmushegi@andrew.cmu.edu>\nOpenLedger <service.github@openledger.info> <service.github@openledger.info>\nOpenLedger <service.github@openledger.info> <42674402+OpenLedgerApp@users.noreply.github.com>\nPeter Conrad <conrad@quisquis.de> <cyrano@quisquis.de>\nPeter Conrad <conrad@quisquis.de> <github.com@quisquis.de>\nQi Xing <cwyyprog@163.com> <cwyyprog@163.com>\nRoelandp <dnaleor@gmail.com>\nRyan R. Fox <ryan@ryanrfox.com> <ryan@ryanrfox.com>\nRyan R. Fox <ryan@ryanrfox.com> <ryanRfox@users.noreply.github.com>\nSigve Kvalsvik <bitsharesblocks@gmail.com> <sigvekvalsvik@gmail.com>\nValentine Zavgorodnev <i@valzav.com> <i@valzav.com>\nValentine Zavgorodnev <i@valzav.com> <vz@valzav.com>\nValera Cogut <info@valeracogut.com> <valerakogut@gmail.com>\nVikram Rajkumar <vikramrajkumar@users.noreply.github.com> <vikram@bitshares.org>\nVikram Rajkumar <vikramrajkumar@users.noreply.github.com> <vikram@soledger.com>\nVikram Rajkumar <vikramrajkumar@users.noreply.github.com> <vikramrajkumar1@gmail.com>\nWilliam <tmfc@homtail.com> <jinwei@gmail.com>\nWilliam <tmfc@homtail.com> <tmfc@homtail.com>\nXiaodong Li <mantianyu@gmail.com> <cifer-lee@users.noreply.github.com>\nXiaodong Li <mantianyu@gmail.com> <mantianyu@gmail.com>\nXiaodong Li <mantianyu@gmail.com> <maintianyu@gmail.com>\nXiaodong Li <mantianyu@gmail.com> <lixiaodongcifer@didichuxing.com>\nYuvaraj Gogoi <yuvarajgogoi@gmail.com> <yuvarajgogoi@gmail.com>\nabitmore <abitmore@users.noreply.github.com>\nalbert <393259066@qq.com> <393259066@qq.com>\nalbert <393259066@qq.com> <zhuliting@gxb.io>\nbatmaninpink <batmaninpink@users.noreply.github.com> <batmaninpink@>\nbitcube <root@seed.cubeconnex.com>\nbtcinshares <btcinshares@protonmail.com> <33876675+btcinshares@users.noreply.github.com>\ncrazybits <crazybit.github@gmail.com> <crazybit.github@gmail.com>\ncrazybits <crazybit.github@gmail.com> <crazybits@users.noreply.github.com>\ntheoreticalbts <theoreticalbts@users.noreply.github.com> <drltc@users.noreply.github.com>\ntheoreticalbts <theoreticalbts@users.noreply.github.com> <theoreticalbts@users.noreply.github.com>\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# Defines BitShares library target.\ncmake_minimum_required( VERSION 3.2 FATAL_ERROR )\nproject( BitShares LANGUAGES CXX C)\n\nset( BLOCKCHAIN_NAME \"BitShares\" )\n\nset( CLI_CLIENT_EXECUTABLE_NAME graphene_client )\nset( GUI_CLIENT_EXECUTABLE_NAME BitShares )\nset( CUSTOM_URL_SCHEME \"gcs\" )\nset( INSTALLER_APP_ID \"68ad7005-8eee-49c9-95ce-9eed97e5b347\" )\n\nset( CMAKE_CXX_STANDARD 14 )\nset( CMAKE_CXX_STANDARD_REQUIRED ON )\n\nif( \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\" OR\n    \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" )\n  set( CMAKE_CXX_EXTENSIONS ON ) # for __int128 support\nelse()\n  set( CMAKE_CXX_EXTENSIONS OFF )\nendif()\n\n# http://stackoverflow.com/a/18369825\nif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n    if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8)\n        message(FATAL_ERROR \"GCC version must be at least 4.8!\")\n    endif()\nelseif (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\")\n    if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.3)\n        message(FATAL_ERROR \"Clang version must be at least 3.3!\")\n    endif()\nelseif (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"MSVC\")\n    if (\"${CMAKE_CXX_COMPILER_VERSION}\" VERSION_LESS \"19.0\")\n      message(FATAL_ERROR \"MSVC version must be at least 19.0 (Visual Studio 2015 Update 1)!\")\n    endif()\n\n    # allow MSVC VS2015 with Update 1, other 2015 versions are not supported\n    if (\"${CMAKE_CXX_COMPILER_VERSION}\" VERSION_EQUAL \"19.0\")\n        if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL \"19.0.23506.0\")\n            message(FATAL_ERROR \"Your version ${CMAKE_CXX_COMPILER_VERSION} of MSVC is not supported, use version 19.0.23506.0 (Visual Studio 2015 Update 1)!\")\n        endif()\n    endif()\nendif()\n\nlist( APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules\" )\n\ninclude(CheckCCompilerFlag)\ninclude(Utils)\n\n# function to help with cUrl\nmacro(FIND_CURL)\n   if (NOT WIN32 AND NOT APPLE AND CURL_STATICLIB)\n      find_package(OpenSSL REQUIRED)\n      set (OLD_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})\n      set (CMAKE_FIND_LIBRARY_SUFFIXES .a)\n      find_package(CURL REQUIRED)\n      list(APPEND CURL_LIBRARIES ${OPENSSL_LIBRARIES} ${BOOST_THREAD_LIBRARY} ${CMAKE_DL_LIBS})\n      set (CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_SUFFIXES})\n   else (NOT WIN32 AND NOT APPLE AND CURL_STATICLIB)\n      find_package(CURL REQUIRED)\n   endif (NOT WIN32 AND NOT APPLE AND CURL_STATICLIB)\n\n   if( APPLE AND NOT \"${CURL_VERSION_STRING}\" VERSION_LESS \"7.77.0\" )\n      list( APPEND CURL_LIBRARIES \"-framework CoreFoundation\" )\n      list( APPEND CURL_LIBRARIES \"-framework SystemConfiguration\" )\n   endif()\n\n   message(STATUS \"CURL libraries: ${CURL_LIBRARIES}\")\n\n   if( WIN32 )\n     if ( MSVC )\n       list( APPEND CURL_LIBRARIES Wldap32 )\n     endif( MSVC )\n\n     if( MINGW )\n       # MinGW requires a specific order of included libraries ( CURL before ZLib )\n       find_package( ZLIB REQUIRED )\n       list( APPEND CURL_LIBRARIES ${ZLIB_LIBRARY} pthread )\n     endif( MINGW )\n\n     list( APPEND CURL_LIBRARIES ${PLATFORM_SPECIFIC_LIBS} )\n   endif( WIN32 )\nendmacro()\n\n# Save the old value of CMAKE_REQUIRED_FLAGS\nset( TEMP_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} )\n\n# Check submodules\nif(NOT MANUAL_SUBMODULES)\n  find_package(Git)\n  if(GIT_FOUND)\n    function (check_submodule relative_path)\n      execute_process(COMMAND git rev-parse \"HEAD\" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE localHead)\n      execute_process(COMMAND git rev-parse \"HEAD:${relative_path}\" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE checkedHead)\n      string(COMPARE EQUAL \"${localHead}\" \"${checkedHead}\" upToDate)\n      if (upToDate)\n        message(STATUS \"Submodule '${relative_path}' is up-to-date\")\n      else()\n        message(FATAL_ERROR \"Submodule '${relative_path}' is not up-to-date. Please update all submodules with\\ngit submodule update --init --recursive --force\\nor run cmake with -DMANUAL_SUBMODULES=1\\n\")\n      endif()\n    endfunction ()\n\n    message(STATUS \"Checking submodules\")\n    check_submodule(docs)\n    check_submodule(libraries/fc)\n  endif()\nendif()\n# Make sure to always re-run the test.\nunset( MANUAL_SUBMODULES CACHE )\n\n# Fortify source\nif (CMAKE_COMPILER_IS_GNUCXX)\n\tif (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\")\n\t\tmessage (STATUS \"Setting optimizations for clang++\")\n\t\tset(CMAKE_CXX_FLAGS_RELEASE \"-D_FORTIFY_SOURCE=2 -O3 -DNDEBUG=1\")\n\t\tset(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"-D_FORTIFY_SOURCE=2 -O3 -DNDEBUG=1 -g\")\n\n\t\t# check and add data execution prevention\n\t\tmessage (STATUS \"Enabling data execution prevention\")\n\t\tadd_linker_flag(\"-fsanitize=safe-stack\")\n\n\t\t# check and add Stack-based buffer overrun detection\n\t\tset(CMAKE_REQUIRED_FLAGS \"-fstack-protector\")\n\t\tcheck_c_compiler_flag(\"\" HAVE_STACKPROTECTOR)\n\t\tif(HAVE_STACKPROTECTOR)\n\t\t\tmessage (STATUS \"Enabling stack-based buffer overrun detection\")\n\t\t\tadd_flag_append(CMAKE_C_FLAGS \"-fstack-protector\")\n\t\t\tadd_flag_append(CMAKE_CXX_FLAGS \"-fstack-protector\")\n\t\tendif()\n\telse ()\n\t\tmessage (STATUS \"Setting optimizations for g++\")\n\t\tset(CMAKE_CXX_FLAGS_RELEASE \"-D_FORTIFY_SOURCE=2 -O3 -DNDEBUG=1\")\n\t\tset(CMAKE_CXX_FLAGS_RELWITHDEBINFO \"-D_FORTIFY_SOURCE=2 -O3 -DNDEBUG=1 -g\")\n\n\t\t# check and add data execution prevention\n\t\tset(CMAKE_REQUIRED_FLAGS \"-Wl,-znoexecstack\")\n\t\tcheck_c_compiler_flag(\"\" HAVE_NOEXECSTACK)\n\t\tif(HAVE_NOEXECSTACK)\n\t\t\tmessage (STATUS \"Enabling data execution prevention\")\n\t\t\tadd_linker_flag(\"-znoexecstack\")\n\t\tendif()\n\n\t\t# check and add Stack-based buffer overrun detection\n\t\tset(CMAKE_REQUIRED_FLAGS \"-fstack-protector-strong\")\n\t\tcheck_c_compiler_flag(\"\" HAVE_STACKPROTECTOR)\n\t\tif(HAVE_STACKPROTECTOR)\n\t\t\tmessage (STATUS \"Enabling stack-based buffer overrun detection\")\n\t\t\tadd_flag_append(CMAKE_C_FLAGS \"-fstack-protector-strong\")\n\t\t\tadd_flag_append(CMAKE_CXX_FLAGS \"-fstack-protector-strong\")\n\t\tendif()\n\n\tendif ()\nendif ()\n\n# check for Data relocation and Protection (RELRO)\nset(CMAKE_REQUIRED_FLAGS \"-Wl,-zrelro,-znow\")\ncheck_c_compiler_flag(\"\" HAVE_RELROFULL)\nif(HAVE_RELROFULL)\n\tmessage (STATUS \"Enabling full data relocation and protection\")\n\tadd_linker_flag(\"-zrelro\")\n\tadd_linker_flag(\"-znow\")\nelse()\n\t#if full relro is not available, try partial relro\n\tset(CMAKE_REQUIRED_FLAGS \"-Wl,-zrelro\")\n\tcheck_c_compiler_flag(\"\" HAVE_RELROPARTIAL)\n\tif(HAVE_RELROPARTIAL)\n\t\tmessage (STATUS \"Enabling partial data relocation and protection\")\n\t\tadd_linker_flag(\"-zrelro\")\n\tendif()\nendif()\n\nset(CMAKE_REQUIRED_FLAGS ${TEMP_REQUIRED_FLAGS} )\n\n# position independent executetable (PIE)\n# position independent code (PIC)\nif (NOT MSVC)\n    add_definitions (-fPIC)\nendif(NOT MSVC)\n\nset(CMAKE_EXPORT_COMPILE_COMMANDS \"ON\")\nset( GRAPHENE_EGENESIS_JSON \"${CMAKE_CURRENT_SOURCE_DIR}/libraries/egenesis/genesis.json\"\n     CACHE STRING \"Path to embedded genesis file\" )\n\nif (USE_PCH)\n  include (cotire)\nendif(USE_PCH)\n\noption(USE_PROFILER \"Build with GPROF support(Linux).\" OFF)\n\n# Use Boost config file from fc\nset(Boost_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/libraries/fc/CMakeModules/Boost\")\n\nlist( APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/libraries/fc/GitVersionGen\" )\ninclude( GetGitRevisionDescription )\nget_git_head_revision( GIT_REFSPEC GIT_SHA2 )\n\nSET(BOOST_COMPONENTS)\nLIST(APPEND BOOST_COMPONENTS thread\n                             iostreams\n                             date_time\n                             system\n                             filesystem\n                             program_options\n                             chrono\n                             unit_test_framework\n                             context\n                             coroutine\n                             regex)\n# boost::endian is also required, but FindBoost can't handle header-only libs\nSET( Boost_USE_STATIC_LIBS ON CACHE STRING \"ON or OFF\" )\n\nIF(WIN32)\n   if($ENV{BOOST_ROOT})\n       SET(BOOST_ROOT $ENV{BOOST_ROOT})\n   endif($ENV{BOOST_ROOT})\n  set(Boost_USE_MULTITHREADED ON)\n  set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries\n  add_definitions(\"-DCURL_STATICLIB\")\n  list(APPEND PLATFORM_SPECIFIC_LIBS ws2_32 crypt32 mswsock userenv )\nELSE( WIN32 )\n   IF( APPLE )\n      set( CMAKE_THREAD_LIBS_INIT \"-lpthread\" )\n      set( CMAKE_HAVE_THREADS_LIBRARY 1 )\n      set( CMAKE_USE_WIN32_THREADS_INIT 0 )\n      set( CMAKE_USE_PTHREADS_INIT 1 )\n      set( THREADS_PREFER_PTHREAD_FLAG ON )\n   ENDIF( APPLE )\nENDIF(WIN32)\n\nFIND_PACKAGE(Boost CONFIG REQUIRED COMPONENTS ${BOOST_COMPONENTS})\n\n# enforce more strict compiler warnings and errors\nadd_compiler_flag_if_available(\"-Wall\")\nadd_compiler_flag_if_available(\"-Wclobbered\")\nadd_compiler_flag_if_available(\"-Wempty-body\")\nadd_compiler_flag_if_available(\"-Wformat-security\")\nadd_compiler_flag_if_available(\"-Wignored-qualifiers\")\nadd_compiler_flag_if_available(\"-Wimplicit-fallthrough=5\")\nadd_compiler_flag_if_available(\"-Wmissing-field-initializers\")\nadd_compiler_flag_if_available(\"-Wpointer-arith\")\nadd_compiler_flag_if_available(\"-Wshift-negative-value\")\nadd_compiler_flag_if_available(\"-Wtype-limits\")\nadd_compiler_flag_if_available(\"-Wunused-but-set-parameter\")\n\nif( WIN32 )\n\n    message( STATUS \"Configuring BitShares on WIN32\")\n\n    if ( MINGW )\n        message( STATUS \"Windows build using MinGW\" )\n        set( FULL_STATIC_BUILD TRUE )\n    else( MINGW )\n        set( ZLIB_LIBRARIES \"\" )\n    endif( MINGW )\n\n    SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ )\n\n    if( MSVC )\n        add_definitions(-DWIN32_LEAN_AND_MEAN)\n        #looks like this flag can have different default on some machines.\n        SET(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /SAFESEH:NO\")\n        SET(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO\")\n\n        # Probably cmake has a bug and vcxproj generated for executable in Debug conf. has disabled debug info\n        set(CMAKE_EXE_LINKER_FLAGS_DEBUG \"${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG\")\n    endif ( MSVC )\n\nelse( WIN32 ) # Apple AND Linux\n\n    if( APPLE )\n        # Apple Specific Options Here\n        message( STATUS \"Configuring BitShares on macOS\" )\n        set( CMAKE_CXX_FLAGS \"${CMAKE_C_FLAGS} -stdlib=libc++ -Wall -fvisibility-inlines-hidden -fvisibility=hidden\" )\n    else( APPLE )\n        if ( \"${CMAKE_SYSTEM_NAME}\" STREQUAL \"OpenBSD\" )\n            # OpenBSD Specific Options\n            message( STATUS \"Configuring BitShares on OpenBSD\" )\n        else()\n            # Linux Specific Options Here\n            message( STATUS \"Configuring BitShares on Linux\" )\n            set( rt_library rt )\n        endif()\n        # Common Linux & OpenBSD Options\n        set( CMAKE_CXX_FLAGS \"${CMAKE_C_FLAGS} -Wall\" )\n        if(USE_PROFILER)\n            set( CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -pg\" )\n        endif( USE_PROFILER )\n        set( pthread_library pthread)\n        if ( NOT DEFINED crypto_library )\n          # I'm not sure why this is here, I guess someone has openssl and can't detect it with find_package()?\n          # if you have a normal install, you can define crypto_library to the empty string to avoid a build error\n          set( crypto_library crypto)\n        endif ()\n    endif( APPLE )\n\n    if( \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\" )\n        set( CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fno-builtin-memcmp\" )\n    elseif( \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" )\n        if( CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.0.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0.0 )\n            set( CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization\" )\n        endif()\n    endif()\n\n    if( \"${CMAKE_GENERATOR}\" STREQUAL \"Ninja\" )\n        if( \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\" )\n            set( CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fcolor-diagnostics\" )\n        endif()\n    endif()\n\n    # based on http://www.delorie.com/gnu/docs/gdb/gdb_70.html\n    # uncomment this line to tell GDB about macros (slows compile times)\n    # set( CMAKE_CXX_FLAGS_DEBUG \"${CMAKE_CXX_FLAGS_DEBUG} -gdwarf-2 -g3\" )\n\nendif( WIN32 )\n\nif ( NOT MSVC AND FULL_STATIC_BUILD )\n    set( CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} -static -static-libstdc++ -static-libgcc\" )\nendif ( NOT MSVC AND FULL_STATIC_BUILD )\n\nset(ENABLE_COVERAGE_TESTING FALSE CACHE BOOL \"Build BitShares for code coverage analysis\")\n\nif(ENABLE_COVERAGE_TESTING)\n    SET(CMAKE_CXX_FLAGS \"--coverage ${CMAKE_CXX_FLAGS}\")\nendif()\n\nadd_subdirectory( libraries )\nadd_subdirectory( programs )\nadd_subdirectory( tests )\n\n\nif (ENABLE_INSTALLER)\n\nset(VERSION_MAJOR 0)\nset(VERSION_MINOR 1)\nset(VERSION_PATCH 0)\n\n\ninclude(InstallRequiredSystemLibraries)\n\nset(CPACK_OUTPUT_FILE_PREFIX ${CMAKE_BINARY_DIR}/packages)\nset(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install)\n\nSET(CPACK_PACKAGE_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\")\nset(CPACK_PACKAGE_NAME \"graphene\")\nset(CPACK_PACKAGE_VENDOR \"Cryptonomex, Inc.\")\nset(CPACK_PACKAGE_VERSION_MAJOR \"${VERSION_MAJOR}\")\nset(CPACK_PACKAGE_VERSION_MINOR \"${VERSION_MINOR}\")\nset(CPACK_PACKAGE_VERSION_PATCH \"${VERSION_PATCH}\")\nset(CPACK_PACKAGE_VERSION \"${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}\")\nset(CPACK_PACKAGE_DESCRIPTION \"A client for the BitShares network\")\nset(CPACK_PACKAGE_DESCRIPTION_SUMMARY \"A client for the BitShares network\")\nset(CPACK_RESOURCE_FILE_LICENSE \"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.md\")\nset(CPACK_PACKAGE_INSTALL_DIRECTORY \"BitShares ${CPACK_PACKAGE_VERSION}\")\n\nif(WIN32)\n SET(CPACK_GENERATOR \"ZIP;NSIS\")\n set(CPACK_PACKAGE_NAME \"BitShares\") # override above\n set(CPACK_NSIS_EXECUTABLES_DIRECTORY .)\n set(CPACK_NSIS_PACKAGE_NAME \"BitShares v${CPACK_PACKAGE_VERSION}\")\n set(CPACK_NSIS_DISPLAY_NAME \"${CPACK_NSIS_PACKAGE_NAME}\")\n set(CPACK_NSIS_DEFINES \"  !define MUI_STARTMENUPAGE_DEFAULTFOLDER \\\\\\\"BitShares\\\\\\\"\")\n # it seems like windows zip files usually don't have a single directory inside them, unix tgz frequently do\n SET(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)\n\nendif(WIN32)\n\nif(APPLE)\n  set(CPACK_GENERATOR \"DragNDrop\")\nendif()\n\nif(LINUX)\n  # Linux gets a .tgz\n  SET(CPACK_GENERATOR \"TGZ\")\n  SET(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 1)\nendif(LINUX)\n\n include(CPack)\nendif(ENABLE_INSTALLER)\n\nMESSAGE( STATUS \"\" )\nMESSAGE( STATUS \"PROFILER: ${USE_PROFILER}\" )\nMESSAGE( STATUS \"\" )\n"
  },
  {
    "path": "CONTRIBUTORS.txt",
    "content": "Contributors to this repository, in descending order by number of commits. Update with\n  head -5 CONTRIBUTORS.txt >contrib.tmp && git shortlog -s -e -n | cut -f 2 >>contrib.tmp && mv contrib.tmp CONTRIBUTORS.txt\n\n==============================================================================\n\nabitmore <abitmore@users.noreply.github.com>\nAlfredo Garcia <oxarbitrage@gmail.com>\nJohn M. Jones <jmjatlanta@gmail.com>\ntheoreticalbts <theoreticalbts@users.noreply.github.com>\nPeter Conrad <conrad@quisquis.de>\nDaniel Larimer <bytemaster@users.noreply.github.com>\nNathan Hourt <themodprobe@protonmail.com>\nVikram Rajkumar <vikramrajkumar@users.noreply.github.com>\nEric Frias <efrias@syncad.com>\nValera Cogut <info@valeracogut.com>\nMichel Santos <MichelSantos@users.noreply.github.com>\nXiaodong Li <mantianyu@gmail.com>\nFabian Schuh <Fabian@chainsquad.com>\nmanikey123 <mansiimohan@gmail.com>\nChristopher Sanborn <23085117+christophersanborn@users.noreply.github.com>\ncrypto-ape <43807588+crypto-ape@users.noreply.github.com>\nlubos.ilcik <lubos.ilcik@touch4it.com>\nMatias Romeo <matias.romeo@gmail.com>\nOpenLedger <service.github@openledger.info>\nSigve Kvalsvik <bitsharesblocks@gmail.com>\nalbert <393259066@qq.com>\nRyan R. Fox <ryan@ryanrfox.com>\nValentine Zavgorodnev <i@valzav.com>\nMichael Vandeberg <mvandeberg@users.noreply.github.com>\nJames Calfee <james@jcalfee.info>\nioBanker <37595908+ioBanker@users.noreply.github.com>\nAlexey Frolov <alexey.frolov@aetsoft.by>\nsyalon <hanomirin@foxmail.com>\ntakaaki7 <nakama67006700@gmail.com>\nNicolas Wack <wackou@gmail.com>\nTaconator <TheTaconator@users.noreply.github.com>\nQi Xing <cwyyprog@163.com>\nAnton Autushka <antoninus.liberalis@gmail.com>\nChronos <chronos.crypto@gmail.com>\nWei Yang <richard.weiyang@gmail.com>\nZapata <marco.tessari@gmail.com>\nbtcinshares <btcinshares@protonmail.com>\ncrazybits <crazybit.github@gmail.com>\nAnzhy Cherrnyavski <a.chernyavski@pixelplex.io>\nTengfei Niu <spartucus@users.noreply.github.com>\nTiago Peralta <tperalta82@gmail.com>\nxiao93 <42384581+xiao93@users.noreply.github.com>\nKarl Semich <0xloem@gmail.com>\nSahkanDesertHawk <panasiuki@gmail.com>\nScott Howard <showard314@gmail.com>\nTechnoL0g <9394904+technologiespro@users.noreply.github.com>\nTydus <Tydus@Tydus.org>\nWilliam <tmfc@homtail.com>\nbangzi1001 <36911788+bangzi1001@users.noreply.github.com>\nd.yakovitsky <d.yakovitsky@aetsoft.by>\nddylko <ddylko@ddylkoPC>\niHashFury <iPerky@users.noreply.github.com>\nnecklace <necklace@163.com>\nxuquan316 <xuquan316@vip.qq.com>\nBartek Wrona <wrona@syncad.com>\nBhuzOr <giaquinta.adriano@gmail.com>\nBlockchain Projects BV <info@blockchainprojectsbv.com>\nBruce Steedman <MatzFan@users.noreply.github.com>\nCharles Cooper <cooper.charles.m@gmail.com>\nErkan Yilmaz <erkan77@gmail.com>\nHaruka Ma <mrx@hcc.im>\nJaewoo Cho <clayop@gmail.com>\nJose Marcial Vieira Bisneto <marcial.vieirab@gmail.com>\nJozef Knaperek <jknaperek@gmail.com>\nKen Code <ken@BitShares-Munich.de>\nKrzysztof Szumny <krzysztof.szumny@stxnext.pl>\nMassimo Paladin <massimo.paladin@gmail.com>\nPaul Brossier <piem@piem.org>\nRoelandp <dnaleor@gmail.com>\nSemen Martynov <semen.martynov@gmail.com>\nThomas Freedman <thom@ozarkholler.com>\nTroglodactyl <troglodactyl@gmail.com>\nVoR0220 <catalanor0220@gmail.com>\nalt <pch957@163.com>\nbitcube <root@seed.cubeconnex.com>\nhammadsherwani <83015346+hammadsherwani@users.noreply.github.com>\nlafona <lafona@protonmail.com>\nliondani <liondani@gmx.com>\nlitepresence <finitestate@tutamail.com>\nlososeg <ya.lososeg@gmail.com>\nsinetek <pitwuu@gmail.com>\n"
  },
  {
    "path": "Dockerfile",
    "content": "# The image for building\nFROM phusion/baseimage:focal-1.2.0 as build\nENV LANG=en_US.UTF-8\n\n# Install dependencies\nRUN \\\n    apt-get update && \\\n    apt-get upgrade -y -o Dpkg::Options::=\"--force-confold\" && \\\n    apt-get update && \\\n    apt-get install -y \\\n      g++ \\\n      autoconf \\\n      cmake \\\n      git \\\n      libbz2-dev \\\n      libcurl4-openssl-dev \\\n      libssl-dev \\\n      libncurses-dev \\\n      libboost-thread-dev \\\n      libboost-iostreams-dev \\\n      libboost-date-time-dev \\\n      libboost-system-dev \\\n      libboost-filesystem-dev \\\n      libboost-program-options-dev \\\n      libboost-chrono-dev \\\n      libboost-test-dev \\\n      libboost-context-dev \\\n      libboost-regex-dev \\\n      libboost-coroutine-dev \\\n      libtool \\\n      doxygen \\\n      ca-certificates \\\n    && \\\n    apt-get clean && \\\n    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\nADD . /bitshares-core\nWORKDIR /bitshares-core\n\n# Compile\nRUN \\\n    ( git submodule sync --recursive || \\\n      find `pwd`  -type f -name .git | \\\n\twhile read f; do \\\n\t  rel=\"$(echo \"${f#$PWD/}\" | sed 's=[^/]*/=../=g')\"; \\\n\t  sed -i \"s=: .*/.git/=: $rel/=\" \"$f\"; \\\n\tdone && \\\n      git submodule sync --recursive ) && \\\n    git submodule update --init --recursive && \\\n    cmake \\\n        -DCMAKE_BUILD_TYPE=Release \\\n\t-DGRAPHENE_DISABLE_UNITY_BUILD=ON \\\n        . && \\\n    make witness_node cli_wallet get_dev_key && \\\n    install -s programs/witness_node/witness_node \\\n               programs/genesis_util/get_dev_key \\\n               programs/cli_wallet/cli_wallet \\\n            /usr/local/bin && \\\n    #\n    # Obtain version\n    mkdir -p /etc/bitshares && \\\n    git rev-parse --short HEAD > /etc/bitshares/version && \\\n    cd / && \\\n    rm -rf /bitshares-core\n\n# The final image\nFROM phusion/baseimage:focal-1.2.0\nLABEL maintainer=\"The bitshares decentralized organisation\"\nENV LANG=en_US.UTF-8\n\n# Install required libraries\nRUN \\\n    apt-get update && \\\n    apt-get upgrade -y -o Dpkg::Options::=\"--force-confold\" && \\\n    apt-get update && \\\n    apt-get install --no-install-recommends -y \\\n      libcurl4 \\\n      ca-certificates \\\n    && \\\n    mkdir -p /etc/bitshares && \\\n    apt-get clean && \\\n    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\nCOPY --from=build /usr/local/bin/* /usr/local/bin/\nCOPY --from=build /etc/bitshares/version /etc/bitshares/\n\nWORKDIR /\nRUN groupadd -g 10001 bitshares\nRUN useradd -u 10000 -g bitshares -s /bin/bash -m -d /var/lib/bitshares --no-log-init bitshares\nENV HOME /var/lib/bitshares\nRUN chown bitshares:bitshares -R /var/lib/bitshares\n\n# default exec/config files\nADD docker/default_config.ini /etc/bitshares/config.ini\nADD docker/default_logging.ini /etc/bitshares/logging.ini\nADD docker/bitsharesentry.sh /usr/local/bin/bitsharesentry.sh\nRUN chmod a+x /usr/local/bin/bitsharesentry.sh\n\n# Volume\nVOLUME [\"/var/lib/bitshares\", \"/etc/bitshares\"]\n\n# rpc service:\nEXPOSE 8090\n# p2p service:\nEXPOSE 1776\n\n# Make Docker send SIGINT instead of SIGTERM to the daemon\nSTOPSIGNAL SIGINT\n\n# Temporarily commented out due to permission issues caused by older versions, to be restored in a future version\n#USER bitshares:bitshares\n\n# default execute entry\nENTRYPOINT [\"/usr/local/bin/bitsharesentry.sh\"]\n"
  },
  {
    "path": "Doxyfile",
    "content": "# Doxyfile 1.8.9.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"BitShares-Core\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         = \"7.0.0\"\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          = \"BitShares blockchain node software and command-line wallet software\"\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = doxygen\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces.\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = README.md libraries\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank the\n# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,\n# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,\n# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,\n# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,\n# *.qsf, *.as and *.js.\n\nFILE_PATTERNS          = *.cpp *.hpp *.hxx\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = libraries/fc/vendor libraries/fc/tests\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        = boost\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE = \"README.md\"\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. To get the times font for\n# instance you can specify\n# EXTRA_PACKAGES=times\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = YES\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sf.net) file that captures the\n# structure of the code including all documentation. Note that this feature is\n# still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot.\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif and svg.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2015-2023 Cryptonomex Inc. <contact@cryptonomex.com> and\ncontributors (see CONTRIBUTORS.txt)\n\nThe MIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README-docker.md",
    "content": "# Docker Container\n\nThis repository comes with built-in Dockerfile to support docker\ncontainers. This README serves as documentation.\n\n## Dockerfile Specifications\n\nThe `Dockerfile` performs the following steps:\n\n1. Obtain base image (phusion/baseimage:0.10.1)\n2. Install required dependencies using `apt-get`\n3. Add bitshares-core source code into container\n4. Update git submodules\n5. Perform `cmake` with build type `Release`\n6. Run `make` and `make_install` (this will install binaries into `/usr/local/bin`\n7. Purge source code off the container\n8. Add a local bitshares user and set `$HOME` to `/var/lib/bitshares`\n9. Make `/var/lib/bitshares` and `/etc/bitshares` a docker *volume*\n10. Expose ports `8090` and `1776`\n11. Add default config from `docker/default_config.ini` and\n    `docker/default_logging.ini`\n12. Add an entry point script\n13. Run the entry point script by default\n\nThe entry point simplifies the use of parameters for the `witness_node`\n(which is run by default when spinning up the container).\n\n### Supported Environmental Variables\n\n* `$BITSHARESD_SEED_NODES`\n* `$BITSHARESD_RPC_ENDPOINT`\n* `$BITSHARESD_PLUGINS`\n* `$BITSHARESD_REPLAY`\n* `$BITSHARESD_RESYNC`\n* `$BITSHARESD_P2P_ENDPOINT`\n* `$BITSHARESD_WITNESS_ID`\n* `$BITSHARESD_PRIVATE_KEY`\n* `$BITSHARESD_TRACK_ACCOUNTS`\n* `$BITSHARESD_PARTIAL_OPERATIONS`\n* `$BITSHARESD_MAX_OPS_PER_ACCOUNT`\n* `$BITSHARESD_ES_NODE_URL`\n* `$BITSHARESD_TRUSTED_NODE`\n\n### Default config\n\nThe default configuration is:\n\n    p2p-endpoint = 0.0.0.0:1776\n    rpc-endpoint = 0.0.0.0:8090\n    bucket-size = [60,300,900,1800,3600,14400,86400]\n    history-per-size = 1000\n    max-ops-per-account = 100\n    partial-operations = true\n\n# Docker Compose\n\nWith docker compose, multiple nodes can be managed with a single\n`docker-compose.yaml` file:\n\n    version: '3'\n    services:\n     main:\n      # Image to run\n      image: bitshares/bitshares-core:latest\n      # \n      volumes:\n       - ./docker/conf/:/etc/bitshares/\n      # Optional parameters\n      environment:\n       - BITSHARESD_ARGS=--help\n\n\n    version: '3'\n    services:\n     fullnode:\n      # Image to run\n      image: bitshares/bitshares-core:latest\n      environment:\n      # Optional parameters\n      environment:\n       - BITSHARESD_ARGS=--help\n      ports:\n       - \"0.0.0.0:8090:8090\"\n      volumes:\n      - \"bitshares-fullnode:/var/lib/bitshares\"\n\n\n# Docker Hub\n\nThis container is properly registered with docker hub under the name:\n\n* [bitshares/bitshares-core](https://hub.docker.com/r/bitshares/bitshares-core/)\n\nGoing forward, every release tag as well as all pushes to `develop` and\n`testnet` will be built into ready-to-run containers, there.\n\n# Docker Compose\n\nOne can use docker compose to setup a trusted full node together with a\ndelayed node like this:\n\n```\nversion: '3'\nservices:\n\n fullnode:\n  image: bitshares/bitshares-core:latest\n  ports:\n   - \"0.0.0.0:8090:8090\"\n  volumes:\n  - \"bitshares-fullnode:/var/lib/bitshares\"\n\n delayed_node:\n  image: bitshares/bitshares-core:latest\n  environment:\n   - 'BITSHARESD_PLUGINS=delayed_node witness'\n   - 'BITSHARESD_TRUSTED_NODE=ws://fullnode:8090'\n  ports:\n   - \"0.0.0.0:8091:8090\"\n  volumes:\n  - \"bitshares-delayed_node:/var/lib/bitshares\"\n  links: \n  - fullnode\n\nvolumes:\n bitshares-fullnode:\n```\n"
  },
  {
    "path": "README.md",
    "content": "BitShares Core\n==============\n\n[BitShares Core](https://github.com/bitshares/bitshares-core) is the BitShares blockchain node software and command-line wallet software.\nFor UI reference wallet software (browser-based wallet and desktop wallet) visit [BitShares UI](https://github.com/bitshares/bitshares-ui).\n\nVisit [BitShares.github.io](https://bitshares.github.io/) to learn about BitShares and join the community at [BitSharesTalk.org](https://bitsharestalk.org/).\n\nInformation for developers can be found in the [Wiki](https://github.com/bitshares/bitshares-core/wiki) and the [BitShares Developer Documentation Portal](https://docs.bitshares.dev/). Users interested in how BitShares works can go to the [BitShares Documentation](https://docs.bitshares.eu/) site.\n\nVisit [Awesome BitShares](https://github.com/bitshares/awesome-bitshares) to find more resources and links E.G. chat groups, client libraries and extended APIs.\n\n* [Getting Started](#getting-started)\n* [Support](#support)\n* [Using Built-In APIs](#using-built-in-apis)\n* [Accessing restrictable node API sets](#accessing-restrictable-node-api-sets)\n* [FAQ](#faq)\n* [License](#license)\n\n|Branch|Build Status|\n|---|---|\n|`master`|[![](https://github.com/bitshares/bitshares-core/workflows/macOS/badge.svg?branch=master)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"macOS\"+branch%3Amaster) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Debug/badge.svg?branch=master)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Debug\"+branch%3Amaster) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Release/badge.svg?branch=master)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Release\"+branch%3Amaster) [![](https://github.com/bitshares/bitshares-core/workflows/Windows%20MinGW64/badge.svg?branch=master)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Windows+MinGW64\"+branch%3Amaster) [![](https://github.com/bitshares/bitshares-core/workflows/Docker/badge.svg?branch=master)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A%22Docker%22+branch%3Amaster)|\n|`develop`|[![](https://github.com/bitshares/bitshares-core/workflows/macOS/badge.svg?branch=develop)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"macOS\"+branch%3Adevelop) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Debug/badge.svg?branch=develop)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Debug\"+branch%3Adevelop) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Release/badge.svg?branch=develop)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Release\"+branch%3Adevelop) [![](https://github.com/bitshares/bitshares-core/workflows/Windows%20MinGW64/badge.svg?branch=develop)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Windows+MinGW64\"+branch%3Adevelop) [![](https://github.com/bitshares/bitshares-core/workflows/Docker/badge.svg?branch=develop)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A%22Docker%22+branch%3Adevelop)|\n|`hardfork`|[![](https://github.com/bitshares/bitshares-core/workflows/macOS/badge.svg?branch=hardfork)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"macOS\"+branch%3Ahardfork) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Debug/badge.svg?branch=hardfork)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Debug\"+branch%3Ahardfork) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Release/badge.svg?branch=hardfork)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Release\"+branch%3Ahardfork) [![](https://github.com/bitshares/bitshares-core/workflows/Windows%20MinGW64/badge.svg?branch=hardfork)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Windows+MinGW64\"+branch%3Ahardfork) [![](https://github.com/bitshares/bitshares-core/workflows/Docker/badge.svg?branch=hardfork)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A%22Docker%22+branch%3Ahardfork)|\n|`testnet`|[![](https://github.com/bitshares/bitshares-core/workflows/macOS/badge.svg?branch=testnet)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"macOS\"+branch%3Atestnet) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Debug/badge.svg?branch=testnet)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Debug\"+branch%3Atestnet) [![](https://github.com/bitshares/bitshares-core/workflows/Ubuntu%20Release/badge.svg?branch=testnet)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Ubuntu+Release\"+branch%3Atestnet) [![](https://github.com/bitshares/bitshares-core/workflows/Windows%20MinGW64/badge.svg?branch=testnet)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A\"Windows+MinGW64\"+branch%3Atestnet) [![](https://github.com/bitshares/bitshares-core/workflows/Docker/badge.svg?branch=testnet)](https://github.com/bitshares/bitshares-core/actions?query=workflow%3A%22Docker%22+branch%3Atestnet)|\n|`master` of `bitshares-fc`|[![](https://github.com/bitshares/bitshares-fc/workflows/macOS/badge.svg?branch=master)](https://github.com/bitshares/bitshares-fc/actions?query=workflow%3A\"macOS\"+branch%3Amaster) [![](https://github.com/bitshares/bitshares-fc/workflows/Ubuntu%20Debug/badge.svg?branch=master)](https://github.com/bitshares/bitshares-fc/actions?query=workflow%3A\"Ubuntu+Debug\"+branch%3Amaster) [![](https://github.com/bitshares/bitshares-fc/workflows/Ubuntu%20Release/badge.svg?branch=master)](https://github.com/bitshares/bitshares-fc/actions?query=workflow%3A\"Ubuntu+Release\"+branch%3Amaster)|\n\n\nGetting Started\n---------------\n\nBuild instructions and additional documentation are available in the\n[Wiki](https://github.com/bitshares/bitshares-core/wiki).\n\nPrebuilt binaries can be found in the [releases page](https://github.com/bitshares/bitshares-core/releases) for download.\n\n\n### Installing Node and Command-Line Wallet Software\n\nWe recommend building on Ubuntu 20.04 LTS (64-bit)\n\n**Install Operating System Dependencies:**\n\n    sudo apt-get update\n    sudo apt-get install autoconf cmake make automake libtool git libboost-all-dev libssl-dev g++ libcurl4-openssl-dev doxygen\n\n**Build Node And Command-Line Wallet:**\n\n    git clone https://github.com/bitshares/bitshares-core.git\n    cd bitshares-core\n    git checkout master # may substitute \"master\" with current release tag\n    git submodule update --init --recursive\n    mkdir build\n    cd build\n    cmake -DCMAKE_BUILD_TYPE=Release ..\n    make\n\n**Upgrade Node And Command-Line Wallet:**\n\n    cd bitshares-core\n    git remote set-url origin https://github.com/bitshares/bitshares-core.git\n    git checkout master\n    git remote set-head origin --auto\n    git pull\n    git submodule update --init --recursive # this command may fail\n    git submodule sync --recursive\n    git submodule update --init --recursive\n    mkdir build\n    cd build\n    cmake -DCMAKE_BUILD_TYPE=Release ..\n    make\n\n**NOTE:**\n\n* BitShares requires a 64-bit operating system to build, and will not build on a 32-bit OS. Tested operating systems:\n  * Linux (heavily tested with Ubuntu LTS releases)\n  * macOS (various versions)\n  * Windows (various versions, Visual Studio and MinGW)\n  * OpenBSD (various versions)\n\n* BitShares requires [Boost](https://www.boost.org/) libraries to build, supports version `1.58` to `1.74`.\nNewer versions may work, but have not been tested.\nIf your system came pre-installed with a version of Boost libraries that you do not wish to use, you may\nmanually build your preferred version and use it with BitShares by specifying it on the CMake command line.\n\n  Example: `cmake -DBOOST_ROOT=/path/to/boost ..`\n\n* BitShares requires [OpenSSL](https://www.openssl.org/) libraries to build, supports version `1.0.2` to `1.1.1`.\nIf your system came pre-installed with a version of OpenSSL libraries that you do not wish to use, you may\nmanually build your preferred version and use it with BitShares by specifying it on the CMake command line.\n\n  Example: `cmake -DOPENSSL_ROOT_DIR=/path/to/openssl ..`\n\n\n### Running and Stopping Node Software\n\n**Run Node Software:**\n\nStay on `bitshares-core/build` directory before you run the below `witness_node` command\n\n    ./programs/witness_node/witness_node\n\nUnder `build` directory the node run will automatically create the directory `witness_node_data_dir` along with config files underneath then start synchronizing the blockchain.\nIt may take usually several hours to fully synchronize the blockchain data.\nThe blockchain data will be stored under the directory `witness_node_data_dir`.\n\n**Stop Node Software:**\n\nFor stopping the node run cleanly, you will need to access the node run terminal then press on `Ctrl+C` then wait for the run to stop, please note that it may take usually few minutes to exit the run.\nIt's recommended to use linux command [screen](https://help.ubuntu.com/community/Screen) to initiate the node run so you can go back to the node run screen to stop it.\n\n\n**IMPORTANT:** By default the node will start in reduced memory mode by using some of the commands detailed in [Memory reduction for nodes](https://github.com/bitshares/bitshares-core/wiki/Memory-reduction-for-nodes).\nIn order to run a full node with all the account histories which usually unnecessary, you need to remove `partial-operations` and `max-ops-per-account` from your config file. Please note that currently(2018-10-17) a full node will need more than 160GB of RAM to operate and required memory is growing fast. Consider the following table as **minimal requirements** before running a node:\n\n| Default | Full | Minimal  | ElasticSearch\n| --- | --- | --- | ---\n| 150G HDD, 16G RAM | 640G SSD, 64G RAM * | 120G HDD, 4G RAM | 1TB SSD, 32G RAM\n\n\\* For this setup, allocate at least 500GB of SSD as swap.\n\nTo use the command-line wallet or other wallets / clients with the node, the node need to be started with RPC connection enabled, which can be done by starting the node with the `--rpc-endpoint` parameter, E.G.\n\n    ./programs/witness_node/witness_node --rpc-endpoint=127.0.0.1:8090\n\nor configure it in the config file by editing `witness_node_data_dir/config.ini` as follows:\n\n    rpc-endpoint = 127.0.0.1:8090\n\nYou can run the program with `--help` parameter to see more info:\n\n    ./programs/witness_node/witness_node --help\n\n\n### Using Command-Line Wallet\n\nStay on `bitshares-core/build` directory before you run the below `cli_wallet` command\n\n    ./programs/cli_wallet/cli_wallet\n\n**IMPORTANT:** The `cli_wallet` or API interfaces to the node wouldn't be fully functional unless the node is fully synchronized with the blockchain. The `cli_wallet` command `info` will show result `head_block_age` which will tell you how far you are from the live current block of the blockchain.\n\nTo check your current block:\n\n    new >>> info\n\nTo query the blockchain, E.G. get info about an account:\n\n    new >>> get_account <account_name_or_id>\n\nIf you need to transact with your account but not only query, firstly set your initial password and unlock the wallet:\n\n* For non-Windows operating systems, you can type the commands and press `[ENTER]`, then input the password and press `[ENTER]`, in this case the password won't show:\n\n      new >>> set_password [ENTER]\n      Enter password:\n      locked >>> unlock [ENTER]\n      Enter password:\n      unlocked >>>\n\n* For Windows, or you'd like to show the password, type the commands with the password:\n\n      new >>> set_password <PASSWORD>\n      locked >>> unlock <PASSWORD>\n      unlocked >>>\n\nTo be able to transact with your account, import the corresponding private keys:\n\n    unlocked >>> import_key <ACCOUNT_NAME> <WIF_KEY>\n\nThe private keys will be encrypted and stored in the wallet file, the file name is `wallet.json` by default.\nThe private keys are accessible when the wallet is unlocked.\n\n    unlocked >>> dump_private_keys\n\nUse `lock` command to make the private keys inaccessible. There is no auto-lock feature so far.\n\n    unlocked >>> lock\n\nTo import your initial (genesis) balances, import the private keys corresponding to the balances:\n\n    unlocked >>> import_balance <ACCOUNT_NAME> [<WIF_KEY> ...] true\n\nUse `help` to see all available wallet commands.\n\n    >>> help\n\nUse `gethelp <COMMAND>` to see more info about individual commands. E.G.\n\n    >>> gethelp get_order_book\n\nThe definition of all commands is available in the\n[wallet.hpp](https://github.com/bitshares/bitshares-core/blob/master/libraries/wallet/include/graphene/wallet/wallet.hpp) source code file.\nCorresponding documentation can be found in the [Doxygen documentation](https://bitshares.github.io/doxygen/classgraphene_1_1wallet_1_1wallet__api.html).\n\nYou can run the program with `--help` parameter to see more info:\n\n    ./programs/cli_wallet/cli_wallet --help\n\nThere is also some info in the [Wiki](https://github.com/bitshares/bitshares-core/wiki/CLI-Wallet-Cookbook).\n\n\nSupport\n-------\n\nTechnical support is available in the [BitSharesTalk technical support subforum](https://bitsharestalk.org/index.php?board=45.0).\n\nBitShares Core bugs can be reported directly to the [issue tracker](https://github.com/bitshares/bitshares-core/issues).\n\nQuestions can be posted in [Github Discussions](https://github.com/bitshares/bitshares-core/discussions).\n\nBitShares UI bugs should be reported to the [UI issue tracker](https://github.com/bitshares/bitshares-ui/issues).\n\nUp to date online Doxygen documentation can be found at [https://bitshares.github.io/doxygen](https://bitshares.github.io/doxygen/hierarchy.html).\n\n\nUsing Built-In APIs\n-------------\n\n### Node API\n\nThe `witness_node` software provides several different API sets, known as *node API*.\n\nEach API set has its own ID and a name.\nWhen running `witness_node` with RPC connection enabled, initially two API sets are available:\n* API set with ID `0` has name *\"database\"*, it provides read-only access to the database,\n* API set with ID `1` has name *\"login\"*, it is used to login and gain access to additional, restrictable API sets.\n\nHere is an example using `wscat` package from `npm` for websockets:\n\n    $ npm install -g wscat\n    $ wscat -c ws://127.0.0.1:8090\n    > {\"id\":1, \"method\":\"call\", \"params\":[0,\"get_accounts\",[[\"1.2.0\"]]]}\n    < {\"id\":1,\"result\":[{\"id\":\"1.2.0\",\"annotations\":[],\"membership_expiration_date\":\"1969-12-31T23:59:59\",\"registrar\":\"1.2.0\",\"referrer\":\"1.2.0\",\"lifetime_referrer\":\"1.2.0\",\"network_fee_percentage\":2000,\"lifetime_referrer_fee_percentage\":8000,\"referrer_rewards_percentage\":0,\"name\":\"committee-account\",\"owner\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[],\"address_auths\":[]},\"active\":{\"weight_threshold\":6,\"account_auths\":[[\"1.2.5\",1],[\"1.2.6\",1],[\"1.2.7\",1],[\"1.2.8\",1],[\"1.2.9\",1],[\"1.2.10\",1],[\"1.2.11\",1],[\"1.2.12\",1],[\"1.2.13\",1],[\"1.2.14\",1]],\"key_auths\":[],\"address_auths\":[]},\"options\":{\"memo_key\":\"GPH1111111111111111111111111111111114T1Anm\",\"voting_account\":\"1.2.0\",\"num_witness\":0,\"num_committee\":0,\"votes\":[],\"extensions\":[]},\"statistics\":\"2.7.0\",\"whitelisting_accounts\":[],\"blacklisting_accounts\":[]}]}\n\nWe can do the same thing using an HTTP client such as `curl` for APIs which do not require login or other session state:\n\n    $ curl --data '{\"jsonrpc\": \"2.0\", \"method\": \"call\", \"params\": [0, \"get_accounts\", [[\"1.2.0\"]]], \"id\": 1}' http://127.0.0.1:8090/\n    {\"id\":1,\"result\":[{\"id\":\"1.2.0\",\"annotations\":[],\"membership_expiration_date\":\"1969-12-31T23:59:59\",\"registrar\":\"1.2.0\",\"referrer\":\"1.2.0\",\"lifetime_referrer\":\"1.2.0\",\"network_fee_percentage\":2000,\"lifetime_referrer_fee_percentage\":8000,\"referrer_rewards_percentage\":0,\"name\":\"committee-account\",\"owner\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[],\"address_auths\":[]},\"active\":{\"weight_threshold\":6,\"account_auths\":[[\"1.2.5\",1],[\"1.2.6\",1],[\"1.2.7\",1],[\"1.2.8\",1],[\"1.2.9\",1],[\"1.2.10\",1],[\"1.2.11\",1],[\"1.2.12\",1],[\"1.2.13\",1],[\"1.2.14\",1]],\"key_auths\":[],\"address_auths\":[]},\"options\":{\"memo_key\":\"GPH1111111111111111111111111111111114T1Anm\",\"voting_account\":\"1.2.0\",\"num_witness\":0,\"num_committee\":0,\"votes\":[],\"extensions\":[]},\"statistics\":\"2.7.0\",\"whitelisting_accounts\":[],\"blacklisting_accounts\":[]}]}\n\nWhen using an HTTP client, the API set ID can be replaced by the API set name, E.G.\n\n    $ curl --data '{\"jsonrpc\": \"2.0\", \"method\": \"call\", \"params\": [\"database\", \"get_accounts\", [[\"1.2.0\"]]], \"id\": 1}' http://127.0.0.1:8090/\n\nThe definition of all node APIs is available in the source code files including\n[database_api.hpp](https://github.com/bitshares/bitshares-core/blob/master/libraries/app/include/graphene/app/database_api.hpp)\nand [api.hpp](https://github.com/bitshares/bitshares-core/blob/master/libraries/app/include/graphene/app/api.hpp).\nCorresponding documentation can be found in Doxygen:\n* [database API](https://bitshares.github.io/doxygen/classgraphene_1_1app_1_1database__api.html)\n* [other APIs](https://bitshares.github.io/doxygen/namespacegraphene_1_1app.html)\n\n\n### Wallet API\n\nThe `cli_wallet` program can also be configured to serve **all of its commands** as APIs, known as *wallet API*.\n\nStart `cli_wallet` with RPC connection enabled:\n\n    $ ./programs/cli_wallet/cli_wallet --rpc-http-endpoint=127.0.0.1:8093\n\nAccess the wallet API using an HTTP client:\n\n    $ curl --data '{\"jsonrpc\": \"2.0\", \"method\": \"info\", \"params\": [], \"id\": 1}' http://127.0.0.1:8093/\n    $ curl --data '{\"jsonrpc\": \"2.0\", \"method\": \"get_account\", \"params\": [\"1.2.0\"], \"id\": 1}' http://127.0.0.1:8093/\n\nNote: The syntax to access wallet API is a bit different than accessing node API.\n\n**Important:**\n* When RPC connection is enabled for `cli_wallet`, sensitive data E.G. private keys which is accessible via commands will be accessible via RPC too. It is recommended that only open network connection to localhost or trusted addresses E.G. configure a firewall.\n* When using wallet API, sensitive data E.G. the wallet password and private keys is transmitted as plain text, thus may be vulnerable to network sniffing. It is recommended that only use wallet API with localhost, or in a clean network, and / or use `--rpc-tls-endpoint` parameter to only serve wallet API via secure connections.\n\n\nAccessing restrictable node API sets\n------------------------------------\n\nYou can restrict node API sets to particular users by specifying an `api-access` file in `config.ini`\nor by using the `--api-access /full/path/to/api-access.json` command-line option on node startup. Here is an example `api-access` file which allows\nuser `bytemaster` with password `supersecret` to access four different API sets, while allowing any other user to access the three public API sets\nnecessary to use the node:\n\n    {\n       \"permission_map\" :\n       [\n          [\n             \"bytemaster\",\n             {\n                \"password_hash_b64\" : \"9e9GF7ooXVb9k4BoSfNIPTelXeGOZ5DrgOYMj94elaY=\",\n                \"password_salt_b64\" : \"INDdM6iCi/8=\",\n                \"allowed_apis\" : [\"database_api\", \"network_broadcast_api\", \"history_api\", \"network_node_api\"]\n             }\n          ],\n          [\n             \"*\",\n             {\n                \"password_hash_b64\" : \"*\",\n                \"password_salt_b64\" : \"*\",\n                \"allowed_apis\" : [\"database_api\", \"network_broadcast_api\", \"history_api\"]\n             }\n          ]\n       ]\n    }\n\nNote: the `login` API set is always accessible.\n\nPasswords are stored in `base64` as salted `sha256` hashes.  A simple Python script,\n[`saltpass.py`](https://github.com/bitshares/bitshares-core/blob/master/programs/witness_node/saltpass.py)\nis available to obtain hash and salt values from a password.\nA single asterisk `\"*\"` may be specified as username or password hash to accept any value.\n\nWith the above configuration, here is an example of how to call the `add_node` API from the `network_node` API set:\n\n    {\"id\":1, \"method\":\"call\", \"params\":[1,\"login\",[\"bytemaster\", \"supersecret\"]]}\n    {\"id\":2, \"method\":\"call\", \"params\":[1,\"network_node\",[]]}\n    {\"id\":3, \"method\":\"call\", \"params\":[2,\"add_node\",[\"127.0.0.1:9090\"]]}\n\nNote, the call to `network_node` is necessary to obtain the correct API set ID for the `network_node` API set.  It is not guaranteed that the API set ID for the `network_node` API set will always be `2`.\n\nThe restricted API sets are accessible via HTTP too using *basic access authentication*. E.G.\n\n    $ curl --data '{\"jsonrpc\": \"2.0\", \"method\": \"call\", \"params\": [\"network_node\", \"add_node\", [\"127.0.0.1:9090\"]], \"id\": 1}' http://bytemaster:supersecret@127.0.0.1:8090/\n\nOur `doxygen` documentation contains the most up-to-date information\nabout APIs for the [node](https://bitshares.github.io/doxygen/namespacegraphene_1_1app.html) and the\n[wallet](https://bitshares.github.io/doxygen/classgraphene_1_1wallet_1_1wallet__api.html).\n\n\nFAQ\n---\n\n- Is there a way to generate help with parameter names and method descriptions?\n\n    Yes. Documentation of the code base, including APIs, can be generated using Doxygen. Simply run `doxygen` in this directory.\n\n    If both Doxygen and perl are available in your build environment, the command-line wallet's `help` and `gethelp`\n    commands will display help generated from the doxygen documentation.\n\n    If your command-line wallet's `help` command displays descriptions without parameter names like\n        `signed_transaction transfer(string, string, string, string, string, bool)`\n    it means CMake was unable to find Doxygen or perl during configuration.  If found, the\n    output should look like this:\n        `signed_transaction transfer(string from, string to, string amount, string asset_symbol, string memo, bool broadcast)`\n\n- Is there a way to allow external program to drive `cli_wallet` via websocket, JSONRPC, or HTTP?\n\n    Yes. External programs may connect to the command-line wallet and make its calls over a websockets API. To do this, run the wallet in\n    server mode, i.e. `cli_wallet -H \"127.0.0.1:9999\"` and then have the external program connect to it over the specified port\n    (in this example, port 9999). Please check the [\"Using Built-In APIs\"](#using-built-in-apis) section for more info.\n\n- Is there a way to access methods which require login over HTTP?\n\n    Yes. Most of the methods can be accessed by specifying the API name instead of an API ID. If an API is protected by a username and a password, it can be accessed by using *basic access authentication*. Please check the [\"Accessing restrictable node API sets\"](#accessing-restrictable-node-api-sets) section for more info.\n\n    However, HTTP is not really designed for \"server push\" notifications, and we would have to figure out a way to queue notifications for a polling client. Websockets solves this problem. If you need to access the stateful methods, use Websockets.\n\n- What is the meaning of `a.b.c` numbers?\n\n    The first number specifies the *space*.  Space `1` is for protocol objects, `2` is for implementation objects.\n    Protocol space objects can appear on the wire, for example in the binary form of transactions.\n    Implementation space objects cannot appear on the wire and solely exist for implementation\n    purposes, such as optimization or internal bookkeeping.\n\n    The second number specifies the *type*.  The type of the object determines what fields it has.  For a\n    complete list of type IDs, see `GRAPHENE_DEFINE_IDS(protocol, protocol_ids ...)` in\n    [protocol/types.hpp](https://github.com/bitshares/bitshares-core/blob/master/libraries/protocol/include/graphene/protocol/types.hpp)\n    and `GRAPHENE_DEFINE_IDS(chain, implementation_ids ...)` in [chain/types.hpp](https://github.com/bitshares/bitshares-core/blob/master/libraries/chain/include/graphene/chain/types.hpp).\n\n    The third number specifies the *instance*.  The instance of the object is different for each individual\n    object.\n\n- The answer to the previous question was really confusing.  Can you make it clearer?\n\n    All account IDs are of the form `1.2.x`.  If you were the 9735th account to be registered,\n    your account's ID will be `1.2.9735`.  Account `0` is special (it's the \"committee account\",\n    which is controlled by the committee members and has a few abilities and restrictions other accounts\n    do not).\n\n    All asset IDs are of the form `1.3.x`.  If you were the 29th asset to be registered,\n    your asset's ID will be `1.3.29`.  Asset `0` is special (it's BTS, which is considered the \"core asset\").\n\n    The first and second number together identify the kind of thing you're talking about (`1.2` for accounts,\n    `1.3` for assets).  The third number identifies the particular thing.\n\n- How do I get the `network_add_nodes` command to work?  Why is it so complicated?\n\n    You need to follow the instructions in the [\"Accessing restrictable node API sets\"](#accessing-restrictable-node-api-sets) section to\n    allow a username/password access to the `network_node` API set.  Then you need\n    to pass the username/password to the `cli_wallet` on the command line.\n\n    It's set up this way so that the default configuration is secure even if the RPC port is\n    publicly accessible.  It's fine if your `witness_node` allows the general public to query\n    the database or broadcast transactions (in fact, this is how the hosted web UI works).  It's\n    less fine if your `witness_node` allows the general public to control which p2p nodes it's\n    connecting to.  Therefore the API to add p2p connections needs to be set up with proper access\n    controls.\n\n\nLicense\n-------\n\nBitShares Core is under the MIT license. See [LICENSE](https://github.com/bitshares/bitshares-core/blob/master/LICENSE.txt)\nfor more information.\n"
  },
  {
    "path": "docker/bitsharesentry.sh",
    "content": "#!/bin/bash\nBITSHARESD=\"/usr/local/bin/witness_node\"\n\n# For blockchain download\nVERSION=`cat /etc/bitshares/version`\n\n## Supported Environmental Variables\n#\n#   * $BITSHARESD_SEED_NODES\n#   * $BITSHARESD_RPC_ENDPOINT\n#   * $BITSHARESD_PLUGINS\n#   * $BITSHARESD_REPLAY\n#   * $BITSHARESD_RESYNC\n#   * $BITSHARESD_P2P_ENDPOINT\n#   * $BITSHARESD_WITNESS_ID\n#   * $BITSHARESD_PRIVATE_KEY\n#   * $BITSHARESD_TRACK_ACCOUNTS\n#   * $BITSHARESD_PARTIAL_OPERATIONS\n#   * $BITSHARESD_MAX_OPS_PER_ACCOUNT\n#   * $BITSHARESD_ES_NODE_URL\n#   * $BITSHARESD_ES_START_AFTER_BLOCK\n#   * $BITSHARESD_TRUSTED_NODE\n#\n\nARGS=\"\"\n# Translate environmental variables\nif [[ ! -z \"$BITSHARESD_SEED_NODES\" ]]; then\n    for NODE in $BITSHARESD_SEED_NODES ; do\n        ARGS+=\" --seed-node=$NODE\"\n    done\nfi\nif [[ ! -z \"$BITSHARESD_RPC_ENDPOINT\" ]]; then\n    ARGS+=\" --rpc-endpoint=${BITSHARESD_RPC_ENDPOINT}\"\nfi\n\nif [[ ! -z \"$BITSHARESD_REPLAY\" ]]; then\n    ARGS+=\" --replay-blockchain\"\nfi\n\nif [[ ! -z \"$BITSHARESD_RESYNC\" ]]; then\n    ARGS+=\" --resync-blockchain\"\nfi\n\nif [[ ! -z \"$BITSHARESD_P2P_ENDPOINT\" ]]; then\n    ARGS+=\" --p2p-endpoint=${BITSHARESD_P2P_ENDPOINT}\"\nfi\n\nif [[ ! -z \"$BITSHARESD_WITNESS_ID\" ]]; then\n    ARGS+=\" --witness-id=$BITSHARESD_WITNESS_ID\"\nfi\n\nif [[ ! -z \"$BITSHARESD_PRIVATE_KEY\" ]]; then\n    ARGS+=\" --private-key=$BITSHARESD_PRIVATE_KEY\"\nfi\n\nif [[ ! -z \"$BITSHARESD_TRACK_ACCOUNTS\" ]]; then\n    for ACCOUNT in $BITSHARESD_TRACK_ACCOUNTS ; do\n        ARGS+=\" --track-account=$ACCOUNT\"\n    done\nfi\n\nif [[ ! -z \"$BITSHARESD_PARTIAL_OPERATIONS\" ]]; then\n    ARGS+=\" --partial-operations=${BITSHARESD_PARTIAL_OPERATIONS}\"\nfi\n\nif [[ ! -z \"$BITSHARESD_MAX_OPS_PER_ACCOUNT\" ]]; then\n    ARGS+=\" --max-ops-per-account=${BITSHARESD_MAX_OPS_PER_ACCOUNT}\"\nfi\n\nif [[ ! -z \"$BITSHARESD_ES_NODE_URL\" ]]; then\n    ARGS+=\" --elasticsearch-node-url=${BITSHARESD_ES_NODE_URL}\"\nfi\n\nif [[ ! -z \"$BITSHARESD_ES_START_AFTER_BLOCK\" ]]; then\n    ARGS+=\" --elasticsearch-start-es-after-block=${BITSHARESD_ES_START_AFTER_BLOCK}\"\nfi\n\nif [[ ! -z \"$BITSHARESD_TRUSTED_NODE\" ]]; then\n    ARGS+=\" --trusted-node=${BITSHARESD_TRUSTED_NODE}\"\nfi\n\n## Link the bitshares config file into home\n## This link has been created in Dockerfile, already\nln -f -s /etc/bitshares/config.ini /var/lib/bitshares\nln -f -s /etc/bitshares/logging.ini /var/lib/bitshares\n\nchown -R bitshares:bitshares /var/lib/bitshares\n\n# Get the latest security updates\napt-get update && apt-get upgrade -y -o Dpkg::Options::=\"--force-confold\"\n\n# Plugins need to be provided in a space-separated list, which\n# makes it necessary to write it like this\nif [[ ! -z \"$BITSHARESD_PLUGINS\" ]]; then\n   exec /usr/bin/setpriv --reuid=bitshares --regid=bitshares --clear-groups \\\n     \"$BITSHARESD\" --data-dir \"${HOME}\" ${ARGS} ${BITSHARESD_ARGS} --plugins \"${BITSHARESD_PLUGINS}\"\nelse\n   exec /usr/bin/setpriv --reuid=bitshares --regid=bitshares --clear-groups \\\n     \"$BITSHARESD\" --data-dir \"${HOME}\" ${ARGS} ${BITSHARESD_ARGS}\nfi\n"
  },
  {
    "path": "docker/default_config.ini",
    "content": "# Endpoint for P2P node to listen on\np2p-endpoint = 0.0.0.0:1776\n\n# P2P nodes to connect to on startup (may specify multiple times)\n# seed-node = \n\n# JSON array of P2P nodes to connect to on startup\n# seed-nodes = \n\n# Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.\n# checkpoint = \n\n# Endpoint for websocket RPC to listen on\nrpc-endpoint = 0.0.0.0:8090\n\n# Endpoint for TLS websocket RPC to listen on\n# rpc-tls-endpoint = \n\n# The TLS certificate file for this server\n# server-pem = \n\n# Password for this certificate\n# server-pem-password = \n\n# File to read Genesis State from\n# genesis-json = \n\n# Block signing key to use for init witnesses, overrides genesis file\n# dbg-init-key = \n\n# JSON file specifying API permissions\n# api-access = \n\n# Number of IO threads, default to 0 for auto-configuration\n# io-threads =\n\n# Whether allow API clients to subscribe to universal object creation and removal events\n# enable-subscribe-to-all =\n\n# Whether to enable tracking of votes of standby witnesses and committee members. Set it to true to provide accurate data to API clients, set to false for slightly better performance.\n# enable-standby-votes-tracking =\n\n# For history_api::get_account_history_operations to set max limit value\n# api-limit-get-account-history-operations = 100\n\n# For history_api::get_account_history to set max limit value\n# api-limit-get-account-history = 100\n\n# For orders_api::get_grouped_limit_orders to set max limit value\n# api-limit-get-grouped-limit-orders = 101\n\n# For history_api::get_relative_account_history to set max limit value\n# api-limit-get-relative-account-history = 100\n\n# For history_api::get_account_history_by_operations to set max limit value\n# api-limit-get-account-history-by-operations = 100\n\n# For asset_api::get_asset_holders to set max limit value\n# api-limit-get-asset-holders = 100\n\n# For database_api_impl::get_key_references to set max limit value\n# api-limit-get-key-references = 100\n\n# For database_api_impl::get_htlc_by_from and get_htlc_by_to to set max limit value\n# api-limit-get-htlc-by = 100\n\n# For database_api_impl::get_full_accounts to set max accounts to query at once\n# api-limit-get-full-accounts = 50\n\n# For database_api_impl::get_full_accounts to set max items to return in the lists\n# api-limit-get-full-accounts-lists = 500\n\n# For database_api_impl::get_top_voters to set max limit value\n# api-limit-get-top-voters = 200\n\n# For database_api_impl::get_call_orders and get_call_orders_by_account to set max limit value\n# api-limit-get-call-orders = 300\n\n# For database_api_impl::get_settle_orders and get_settle_orders_by_account to set max limit value\n# api-limit-get-settle-orders = 300\n\n# For database_api_impl::list_assets and get_assets_by_issuer to set max limit value\n# api-limit-get-assets = 101\n\n# For database_api_impl::get_limit_orders to set max limit value\n# api-limit-get-limit-orders = 300\n\n# For database_api_impl::get_limit_orders_by_account to set max limit value\n# api-limit-get-limit-orders-by-account = 101\n\n# For database_api_impl::get_order_book to set max limit value\n# api-limit-get-order-book = 50\n\n# For database_api_impl::lookup_accounts to set max limit value\n# api-limit-lookup-accounts = 1000\n\n# For database_api_impl::lookup_witness_accounts to set max limit value\n# api-limit-lookup-witness-accounts = 1000\n\n# For database_api_impl::lookup_committee_member_accounts to set max limit value\n# api-limit-lookup-committee-member-accounts = 1000\n\n# For database_api_impl::lookup_vote_ids to set max limit value\n# api-limit-lookup-vote-ids = 1000\n\n# For database_api_impl::get_account_limit_orders to set max limit value\n# api-limit-get-account-limit-orders = 101\n\n# For database_api_impl::get_collateral_bids to set max limit value\n# api-limit-get-collateral-bids = 100\n\n# For database_api_impl::get_top_markets to set max limit value\n# api-limit-get-top-markets = 100\n\n# For database_api_impl::get_trade_history to set max limit value\n# api-limit-get-trade-history = 100\n\n# For database_api_impl::get_trade_history_by_sequence to set max limit value\n# api-limit-get-trade-history-by-sequence = 100\n\n# For database_api_impl::get_withdraw_permissions_by_giver to set max limit value\n# api-limit-get-withdraw-permissions-by-giver = 101\n\n# For database_api_impl::get_withdraw_permissions_by_recipient to set max limit value\n# api-limit-get-withdraw-permissions-by-recipient = 101\n\n# Space-separated list of plugins to activate\nplugins = witness account_history market_history grouped_orders api_helper_indexes custom_operations\n\n# Do not exit if api_helper_indexes plugin is not enabled.\n# ignore-api-helper-indexes-warning = true\n\n\n# ==============================================================================\n# witness plugin options\n# ==============================================================================\n\n# Enable block production, even if the chain is stale.\nenable-stale-production = false\n\n# Percent of witnesses (0-100) that must be participating in order to produce blocks\n# required-participation = 33\n\n# ID of witness controlled by this node (e.g. \"1.6.5\", quotes are required, may specify multiple times)\n# witness-id = \n\n# Tuple of [PublicKey, WIF private key] (may specify multiple times)\n# private-key = [\"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"]\n\n# Path to a file containing tuples of [PublicKey, WIF private key]. The file has to contain exactly one tuple (i.e. private - public key pair) per line. This option may be specified multiple times, thus multiple files can be provided.\n# private-key-file =\n\n\n# ==============================================================================\n# debug_witness plugin options\n# ==============================================================================\n\n# Tuple of [PublicKey, WIF private key] (may specify multiple times)\n# debug-private-key = [\"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"]\n\n\n# ==============================================================================\n# account_history plugin options\n# ==============================================================================\n\n# Account ID to track history for (may specify multiple times)\n# track-account = \n\n# Keep only those operations in memory that are related to account history tracking\npartial-operations = true\n\n# Maximum number of operations per account will be kept in memory\nmax-ops-per-account = 100\n\n\n# ==============================================================================\n# elasticsearch plugin options\n# ==============================================================================\n\n# Elastic Search database node url(http://localhost:9200/)\n# elasticsearch-node-url =\n\n# Number of bulk documents to index on replay(10000)\n# elasticsearch-bulk-replay =\n\n# Number of bulk documents to index on a syncronied chain(100)\n# elasticsearch-bulk-sync =\n\n# Use visitor to index additional data(slows down the replay(false))\n# elasticsearch-visitor =\n\n# Pass basic auth to elasticsearch database('')\n# elasticsearch-basic-auth =\n\n# Add a prefix to the index(bitshares-)\n# elasticsearch-index-prefix =\n\n# Save operation as object(false)\n# elasticsearch-operation-object =\n\n# Start doing ES job after block(0)\n# elasticsearch-start-es-after-block =\n\n# Save operation as string. Needed to serve history api calls(true)\n# elasticsearch-operation-string =\n\n# Mode of operation: only_save(0), only_query(1), all(2) - Default: 0\n# elasticsearch-mode =\n\n\n# ==============================================================================\n# market_history plugin options\n# ==============================================================================\n\n# Track market history by grouping orders into buckets of equal size measured in seconds specified as a JSON array of numbers\nbucket-size = [60,300,900,3600,14400,86400,604800]\n\n# How far back in time to track history for each bucket size, measured in the number of buckets (default: 1000)\nhistory-per-size = 1500\n\n# 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)\nmax-order-his-records-per-market = 1000\n\n# Will only store matched orders in last X seconds for each market in order history for querying, or those meet the other option, which has more data (default: 259200 (3 days))\nmax-order-his-seconds-per-market = 259200\n\n\n# ==============================================================================\n# delayed_node plugin options\n# ==============================================================================\n\n# RPC endpoint of a trusted validating node (required for delayed_node)\n# trusted-node =\n\n\n# ==============================================================================\n# snapshot plugin options\n# ==============================================================================\n\n# Block number after which to do a snapshot\n# snapshot-at-block =\n\n# Block time (ISO format) after which to do a snapshot\n# snapshot-at-time =\n\n# Pathname of JSON file where to store the snapshot\n# snapshot-to =\n\n\n# ==============================================================================\n# es_objects plugin options\n# ==============================================================================\n\n# Elasticsearch node url(http://localhost:9200/)\n# es-objects-elasticsearch-url =\n\n# Basic auth username:password('')\n# es-objects-auth =\n\n# Number of bulk documents to index on replay(10000)\n# es-objects-bulk-replay =\n\n# Number of bulk documents to index on a synchronized chain(100)\n# es-objects-bulk-sync =\n\n# Store proposal objects(true)\n# es-objects-proposals =\n\n# Store account objects(true)\n# es-objects-accounts =\n\n# Store asset objects(true)\n# es-objects-assets =\n\n# Store balances objects(true)\n# es-objects-balances =\n\n# Store limit order objects(true)\n# es-objects-limit-orders =\n\n# Store feed data(true)\n# es-objects-asset-bitasset =\n\n# Add a prefix to the index(objects-)\n# es-objects-index-prefix =\n\n# Keep only current state of the objects(true)\n# es-objects-keep-only-current =\n\n# Start doing ES job after block(0)\n# es-objects-start-es-after-block =\n\n\n# ==============================================================================\n# grouped_orders plugin options\n# ==============================================================================\n\n# Group orders by percentage increase on price. Specify a JSON array of numbers here, each number is a group, number 1 means 0.01%.\n# tracked-groups = [10,100]\n\n\n# ==============================================================================\n# logging options\n# ==============================================================================\n#\n# Logging configuration is loaded from logging.ini by default.\n# If logging.ini exists, logging configuration added in this file will be ignored.\n"
  },
  {
    "path": "docker/default_logging.ini",
    "content": "# declare an appender named \"stderr\" that writes messages to the console\n[log.console_appender.stderr]\nstream=std_error\n\n# declare an appender named \"default\" that writes messages to default.log\n[log.file_appender.default]\n# filename can be absolute or relative to this config file\nfilename=logs/default/default.log\n# Rotate log every ? minutes, if leave out default to 60\nrotation_interval=60\n# how long will logs be kept (in days), if leave out default to 1\nrotation_limit=7\n\n# declare an appender named \"p2p\" that writes messages to p2p.log\n[log.file_appender.p2p]\n# filename can be absolute or relative to this config file\nfilename=logs/p2p/p2p.log\n# Rotate log every ? minutes, if leave out default to 60\nrotation_interval=60\n# how long will logs be kept (in days), if leave out default to 1\nrotation_limit=7\n\n# declare an appender named \"rpc\" that writes messages to rpc.log\n[log.file_appender.rpc]\n# filename can be absolute or relative to this config file\nfilename=logs/rpc/rpc.log\n# Rotate log every ? minutes, if leave out default to 60\nrotation_interval=60\n# how long will logs be kept (in days), if leave out default to 1\nrotation_limit=7\n\n# route any messages logged to the default logger to the \"stderr\" appender and\n# \"default\" appender we declared above, if they are info level or higher\n[logger.default]\nlevel=info\nappenders=stderr,default\n\n# route messages sent to the \"p2p\" logger to the \"p2p\" appender declared above\n[logger.p2p]\nlevel=warn\nappenders=p2p\n\n# route messages sent to the \"rpc\" logger to the \"rpc\" appender declared above\n[logger.rpc]\nlevel=error\nappenders=rpc\n\n"
  },
  {
    "path": "libraries/CMakeLists.txt",
    "content": "add_subdirectory( fc )\nadd_subdirectory( db )\nadd_subdirectory( chain )\nadd_subdirectory( egenesis )\nadd_subdirectory( net )\nadd_subdirectory( utilities )\nadd_subdirectory( app )\nadd_subdirectory( plugins )\nadd_subdirectory( wallet )\nadd_subdirectory( protocol )\n"
  },
  {
    "path": "libraries/README.md",
    "content": "# BitShares Libraries\n\nThe libraries are the core of the project and define everything where applications can build on top.\n\nA **graphene** blockchain software will use the `app` library to define what the application will do, what services it will offer. The blockchain is defined by the `chain` library and includes all the objects, types, operations, protocols that build current consensus blockchain. The lowest level in memory database of Bitshares is developed at the `db` library. The `fc` is a helper module broadly used in the libraries code, `egenesis` will help with the genesis file, `plugins` will be loaded optionally to the application. Wallet software like the cli_wallet will benefit from the `wallet` library.\n\nCode in libraries is the most important part of **bitshares-core** project and it is maintained by the Bitshares Core Team and contributors.\n# Available Libraries\n\nFolder | Name | Description | Status\n---|---|---|---\n[app](app) | Application | Bundles component libraries (chain, network, plugins) into a useful application. Also provides API access. | Active \n[chain](chain) | Blockchain | Blockchain implementation and business logic. Database structure in the form of objects and updates to the blockchain in the form of evaluators are implemented here. | Active \n[db](db) | Database | Defines the internal database graphene uses. | Active \n[egenesis](egenesis) | Genesis | Hardcodes the `genesis.json` file into the `witness_node` executable.| Active\n[fc](fc) | Fast-compiling C++ library | https://github.com/bitshares/bitshares-fc | Active \n[net](net) | Network | The graphene p2p layer. | Active \n[plugins](plugins) | Plugins | Collection of singleton designed modules used for extending the bitshares-core.  | Active \n[protocol](protocol) | Protocol | Fundamental structure of the data that will be transmitted on the wire. Operations are defined and basic data integrity checks are done for each.  | Active \n[utilities](utilities) | Utilities | Common utility calls used in applications or other libraries. | Active \n[wallet](wallet) | Wallet | Wallet definition for the `cli_wallet` software. | Active\n"
  },
  {
    "path": "libraries/app/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/app/*.hpp\")\nfile(GLOB EGENESIS_HEADERS \"../egenesis/include/graphene/app/*.hpp\")\n\nadd_library( graphene_app \n             api.cpp\n             api_objects.cpp\n             application.cpp\n             util.cpp\n             database_api.cpp\n             plugin.cpp\n             config_util.cpp\n             ${HEADERS}\n             ${EGENESIS_HEADERS}\n           )\n\n# need to link graphene_debug_witness because plugins aren't sufficiently isolated #246\ntarget_link_libraries( graphene_app\n                       graphene_market_history graphene_account_history graphene_elasticsearch graphene_grouped_orders\n                       graphene_api_helper_indexes graphene_custom_operations graphene_debug_witness\n                       graphene_chain graphene_net graphene_utilities fc )\ntarget_include_directories( graphene_app\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\"\n                            \"${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include\" )\n\nif(MSVC)\n  set_source_files_properties( application.cpp api.cpp database_api.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\nINSTALL( TARGETS\n   graphene_app\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/app\" )\n"
  },
  {
    "path": "libraries/app/api.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <cctype>\n\n#include <graphene/app/api.hpp>\n#include <graphene/app/api_access.hpp>\n\n#include \"database_api_helper.hxx\"\n\n#include <fc/crypto/base64.hpp>\n#include <fc/rpc/api_connection.hpp>\n#include <fc/thread/future.hpp>\n\ntemplate class fc::api<graphene::app::block_api>;\ntemplate class fc::api<graphene::app::network_broadcast_api>;\ntemplate class fc::api<graphene::app::network_node_api>;\ntemplate class fc::api<graphene::app::history_api>;\ntemplate class fc::api<graphene::app::crypto_api>;\ntemplate class fc::api<graphene::app::asset_api>;\ntemplate class fc::api<graphene::app::orders_api>;\ntemplate class fc::api<graphene::app::custom_operations_api>;\ntemplate class fc::api<graphene::debug_witness::debug_api>;\ntemplate class fc::api<graphene::app::dummy_api>;\ntemplate class fc::api<graphene::app::login_api>;\n\n\nnamespace graphene { namespace app {\n\n    login_api::login_api(application& a)\n    :_app(a)\n    {\n       // Nothing to do\n    }\n\n    variant login_api::login(const optional<string>& o_user, const optional<string>& o_password)\n    {\n       if( !o_user && !o_password )\n          return uint32_t(1); // Note: hard code it here for backward compatibility\n\n       FC_ASSERT( o_user.valid() && o_password.valid(), \"Must provide both user and password\" );\n\n       optional< api_access_info > acc = _app.get_api_access_info( *o_user );\n       if( !acc )\n          return logout();\n       if( acc->password_hash_b64 != \"*\" )\n       {\n          std::string acc_password_hash = fc::base64_decode( acc->password_hash_b64 );\n          if( fc::sha256::data_size() != acc_password_hash.length() )\n             return logout();\n\n          std::string password_salt = fc::base64_decode( acc->password_salt_b64 );\n          fc::sha256 hash_obj = fc::sha256::hash( *o_password + password_salt );\n          if( memcmp( hash_obj.data(), acc_password_hash.data(), fc::sha256::data_size() ) != 0 )\n             return logout();\n       }\n\n       // Ideally, we should clean up the API sets that the previous user registered but the new user\n       //   no longer has access to.\n       // However, the shared pointers to these objects are already saved elsewhere (in FC),\n       //   so we are unable to clean up, so it does not make sense to reset the optional fields here.\n\n       _allowed_apis = acc->allowed_apis;\n       return true;\n    }\n\n    bool login_api::logout()\n    {\n       // Ideally, we should clean up the API sets that the previous user registered.\n       // However, the shared pointers to these objects are already saved elsewhere (in FC),\n       //   so we are unable to clean up, so it does not make sense to reset the optional fields here.\n       _allowed_apis.clear();\n       return false;\n    }\n\n    string login_api::get_info() const\n    {\n       return _app.get_node_info();\n    }\n\n    application_options login_api::get_config() const\n    {\n       bool is_allowed = !_allowed_apis.empty();\n       FC_ASSERT( is_allowed, \"Access denied, please login\" );\n       return _app.get_options();\n    }\n\n    flat_set<string> login_api::get_available_api_sets() const\n    {\n       return _allowed_apis;\n    }\n\n    bool login_api::is_database_api_allowed() const\n    {\n       bool is_allowed = ( _allowed_apis.find(\"database_api\") != _allowed_apis.end() );\n       return is_allowed;\n    }\n\n    // block_api\n    block_api::block_api(const graphene::chain::database& db) : _db(db) { /* Nothing to do */ }\n\n    vector<optional<signed_block>> block_api::get_blocks(uint32_t block_num_from, uint32_t block_num_to)const\n    {\n       FC_ASSERT( block_num_to >= block_num_from );\n       vector<optional<signed_block>> res;\n       for(uint32_t block_num=block_num_from; block_num<=block_num_to; block_num++) {\n          res.push_back(_db.fetch_block_by_number(block_num));\n       }\n       return res;\n    }\n\n    network_broadcast_api::network_broadcast_api(application& a):_app(a)\n    {\n       _applied_block_connection = _app.chain_database()->applied_block.connect(\n                                         [this](const signed_block& b){ on_applied_block(b); });\n    }\n\n    void network_broadcast_api::on_applied_block( const signed_block& b )\n    {\n       if( _callbacks.size() )\n       {\n          /// we need to ensure the database_api is not deleted for the life of the async operation\n          auto capture_this = shared_from_this();\n          for( uint32_t trx_num = 0; trx_num < b.transactions.size(); ++trx_num )\n          {\n             const auto& trx = b.transactions[trx_num];\n             auto id = trx.id();\n             auto itr = _callbacks.find(id);\n             if( itr != _callbacks.end() )\n             {\n                auto block_num = b.block_num();\n                auto& callback = _callbacks.find(id)->second;\n                auto v = fc::variant( transaction_confirmation{ id, block_num, trx_num, trx },\n                                      GRAPHENE_MAX_NESTED_OBJECTS );\n                fc::async( [capture_this,v,callback]() {\n                   callback(v);\n                } );\n             }\n          }\n       }\n    }\n\n    void network_broadcast_api::broadcast_transaction(const precomputable_transaction& trx)\n    {\n       FC_ASSERT( _app.p2p_node() != nullptr, \"Not connected to P2P network, can't broadcast!\" );\n       _app.chain_database()->precompute_parallel( trx ).wait();\n       _app.chain_database()->push_transaction(trx);\n       _app.p2p_node()->broadcast_transaction(trx);\n    }\n\n    fc::variant network_broadcast_api::broadcast_transaction_synchronous(const precomputable_transaction& trx)\n    {\n       fc::promise<fc::variant>::ptr prom = fc::promise<fc::variant>::create();\n       broadcast_transaction_with_callback( [prom]( const fc::variant& v ){\n        prom->set_value(v);\n       }, trx );\n\n       return fc::future<fc::variant>(prom).wait();\n    }\n\n    void network_broadcast_api::broadcast_block( const signed_block& b )\n    {\n       FC_ASSERT( _app.p2p_node() != nullptr, \"Not connected to P2P network, can't broadcast!\" );\n       _app.chain_database()->precompute_parallel( b ).wait();\n       _app.chain_database()->push_block(b);\n       _app.p2p_node()->broadcast( net::block_message( b ));\n    }\n\n    void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const precomputable_transaction& trx)\n    {\n       FC_ASSERT( _app.p2p_node() != nullptr, \"Not connected to P2P network, can't broadcast!\" );\n       _app.chain_database()->precompute_parallel( trx ).wait();\n       _callbacks[trx.id()] = cb;\n       _app.chain_database()->push_transaction(trx);\n       _app.p2p_node()->broadcast_transaction(trx);\n    }\n\n    network_node_api::network_node_api( application& a ) : _app( a )\n    {\n       // Nothing to do\n    }\n\n    fc::variant_object network_node_api::get_info() const\n    {\n       FC_ASSERT( _app.p2p_node() != nullptr, \"No P2P network!\" );\n       fc::mutable_variant_object result = _app.p2p_node()->network_get_info();\n       result[\"connection_count\"] = _app.p2p_node()->get_connection_count();\n       return result;\n    }\n\n    void network_node_api::add_node(const fc::ip::endpoint& ep)\n    {\n       if( _app.p2p_node() != nullptr )\n          _app.p2p_node()->add_node(ep);\n    }\n\n    std::vector<net::peer_status> network_node_api::get_connected_peers() const\n    {\n       if( _app.p2p_node() != nullptr )\n          return _app.p2p_node()->get_connected_peers();\n       return {};\n    }\n\n    std::vector<net::potential_peer_record> network_node_api::get_potential_peers() const\n    {\n       if( _app.p2p_node() != nullptr )\n          return _app.p2p_node()->get_potential_peers();\n       return {};\n    }\n\n    fc::variant_object network_node_api::get_advanced_node_parameters() const\n    {\n       FC_ASSERT( _app.p2p_node() != nullptr, \"No P2P network!\" );\n       return _app.p2p_node()->get_advanced_node_parameters();\n    }\n\n    void network_node_api::set_advanced_node_parameters(const fc::variant_object& params)\n    {\n       FC_ASSERT( _app.p2p_node() != nullptr, \"No P2P network!\" );\n       return _app.p2p_node()->set_advanced_node_parameters(params);\n    }\n\n    fc::api<network_broadcast_api> login_api::network_broadcast()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"network_broadcast_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_network_broadcast_api )\n       {\n          _network_broadcast_api = std::make_shared< network_broadcast_api >( std::ref( _app ) );\n       }\n       return *_network_broadcast_api;\n    }\n\n    fc::api<block_api> login_api::block()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"block_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_block_api )\n       {\n          _block_api = std::make_shared< block_api >( std::ref( *_app.chain_database() ) );\n       }\n       return *_block_api;\n    }\n\n    fc::api<network_node_api> login_api::network_node()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"network_node_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_network_node_api )\n       {\n          _network_node_api = std::make_shared< network_node_api >( std::ref(_app) );\n       }\n       return *_network_node_api;\n    }\n\n    fc::api<database_api> login_api::database()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"database_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_database_api )\n       {\n          _database_api = std::make_shared< database_api >( std::ref( *_app.chain_database() ),\n                                                            &( _app.get_options() ) );\n       }\n       return *_database_api;\n    }\n\n    fc::api<history_api> login_api::history()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"history_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_history_api )\n       {\n          _history_api = std::make_shared< history_api >( _app );\n       }\n       return *_history_api;\n    }\n\n    fc::api<crypto_api> login_api::crypto()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"crypto_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_crypto_api )\n       {\n          _crypto_api = std::make_shared< crypto_api >();\n       }\n       return *_crypto_api;\n    }\n\n    fc::api<asset_api> login_api::asset()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"asset_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_asset_api )\n       {\n          _asset_api = std::make_shared< asset_api >( _app );\n       }\n       return *_asset_api;\n    }\n\n    fc::api<orders_api> login_api::orders()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"orders_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       if( !_orders_api )\n       {\n          _orders_api = std::make_shared< orders_api >( std::ref( _app ) );\n       }\n       return *_orders_api;\n    }\n\n    fc::api<graphene::debug_witness::debug_api> login_api::debug()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"debug_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       // can only use this API set if the plugin was loaded\n       bool plugin_enabled = !!_app.get_plugin( \"debug_witness\" );\n       FC_ASSERT( plugin_enabled, \"The debug_witness plugin is not enabled\" );\n       if( ! _debug_api )\n       {\n          _debug_api = std::make_shared< graphene::debug_witness::debug_api >( std::ref(_app) );\n       }\n       return *_debug_api;\n    }\n\n    fc::api<custom_operations_api> login_api::custom_operations()\n    {\n       bool is_allowed = ( _allowed_apis.find(\"custom_operations_api\") != _allowed_apis.end() );\n       FC_ASSERT( is_allowed, \"Access denied\" );\n       // can only use this API set if the plugin was loaded\n       bool plugin_enabled = !!_app.get_plugin( \"custom_operations\" );\n       FC_ASSERT( plugin_enabled, \"The custom_operations plugin is not enabled\" );\n       if( !_custom_operations_api )\n       {\n          _custom_operations_api = std::make_shared< custom_operations_api >( std::ref( _app ) );\n       }\n       return *_custom_operations_api;\n    }\n\n    fc::api<dummy_api> login_api::dummy()\n    {\n       if( !_dummy_api )\n       {\n          _dummy_api = std::make_shared< dummy_api >();\n       }\n       return *_dummy_api;\n    }\n\n    history_api::history_api(application& app)\n    : _app(app)\n    { // Nothing else to do\n    }\n\n    vector<order_history_object> history_api::get_fill_order_history( const std::string& asset_a,\n                                                                      const std::string& asset_b,\n                                                                      uint32_t limit )const\n    {\n       auto market_hist_plugin = _app.get_plugin<market_history_plugin>( \"market_history\" );\n       FC_ASSERT( market_hist_plugin, \"Market history plugin is not enabled\" );\n       FC_ASSERT(_app.chain_database());\n       const auto& db = *_app.chain_database();\n       database_api_helper db_api_helper( _app );\n       asset_id_type a = db_api_helper.get_asset_from_string( asset_a )->get_id();\n       asset_id_type b = db_api_helper.get_asset_from_string( asset_b )->get_id();\n       if( a > b ) std::swap(a,b);\n       const auto& history_idx = db.get_index_type<graphene::market_history::history_index>().indices().get<by_key>();\n       history_key hkey;\n       hkey.base = a;\n       hkey.quote = b;\n       hkey.sequence = std::numeric_limits<int64_t>::min();\n\n       auto itr = history_idx.lower_bound( hkey );\n       vector<order_history_object> result;\n       while( itr != history_idx.end() && result.size() < limit )\n       {\n          if( itr->key.base != a || itr->key.quote != b ) break;\n          result.push_back( *itr );\n          ++itr;\n       }\n\n       return result;\n    }\n\n    vector<operation_history_object> history_api::get_account_history( const std::string& account_id_or_name,\n                                                                       operation_history_id_type stop,\n                                                                       uint32_t limit,\n                                                                       operation_history_id_type start ) const\n    {\n       FC_ASSERT( _app.chain_database(), \"database unavailable\" );\n       const auto& db = *_app.chain_database();\n\n       const auto configured_limit = _app.get_options().api_limit_get_account_history;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       vector<operation_history_object> result;\n       if( start == operation_history_id_type() )\n          // Note: this means we can hardly use ID 0 as start to query for exactly the object with ID 0\n          start = operation_history_id_type::max();\n       if( start < stop )\n          return result;\n\n       account_id_type account;\n       try {\n          database_api_helper db_api_helper( _app );\n          account = db_api_helper.get_account_from_string(account_id_or_name)->get_id();\n       } catch(...) { return result; }\n\n       if(_app.is_plugin_enabled(\"elasticsearch\")) {\n          auto es = _app.get_plugin<elasticsearch::elasticsearch_plugin>(\"elasticsearch\");\n          if(es.get()->get_running_mode() != elasticsearch::mode::only_save) {\n             if(!_app.elasticsearch_thread)\n                _app.elasticsearch_thread= std::make_shared<fc::thread>(\"elasticsearch\");\n\n             return _app.elasticsearch_thread->async([&es, account, stop, limit, start]() {\n                return es->get_account_history(account, stop, limit, start);\n             }, \"thread invoke for method \" BOOST_PP_STRINGIZE(method_name)).wait();\n          }\n       }\n\n       const auto& by_op_idx = db.get_index_type<account_history_index>().indices().get<by_op>();\n       auto itr = by_op_idx.lower_bound( boost::make_tuple( account, start ) );\n       auto itr_end = by_op_idx.lower_bound( boost::make_tuple( account, stop ) );\n\n       while( itr != itr_end && result.size() < limit )\n       {\n          result.emplace_back( itr->operation_id(db) );\n          ++itr;\n       }\n       // Deal with a special case : include the object with ID 0 when it fits\n       if( 0 == stop.instance.value && result.size() < limit && itr != by_op_idx.end() )\n       {\n          const auto& obj = *itr;\n          if( obj.account == account )\n             result.emplace_back( obj.operation_id(db) );\n       }\n\n       return result;\n    }\n\n    vector<operation_history_object> history_api::get_account_history_by_time(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& olimit,\n            const optional<fc::time_point_sec>& ostart ) const\n    {\n       FC_ASSERT( _app.chain_database(), \"database unavailable\" );\n       const auto& db = *_app.chain_database();\n\n       const auto configured_limit = _app.get_options().api_limit_get_account_history;\n       uint32_t limit = olimit.valid() ? *olimit : configured_limit;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       vector<operation_history_object> result;\n       account_id_type account;\n       try {\n          database_api_helper db_api_helper( _app );\n          account = db_api_helper.get_account_from_string(account_name_or_id)->get_id();\n       } catch( const fc::exception& ) { return result; }\n\n       fc::time_point_sec start = ostart.valid() ? *ostart : fc::time_point_sec::maximum();\n\n       const auto& op_hist_idx = db.get_index_type<operation_history_index>().indices().get<by_time>();\n       auto op_hist_itr = op_hist_idx.lower_bound( start );\n       if( op_hist_itr == op_hist_idx.end() )\n          return result;\n\n       const auto& acc_hist_idx = db.get_index_type<account_history_index>().indices().get<by_op>();\n       auto itr = acc_hist_idx.lower_bound( boost::make_tuple( account, op_hist_itr->get_id() ) );\n       auto itr_end = acc_hist_idx.upper_bound( account );\n\n       while( itr != itr_end && result.size() < limit )\n       {\n          result.emplace_back( itr->operation_id(db) );\n          ++itr;\n       }\n\n       return result;\n    }\n\n    vector<operation_history_object> history_api::get_account_history_operations(\n          const std::string& account_id_or_name,\n          int64_t operation_type,\n          operation_history_id_type start,\n          operation_history_id_type stop,\n          uint32_t limit ) const\n    {\n       FC_ASSERT( _app.chain_database(), \"database unavailable\" );\n       const auto& db = *_app.chain_database();\n\n       const auto configured_limit = _app.get_options().api_limit_get_account_history_operations;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       vector<operation_history_object> result;\n       account_id_type account;\n       try {\n          database_api_helper db_api_helper( _app );\n          account = db_api_helper.get_account_from_string(account_id_or_name)->get_id();\n       } catch(...) { return result; }\n       const auto& stats = account(db).statistics(db);\n       if( stats.most_recent_op == account_history_id_type() ) return result;\n       const account_history_object* node = &stats.most_recent_op(db);\n       if( start == operation_history_id_type() )\n          start = node->operation_id;\n\n       while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit)\n       {\n          if( node->operation_id.instance.value <= start.instance.value ) {\n\n             if(node->operation_id(db).op.which() == operation_type)\n               result.push_back( node->operation_id(db) );\n          }\n          if( node->next == account_history_id_type() )\n             node = nullptr;\n          else node = &node->next(db);\n       }\n       if( stop.instance.value == 0 && result.size() < limit ) {\n          const auto* head = db.find(account_history_id_type());\n          if (head != nullptr && head->account == account && head->operation_id(db).op.which() == operation_type)\n            result.push_back(head->operation_id(db));\n       }\n       return result;\n    }\n\n\n    vector<operation_history_object> history_api::get_relative_account_history( const std::string& account_id_or_name,\n                                                                                uint64_t stop,\n                                                                                uint32_t limit,\n                                                                                uint64_t start ) const\n    {\n       FC_ASSERT( _app.chain_database(), \"database unavailable\" );\n       const auto& db = *_app.chain_database();\n\n       const auto configured_limit = _app.get_options().api_limit_get_relative_account_history;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       vector<operation_history_object> result;\n       account_id_type account;\n       try {\n          database_api_helper db_api_helper( _app );\n          account = db_api_helper.get_account_from_string(account_id_or_name)->get_id();\n       } catch(...) { return result; }\n       const auto& stats = account(db).statistics(db);\n       if( start == 0 )\n          start = stats.total_ops;\n       else\n          start = std::min( stats.total_ops, start );\n\n       if( start >= stop && start > stats.removed_ops && limit > 0 )\n       {\n          const auto& hist_idx = db.get_index_type<account_history_index>();\n          const auto& by_seq_idx = hist_idx.indices().get<by_seq>();\n\n          auto itr = by_seq_idx.upper_bound( boost::make_tuple( account, start ) );\n          auto itr_stop = by_seq_idx.lower_bound( boost::make_tuple( account, stop ) );\n\n          do\n          {\n             --itr;\n             result.push_back( itr->operation_id(db) );\n          }\n          while ( itr != itr_stop && result.size() < limit );\n       }\n       return result;\n    }\n\n    vector<operation_history_object> history_api::get_block_operation_history(\n          uint32_t block_num,\n          const optional<uint16_t>& trx_in_block ) const\n    {\n       FC_ASSERT( _app.chain_database(), \"database unavailable\" );\n       const auto& db = *_app.chain_database();\n       const auto& idx = db.get_index_type<operation_history_index>().indices().get<by_block>();\n       auto range = trx_in_block.valid() ? idx.equal_range( boost::make_tuple( block_num, *trx_in_block  ) )\n                                         : idx.equal_range( block_num );\n       vector<operation_history_object> result;\n       std::copy( range.first, range.second, std::back_inserter( result ) );\n       return result;\n    }\n\n    vector<operation_history_object> history_api::get_block_operations_by_time(\n          const optional<fc::time_point_sec>& start ) const\n    {\n       FC_ASSERT( _app.chain_database(), \"database unavailable\" );\n       const auto& db = *_app.chain_database();\n       const auto& idx = db.get_index_type<operation_history_index>().indices().get<by_time>();\n       auto itr = start.valid() ? idx.lower_bound( *start ) : idx.begin();\n\n       vector<operation_history_object> result;\n       if( itr == idx.end() )\n          return result;\n\n       auto itr_end = idx.upper_bound( itr->block_time );\n\n       std::copy( itr, itr_end, std::back_inserter( result ) );\n\n       return result;\n    }\n\n    flat_set<uint32_t> history_api::get_market_history_buckets()const\n    {\n       auto market_hist_plugin = _app.get_plugin<market_history_plugin>( \"market_history\" );\n       FC_ASSERT( market_hist_plugin, \"Market history plugin is not enabled\" );\n       return market_hist_plugin->tracked_buckets();\n    }\n\n    history_api::history_operation_detail history_api::get_account_history_by_operations(\n          const std::string& account_id_or_name,\n          const flat_set<uint16_t>& operation_types,\n          uint32_t start, uint32_t limit )const\n    {\n       const auto configured_limit = _app.get_options().api_limit_get_account_history_by_operations;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       history_operation_detail result;\n       vector<operation_history_object> objs = get_relative_account_history( account_id_or_name, start, limit,\n                                                                             limit + start - 1 );\n       result.total_count = objs.size();\n\n       if( operation_types.empty() )\n          result.operation_history_objs = std::move(objs);\n       else\n       {\n          for( const operation_history_object &o : objs )\n          {\n             if( operation_types.find(o.op.which()) != operation_types.end() ) {\n                result.operation_history_objs.push_back(o);\n             }\n          }\n       }\n\n       return result;\n    }\n\n    vector<bucket_object> history_api::get_market_history( const std::string& asset_a, const std::string& asset_b,\n                                                           uint32_t bucket_seconds,\n                                                           const fc::time_point_sec& start,\n                                                           const fc::time_point_sec& end )const\n    { try {\n\n       auto market_hist_plugin = _app.get_plugin<market_history_plugin>( \"market_history\" );\n       FC_ASSERT( market_hist_plugin, \"Market history plugin is not enabled\" );\n       FC_ASSERT(_app.chain_database());\n\n       const auto& db = *_app.chain_database();\n       database_api_helper db_api_helper( _app );\n       asset_id_type a = db_api_helper.get_asset_from_string( asset_a )->get_id();\n       asset_id_type b = db_api_helper.get_asset_from_string( asset_b )->get_id();\n       vector<bucket_object> result;\n       const auto configured_limit = _app.get_options().api_limit_get_market_history;\n       result.reserve( configured_limit );\n\n       if( a > b ) std::swap(a,b);\n\n       const auto& bidx = db.get_index_type<bucket_index>();\n       const auto& by_key_idx = bidx.indices().get<by_key>();\n\n       auto itr = by_key_idx.lower_bound( bucket_key( a, b, bucket_seconds, start ) );\n       while( itr != by_key_idx.end() && itr->key.open <= end && result.size() < configured_limit )\n       {\n          if( !(itr->key.base == a && itr->key.quote == b && itr->key.seconds == bucket_seconds) )\n          {\n            return result;\n          }\n          result.push_back(*itr);\n          ++itr;\n       }\n       return result;\n    } FC_CAPTURE_AND_RETHROW( (asset_a)(asset_b)(bucket_seconds)(start)(end) ) }\n\n    static uint32_t validate_get_lp_history_params( const application& _app, const optional<uint32_t>& olimit )\n    {\n       FC_ASSERT( _app.get_options().has_market_history_plugin, \"Market history plugin is not enabled.\" );\n\n       const auto configured_limit = _app.get_options().api_limit_get_liquidity_pool_history;\n       uint32_t limit = olimit.valid() ? *olimit : configured_limit;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       FC_ASSERT( _app.chain_database(), \"Internal error: the chain database is not availalbe\" );\n\n       return limit;\n    }\n\n    vector<liquidity_pool_history_object> history_api::get_liquidity_pool_history(\n               liquidity_pool_id_type pool_id,\n               const optional<fc::time_point_sec>& start,\n               const optional<fc::time_point_sec>& stop,\n               const optional<uint32_t>& olimit,\n               const optional<int64_t>& operation_type )const\n    { try {\n       uint32_t limit = validate_get_lp_history_params( _app, olimit );\n\n       vector<liquidity_pool_history_object> result;\n\n       if( 0 == limit || ( start.valid() && stop.valid() && *start <= *stop ) ) // empty result\n          return result;\n\n       const auto& db = *_app.chain_database();\n\n       const auto& hist_idx = db.get_index_type<liquidity_pool_history_index>();\n\n       if( operation_type.valid() ) // one operation type\n       {\n          const auto& idx = hist_idx.indices().get<by_pool_op_type_time>();\n          auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *operation_type, *start ) )\n                                   : idx.lower_bound( boost::make_tuple( pool_id, *operation_type ) );\n          auto itr_stop = stop.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *operation_type, *stop ) )\n                                       : idx.upper_bound( boost::make_tuple( pool_id, *operation_type ) );\n          while( itr != itr_stop && result.size() < limit )\n          {\n             result.push_back( *itr );\n             ++itr;\n          }\n       }\n       else // all operation types\n       {\n          const auto& idx = hist_idx.indices().get<by_pool_time>();\n          auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *start ) )\n                                   : idx.lower_bound( pool_id );\n          auto itr_stop = stop.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *stop ) )\n                                       : idx.upper_bound( pool_id );\n          while( itr != itr_stop && result.size() < limit )\n          {\n             result.push_back( *itr );\n             ++itr;\n          }\n       }\n\n       return result;\n\n    } FC_CAPTURE_AND_RETHROW( (pool_id)(start)(stop)(olimit)(operation_type) ) }\n\n    vector<liquidity_pool_history_object> history_api::get_liquidity_pool_history_by_sequence(\n               liquidity_pool_id_type pool_id,\n               const optional<uint64_t>& start,\n               const optional<fc::time_point_sec>& stop,\n               const optional<uint32_t>& olimit,\n               const optional<int64_t>& operation_type )const\n    { try {\n       uint32_t limit = validate_get_lp_history_params( _app, olimit );\n\n       vector<liquidity_pool_history_object> result;\n\n       if( 0 == limit ) // empty result\n          return result;\n\n       const auto& db = *_app.chain_database();\n\n       const auto& hist_idx = db.get_index_type<liquidity_pool_history_index>();\n\n       if( operation_type.valid() ) // one operation type\n       {\n          const auto& idx = hist_idx.indices().get<by_pool_op_type_seq>();\n          const auto& idx_t = hist_idx.indices().get<by_pool_op_type_time>();\n          auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *operation_type, *start ) )\n                                   : idx.lower_bound( boost::make_tuple( pool_id, *operation_type ) );\n          if( itr == idx.end() || itr->pool != pool_id || itr->op_type != *operation_type ) // empty result\n             return result;\n          if( stop.valid() && itr->time <= *stop ) // empty result\n             return result;\n          auto itr_temp = stop.valid() ? idx_t.lower_bound( boost::make_tuple( pool_id, *operation_type, *stop ) )\n                                       : idx_t.upper_bound( boost::make_tuple( pool_id, *operation_type ) );\n          auto itr_stop = ( itr_temp == idx_t.end() ? idx.end() : idx.iterator_to( *itr_temp ) );\n          while( itr != itr_stop && result.size() < limit )\n          {\n             result.push_back( *itr );\n             ++itr;\n          }\n       }\n       else // all operation types\n       {\n          const auto& idx = hist_idx.indices().get<by_pool_seq>();\n          const auto& idx_t = hist_idx.indices().get<by_pool_time>();\n          auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *start ) )\n                                   : idx.lower_bound( pool_id );\n          if( itr == idx.end() || itr->pool != pool_id ) // empty result\n             return result;\n          if( stop.valid() && itr->time <= *stop ) // empty result\n             return result;\n          auto itr_temp = stop.valid() ? idx_t.lower_bound( boost::make_tuple( pool_id, *stop ) )\n                                       : idx_t.upper_bound( pool_id );\n          auto itr_stop = ( itr_temp == idx_t.end() ? idx.end() : idx.iterator_to( *itr_temp ) );\n          while( itr != itr_stop && result.size() < limit )\n          {\n             result.push_back( *itr );\n             ++itr;\n          }\n       }\n\n       return result;\n\n    } FC_CAPTURE_AND_RETHROW( (pool_id)(start)(stop)(olimit)(operation_type) ) }\n\n\n    fc::ecc::commitment_type crypto_api::blind( const blind_factor_type& blind, uint64_t value ) const\n    {\n       return fc::ecc::blind( blind, value );\n    }\n\n    fc::ecc::blind_factor_type crypto_api::blind_sum( const std::vector<blind_factor_type>& blinds_in,\n                                                      uint32_t non_neg ) const\n    {\n       return fc::ecc::blind_sum( blinds_in, non_neg );\n    }\n\n    bool crypto_api::verify_sum( const std::vector<commitment_type>& commits_in,\n                                 const std::vector<commitment_type>& neg_commits_in,\n                                 int64_t excess ) const\n    {\n       return fc::ecc::verify_sum( commits_in, neg_commits_in, excess );\n    }\n\n    crypto_api::verify_range_result crypto_api::verify_range( const commitment_type& commit,\n                                                              const std::vector<char>& proof ) const\n    {\n       verify_range_result result;\n       result.success = fc::ecc::verify_range( result.min_val, result.max_val, commit, proof );\n       return result;\n    }\n\n    std::vector<char> crypto_api::range_proof_sign( uint64_t min_value,\n                                                    const commitment_type& commit,\n                                                    const blind_factor_type& commit_blind,\n                                                    const blind_factor_type& nonce,\n                                                    int8_t base10_exp,\n                                                    uint8_t min_bits,\n                                                    uint64_t actual_value ) const\n    {\n       return fc::ecc::range_proof_sign( min_value, commit, commit_blind, nonce, base10_exp, min_bits, actual_value );\n    }\n\n    crypto_api::verify_range_proof_rewind_result crypto_api::verify_range_proof_rewind(\n          const blind_factor_type& nonce,\n          const commitment_type& commit,\n          const std::vector<char>& proof ) const\n    {\n       verify_range_proof_rewind_result result;\n       result.success = fc::ecc::verify_range_proof_rewind( result.blind_out,\n                                                            result.value_out,\n                                                            result.message_out,\n                                                            nonce,\n                                                            result.min_val,\n                                                            result.max_val,\n                                                            const_cast< commitment_type& >( commit ),\n                                                            proof );\n       return result;\n    }\n\n    fc::ecc::range_proof_info crypto_api::range_get_info( const std::vector<char>& proof ) const\n    {\n       return fc::ecc::range_get_info( proof );\n    }\n\n    // asset_api\n    asset_api::asset_api(graphene::app::application& app)\n    : _app(app),\n      _db( *app.chain_database() )\n    { // Nothing else to do\n    }\n\n    vector<asset_api::account_asset_balance> asset_api::get_asset_holders( const std::string& asset_symbol_or_id,\n                                                                           uint32_t start, uint32_t limit ) const\n    {\n       const auto configured_limit = _app.get_options().api_limit_get_asset_holders;\n       FC_ASSERT( limit <= configured_limit,\n                  \"limit can not be greater than ${configured_limit}\",\n                  (\"configured_limit\", configured_limit) );\n\n       database_api_helper db_api_helper( _app );\n       asset_id_type asset_id = db_api_helper.get_asset_from_string( asset_symbol_or_id )->get_id();\n       const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();\n       auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) );\n\n       vector<account_asset_balance> result;\n\n       uint32_t index = 0;\n       for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) )\n       {\n          if( result.size() >= limit )\n             break;\n\n          if( bal.balance.value == 0 )\n             continue;\n\n          if( index++ < start )\n             continue;\n\n          const auto account = _db.find(bal.owner);\n\n          account_asset_balance aab;\n          aab.name       = account->name;\n          aab.account_id = account->id;\n          aab.amount     = bal.balance.value;\n\n          result.push_back(aab);\n       }\n\n       return result;\n    }\n    // get number of asset holders.\n    int64_t asset_api::get_asset_holders_count( const std::string& asset_symbol_or_id ) const {\n       const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();\n       database_api_helper db_api_helper( _app );\n       asset_id_type asset_id = db_api_helper.get_asset_from_string( asset_symbol_or_id )->get_id();\n       auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) );\n\n       int64_t count = boost::distance(range) - 1;\n\n       return count;\n    }\n    // function to get vector of system assets with holders count.\n    vector<asset_api::asset_holders> asset_api::get_all_asset_holders() const {\n       vector<asset_holders> result;\n       vector<asset_id_type> total_assets;\n       for( const asset_object& asset_obj : _db.get_index_type<asset_index>().indices() )\n       {\n          const auto& dasset_obj = asset_obj.dynamic_asset_data_id(_db);\n\n          asset_id_type asset_id;\n          asset_id = dasset_obj.id;\n\n          const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();\n          auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) );\n\n          int64_t count = boost::distance(range) - 1;\n\n          asset_holders ah;\n          ah.asset_id       = asset_id;\n          ah.count     = count;\n\n          result.push_back(ah);\n       }\n\n       return result;\n    }\n\n   // orders_api\n   orders_api::orders_api(application& app)\n   : _app(app)\n   { // Nothing else to do\n   }\n\n   flat_set<uint16_t> orders_api::get_tracked_groups()const\n   {\n      auto plugin = _app.get_plugin<grouped_orders_plugin>( \"grouped_orders\" );\n      FC_ASSERT( plugin );\n      return plugin->tracked_groups();\n   }\n\n   vector< orders_api::limit_order_group > orders_api::get_grouped_limit_orders( const std::string& base_asset,\n                                                                                 const std::string& quote_asset,\n                                                                                 uint16_t group,\n                                                                                 const optional<price>& start,\n                                                                                 uint32_t limit )const\n   {\n      const auto configured_limit = _app.get_options().api_limit_get_grouped_limit_orders;\n      FC_ASSERT( limit <= configured_limit,\n                 \"limit can not be greater than ${configured_limit}\",\n                 (\"configured_limit\", configured_limit) );\n\n      auto plugin = _app.get_plugin<graphene::grouped_orders::grouped_orders_plugin>( \"grouped_orders\" );\n      FC_ASSERT( plugin );\n      const auto& limit_groups = plugin->limit_order_groups();\n      vector< limit_order_group > result;\n\n      database_api_helper db_api_helper( _app );\n      asset_id_type base_asset_id = db_api_helper.get_asset_from_string( base_asset )->get_id();\n      asset_id_type quote_asset_id = db_api_helper.get_asset_from_string( quote_asset )->get_id();\n\n      price max_price = price::max( base_asset_id, quote_asset_id );\n      price min_price = price::min( base_asset_id, quote_asset_id );\n      if( start.valid() && !start->is_null() )\n         max_price = std::max( std::min( max_price, *start ), min_price );\n\n      auto itr = limit_groups.lower_bound( limit_order_group_key( group, max_price ) );\n      // use an end iterator to try to avoid expensive price comparison\n      auto end = limit_groups.upper_bound( limit_order_group_key( group, min_price ) );\n      while( itr != end && result.size() < limit )\n      {\n         result.emplace_back( *itr );\n         ++itr;\n      }\n      return result;\n   }\n\n   // custom operations api\n   custom_operations_api::custom_operations_api(application& app)\n   : _app(app)\n   { // Nothing else to do\n   }\n\n   vector<account_storage_object> custom_operations_api::get_storage_info(\n         const optional<std::string>& o_account_name_or_id,\n         const optional<std::string>& catalog,\n         const optional<std::string>& key,\n         const optional<uint32_t>& limit,\n         const optional<account_storage_id_type>& start_id )const\n   {\n      auto plugin = _app.get_plugin<graphene::custom_operations::custom_operations_plugin>(\"custom_operations\");\n      FC_ASSERT( plugin, \"The custom_operations plugin is not enabled\" );\n\n      database_api_helper db_api_helper( _app );\n      const auto& storage_index = _app.chain_database()->get_index_type<account_storage_index>().indices();\n\n      if( o_account_name_or_id.valid() )\n      {\n         const string& account_name_or_id = *o_account_name_or_id;\n         const account_id_type account_id = db_api_helper.get_account_from_string(account_name_or_id)->get_id();\n         if( catalog.valid() )\n         {\n            if( key.valid() )\n               return db_api_helper.get_objects_by_x< account_storage_object,\n                                                      account_storage_id_type\n                                                     >( &application_options::api_limit_get_storage_info,\n                                                        storage_index.get<by_account_catalog_key>(),\n                                                        limit, start_id, account_id, *catalog, *key );\n            else\n               return db_api_helper.get_objects_by_x< account_storage_object,\n                                                      account_storage_id_type\n                                                     >( &application_options::api_limit_get_storage_info,\n                                                        storage_index.get<by_account_catalog>(),\n                                                        limit, start_id, account_id, *catalog );\n         }\n         else\n         {\n            FC_ASSERT( !key.valid(), \"Can not specify key if catalog is not specified\" );\n            return db_api_helper.get_objects_by_x< account_storage_object,\n                                                   account_storage_id_type\n                                                  >( &application_options::api_limit_get_storage_info,\n                                                     storage_index.get<custom_operations::by_account>(),\n                                                     limit, start_id, account_id );\n         }\n      }\n      else if( catalog.valid() )\n      {\n         if( key.valid() )\n            return db_api_helper.get_objects_by_x< account_storage_object,\n                                                   account_storage_id_type\n                                                  >( &application_options::api_limit_get_storage_info,\n                                                     storage_index.get<by_catalog_key>(),\n                                                     limit, start_id, *catalog, *key );\n         else\n            return db_api_helper.get_objects_by_x< account_storage_object,\n                                                   account_storage_id_type\n                                                  >( &application_options::api_limit_get_storage_info,\n                                                     storage_index.get<by_catalog>(),\n                                                     limit, start_id, *catalog );\n      }\n      else\n      {\n         FC_ASSERT( !key.valid(), \"Can not specify key if catalog is not specified\" );\n         return db_api_helper.get_objects_by_x< account_storage_object,\n                                                account_storage_id_type\n                                               >( &application_options::api_limit_get_storage_info,\n                                                  storage_index.get<by_id>(),\n                                                  limit, start_id );\n      }\n\n   }\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/api_objects.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/app/api_objects.hpp>\n#include <graphene/app/util.hpp>\n\nnamespace graphene { namespace app {\n\norder::order( const string& _price,\n              const string& _quote,\n              const string& _base,\n              const limit_order_id_type& _id,\n              const account_id_type& _oid,\n              const string& _oname,\n              const time_point_sec& _exp )\n: price( _price ),\n  quote( _quote ),\n  base( _base ),\n  id( _id ),\n  owner_id( _oid ),\n  owner_name( _oname ),\n  expiration( _exp )\n{\n   // Nothing to do\n}\n\norder_book::order_book( const string& _base, const string& _quote )\n: base( _base ), quote( _quote )\n{\n   // Do nothing else\n}\n\nmarket_ticker::market_ticker(const market_ticker_object& mto,\n                             const fc::time_point_sec& now,\n                             const asset_object& asset_base,\n                             const asset_object& asset_quote,\n                             const order_book& orders)\n{\n   time = now;\n   mto_id = mto.id;\n   base = asset_base.symbol;\n   quote = asset_quote.symbol;\n   percent_change = \"0\";\n   lowest_ask = \"0\";\n   lowest_ask_base_size = \"0\";\n   lowest_ask_quote_size = \"0\";\n   highest_bid = \"0\";\n   highest_bid_base_size = \"0\";\n   highest_bid_quote_size = \"0\";\n   fc::uint128_t bv;\n   fc::uint128_t qv;\n   price latest_price = asset( mto.latest_base, mto.base ) / asset( mto.latest_quote, mto.quote );\n   if( mto.base != asset_base.id )\n      latest_price = ~latest_price;\n   latest = price_to_string( latest_price, asset_base, asset_quote );\n   if( mto.last_day_base != 0 && mto.last_day_quote != 0 // has trade data before 24 hours\n       && ( mto.last_day_base != mto.latest_base || mto.last_day_quote != mto.latest_quote ) ) // price changed\n   {\n      price last_day_price = asset( mto.last_day_base, mto.base ) / asset( mto.last_day_quote, mto.quote );\n      if( mto.base != asset_base.id )\n         last_day_price = ~last_day_price;\n      percent_change = price_diff_percent_string( last_day_price, latest_price );\n   }\n   if( asset_base.id == mto.base )\n   {\n      bv = mto.base_volume;\n      qv = mto.quote_volume;\n   }\n   else\n   {\n      bv = mto.quote_volume;\n      qv = mto.base_volume;\n   }\n   base_volume = uint128_amount_to_string( bv, asset_base.precision );\n   quote_volume = uint128_amount_to_string( qv, asset_quote.precision );\n\n   if(!orders.asks.empty())\n   {\n       lowest_ask = orders.asks[0].price;\n       lowest_ask_base_size = orders.asks[0].base;\n       lowest_ask_quote_size = orders.asks[0].quote;\n   }\n\n   if(!orders.bids.empty())\n   {\n       highest_bid = orders.bids[0].price;\n       highest_bid_base_size = orders.bids[0].base;\n       highest_bid_quote_size = orders.bids[0].quote;\n   }\n\n}\n\nmarket_ticker::market_ticker(const fc::time_point_sec& now,\n                             const asset_object& asset_base,\n                             const asset_object& asset_quote)\n{\n   time = now;\n   base = asset_base.symbol;\n   quote = asset_quote.symbol;\n   latest = \"0\";\n   lowest_ask = \"0\";\n   lowest_ask_base_size = \"0\";\n   lowest_ask_quote_size = \"0\";\n   highest_bid = \"0\";\n   highest_bid_base_size = \"0\";\n   highest_bid_quote_size = \"0\";\n   percent_change = \"0\";\n   base_volume = \"0\";\n   quote_volume = \"0\";\n}\n\nmaybe_signed_block_header::maybe_signed_block_header( const signed_block_header& bh, bool with_witness_signature )\n: block_header( bh ), // Slice intentionally\n  witness_signature( with_witness_signature ? bh.witness_signature : optional<signature_type>() )\n{ // Nothing else to do\n}\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/application.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/app/api.hpp>\n#include <graphene/app/api_access.hpp>\n#include <graphene/app/application.hpp>\n#include <graphene/app/plugin.hpp>\n\n#include <graphene/chain/db_with.hpp>\n#include <graphene/chain/genesis_state.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <graphene/protocol/types.hpp>\n\n#include <graphene/egenesis/egenesis.hpp>\n\n#include <graphene/net/core_messages.hpp>\n#include <graphene/net/exceptions.hpp>\n\n#include <graphene/utilities/key_conversion.hpp>\n#include <graphene/chain/worker_evaluator.hpp>\n\n#include <fc/asio.hpp>\n#include <fc/io/fstream.hpp>\n#include <fc/rpc/api_connection.hpp>\n#include <fc/rpc/websocket_api.hpp>\n#include <fc/crypto/base64.hpp>\n\n#include <boost/filesystem/path.hpp>\n#include <boost/signals2.hpp>\n#include <boost/range/algorithm/reverse.hpp>\n#include <boost/algorithm/string.hpp>\n\n#include <iostream>\n\n#include <fc/log/file_appender.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/log/logger_config.hpp>\n\n#include <boost/range/adaptor/reversed.hpp>\n\nnamespace graphene { namespace app {\nusing net::item_hash_t;\nusing net::item_id;\nusing net::message;\nusing net::block_message;\nusing net::trx_message;\n\nusing chain::block_header;\nusing chain::signed_block_header;\nusing chain::signed_block;\nusing chain::block_id_type;\n\nusing std::vector;\n\nnamespace bpo = boost::program_options;\n\nnamespace detail {\n\n   graphene::chain::genesis_state_type create_example_genesis() {\n      auto nathan_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n      dlog(\"Allocating all stake to ${key}\", (\"key\", utilities::key_to_wif(nathan_key)));\n      graphene::chain::genesis_state_type initial_state;\n      initial_state.initial_parameters.get_mutable_fees() = fee_schedule::get_default();\n      initial_state.initial_active_witnesses = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT;\n      initial_state.initial_timestamp = time_point_sec(time_point::now().sec_since_epoch() /\n            initial_state.initial_parameters.block_interval *\n            initial_state.initial_parameters.block_interval);\n      for( uint64_t i = 0; i < initial_state.initial_active_witnesses; ++i )\n      {\n         auto name = \"init\"+fc::to_string(i);\n         initial_state.initial_accounts.emplace_back(name,\n                                                     nathan_key.get_public_key(),\n                                                     nathan_key.get_public_key(),\n                                                     true);\n         initial_state.initial_committee_candidates.push_back({name});\n         initial_state.initial_witness_candidates.push_back({name, nathan_key.get_public_key()});\n      }\n\n      initial_state.initial_accounts.emplace_back(\"nathan\", nathan_key.get_public_key());\n      initial_state.initial_balances.push_back({address(nathan_key.get_public_key()),\n                                                GRAPHENE_SYMBOL,\n                                                GRAPHENE_MAX_SHARE_SUPPLY});\n      initial_state.initial_chain_id = fc::sha256::hash( \"BOGUS\" );\n\n      return initial_state;\n   }\n\n\n}\n\n}}\n\n#include \"application_impl.hxx\"\n\nnamespace graphene { namespace app { namespace detail {\n\napplication_impl::~application_impl()\n{\n   this->shutdown();\n}\n\nvoid application_impl::reset_p2p_node(const fc::path& data_dir)\n{ try {\n   _p2p_network = std::make_shared<net::node>(\"BitShares Reference Implementation\");\n\n   _p2p_network->load_configuration(data_dir / \"p2p\");\n   _p2p_network->set_node_delegate(shared_from_this());\n\n   if( _options->count(\"seed-node\") > 0 )\n   {\n      auto seeds = _options->at(\"seed-node\").as<vector<string>>();\n      _p2p_network->add_seed_nodes(seeds);\n   }\n\n   if( _options->count(\"seed-nodes\") > 0 )\n   {\n      auto seeds_str = _options->at(\"seed-nodes\").as<string>();\n      auto seeds = fc::json::from_string(seeds_str).as<vector<string>>(2);\n      _p2p_network->add_seed_nodes(seeds);\n   }\n   else\n   {\n      vector<string> seeds = {\n         #include \"../egenesis/seed-nodes.txt\"\n      };\n      _p2p_network->add_seed_nodes(seeds);\n   }\n\n   if( _options->count( \"p2p-advertise-peer-algorithm\" ) > 0 )\n   {\n      std::string algo = _options->at(\"p2p-advertise-peer-algorithm\").as<string>();\n      std::vector<std::string> list;\n      if( algo == \"list\" && _options->count(\"p2p-advertise-peer-endpoint\") > 0 )\n         list = _options->at(\"p2p-advertise-peer-endpoint\").as<std::vector<std::string>>();\n      else if( algo == \"exclude_list\" && _options->count(\"p2p-exclude-peer-endpoint\") > 0 )\n         list = _options->at(\"p2p-exclude-peer-endpoint\").as<std::vector<std::string>>();\n      _p2p_network->set_advertise_algorithm( algo, list );\n   }\n\n   if( _options->count(\"p2p-endpoint\") > 0 )\n      _p2p_network->set_listen_endpoint( fc::ip::endpoint::from_string(_options->at(\"p2p-endpoint\").as<string>()),\n                                         true );\n   // else try to listen on the default port first, if failed, use a random port\n\n   if( _options->count(\"p2p-inbound-endpoint\") > 0 )\n      _p2p_network->set_inbound_endpoint( fc::ip::endpoint::from_string(_options->at(\"p2p-inbound-endpoint\")\n                                              .as<string>()) );\n\n   if ( _options->count(\"p2p-accept-incoming-connections\") > 0 )\n      _p2p_network->set_accept_incoming_connections( _options->at(\"p2p-accept-incoming-connections\").as<bool>() );\n\n   if ( _options->count(\"p2p-connect-to-new-peers\") > 0 )\n      _p2p_network->set_connect_to_new_peers( _options->at( \"p2p-connect-to-new-peers\" ).as<bool>() );\n\n   _p2p_network->listen_to_p2p_network();\n   fc::ip::endpoint listening_endpoint = _p2p_network->get_actual_listening_endpoint();\n   if( listening_endpoint.port() != 0 )\n      ilog( \"Configured p2p node to listen on ${ip}\", (\"ip\", listening_endpoint) );\n   else\n      ilog( \"Configured p2p node to not listen for incoming connections\" );\n\n   _p2p_network->connect_to_p2p_network();\n   _p2p_network->sync_from(net::item_id(net::core_message_type_enum::block_message_type,\n                                        _chain_db->head_block_id()),\n                           std::vector<uint32_t>());\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid application_impl::new_connection( const fc::http::websocket_connection_ptr& c )\n{\n   auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(c, GRAPHENE_NET_MAX_NESTED_OBJECTS);\n   auto login = std::make_shared<graphene::app::login_api>( _self );\n\n    // Try to extract login information from \"Authorization\" header if present\n   std::string auth = c->get_request_header(\"Authorization\");\n   if( boost::starts_with(auth, \"Basic \") ) {\n\n      FC_ASSERT( auth.size() > 6 );\n      auto user_pass = fc::base64_decode(auth.substr(6));\n\n      std::vector<std::string> parts;\n      boost::split( parts, user_pass, boost::is_any_of(\":\") );\n\n      FC_ASSERT(parts.size() == 2);\n\n      const string& username = parts[0];\n      const string& password = parts[1];\n      login->login(username, password);\n   }\n   else\n      login->login(\"\", \"\");\n\n   // API set ID 0. Note: changing it may break client applications\n   if( login->is_database_api_allowed() )\n      wsc->register_api(login->database());\n   else\n      wsc->register_api(login->dummy());\n   // API set ID 1. Note: changing it may break client applications\n   wsc->register_api(fc::api<graphene::app::login_api>(login));\n   c->set_session_data( wsc );\n}\n\nvoid application_impl::reset_websocket_server()\n{ try {\n   if( 0 == _options->count(\"rpc-endpoint\") )\n      return;\n\n   string proxy_forward_header;\n   if( _options->count(\"proxy-forwarded-for-header\") > 0 )\n      proxy_forward_header = _options->at(\"proxy-forwarded-for-header\").as<string>();\n\n   _websocket_server = std::make_shared<fc::http::websocket_server>( proxy_forward_header );\n   _websocket_server->on_connection( std::bind(&application_impl::new_connection, this, std::placeholders::_1) );\n\n   ilog(\"Configured websocket rpc to listen on ${ip}\", (\"ip\",_options->at(\"rpc-endpoint\").as<string>()));\n   _websocket_server->listen( fc::ip::endpoint::from_string(_options->at(\"rpc-endpoint\").as<string>()) );\n   _websocket_server->start_accept();\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid application_impl::reset_websocket_tls_server()\n{ try {\n   if( 0 == _options->count(\"rpc-tls-endpoint\") )\n      return;\n   if( 0 == _options->count(\"server-pem\") )\n   {\n      wlog( \"Please specify a server-pem to use rpc-tls-endpoint\" );\n      return;\n   }\n\n   string proxy_forward_header;\n   if( _options->count(\"proxy-forwarded-for-header\") > 0 )\n      proxy_forward_header = _options->at(\"proxy-forwarded-for-header\").as<string>();\n\n   string password = ( _options->count(\"server-pem-password\") > 0 ) ?\n                        _options->at(\"server-pem-password\").as<string>() : \"\";\n   _websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>(\n                                 _options->at(\"server-pem\").as<string>(), password, proxy_forward_header );\n   _websocket_tls_server->on_connection( std::bind(&application_impl::new_connection, this, std::placeholders::_1) );\n\n   ilog(\"Configured websocket TLS rpc to listen on ${ip}\", (\"ip\",_options->at(\"rpc-tls-endpoint\").as<string>()));\n   _websocket_tls_server->listen( fc::ip::endpoint::from_string(_options->at(\"rpc-tls-endpoint\").as<string>()) );\n   _websocket_tls_server->start_accept();\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid application_impl::initialize(const fc::path& data_dir, shared_ptr<boost::program_options::variables_map> options)\n{\n   _data_dir = data_dir;\n   _options = options;\n\n   if ( _options->count(\"io-threads\") > 0 )\n   {\n      const uint16_t num_threads = _options->at(\"io-threads\").as<uint16_t>();\n      fc::asio::default_io_service_scope::set_num_threads(num_threads);\n   }\n\n   if( _options->count(\"force-validate\") > 0 )\n   {\n      ilog( \"All transaction signatures will be validated\" );\n      _force_validate = true;\n   }\n\n   if ( _options->count(\"enable-subscribe-to-all\") > 0 )\n      _app_options.enable_subscribe_to_all = _options->at( \"enable-subscribe-to-all\" ).as<bool>();\n\n   set_api_limit();\n\n   if( is_plugin_enabled( \"market_history\" ) )\n      _app_options.has_market_history_plugin = true;\n   else\n      ilog(\"Market history plugin is not enabled\");\n\n   if( is_plugin_enabled( \"api_helper_indexes\" ) )\n      _app_options.has_api_helper_indexes_plugin = true;\n   else\n      ilog(\"API helper indexes plugin is not enabled\");\n\n   if (_options->count(\"api-node-info\") > 0)\n      _node_info = _options->at(\"api-node-info\").as<string>();\n\n   if( _options->count(\"api-access\") > 0 )\n   {\n\n      fc::path api_access_file = _options->at(\"api-access\").as<boost::filesystem::path>();\n\n      FC_ASSERT( fc::exists(api_access_file),\n            \"Failed to load file from ${path}\", (\"path\", api_access_file) );\n\n      _apiaccess = fc::json::from_file( api_access_file ).as<api_access>( 20 );\n      ilog( \"Using api access file from ${path}\",\n            (\"path\", api_access_file) );\n   }\n   else\n   {\n      // TODO:  Remove this generous default access policy\n      // when the UI logs in properly\n      _apiaccess = api_access();\n      api_access_info wild_access(\"*\", \"*\");\n      wild_access.allowed_apis.insert( \"database_api\" );\n      wild_access.allowed_apis.insert( \"network_broadcast_api\" );\n      wild_access.allowed_apis.insert( \"history_api\" );\n      wild_access.allowed_apis.insert( \"orders_api\" );\n      wild_access.allowed_apis.insert( \"custom_operations_api\" );\n      _apiaccess.permission_map[\"*\"] = wild_access;\n   }\n\n   initialize_plugins();\n}\n\nvoid application_impl::set_api_limit() {\n   if (_options->count(\"api-limit-get-account-history-operations\") > 0) {\n      _app_options.api_limit_get_account_history_operations =\n            _options->at(\"api-limit-get-account-history-operations\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-account-history\") > 0){\n      _app_options.api_limit_get_account_history =\n            _options->at(\"api-limit-get-account-history\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-grouped-limit-orders\") > 0){\n      _app_options.api_limit_get_grouped_limit_orders =\n            _options->at(\"api-limit-get-grouped-limit-orders\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-market-history\") > 0){\n      _app_options.api_limit_get_market_history =\n            _options->at(\"api-limit-get-market-history\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-relative-account-history\") > 0){\n      _app_options.api_limit_get_relative_account_history =\n            _options->at(\"api-limit-get-relative-account-history\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-account-history-by-operations\") > 0){\n      _app_options.api_limit_get_account_history_by_operations =\n            _options->at(\"api-limit-get-account-history-by-operations\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-asset-holders\") > 0){\n      _app_options.api_limit_get_asset_holders =\n            _options->at(\"api-limit-get-asset-holders\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-key-references\") > 0){\n      _app_options.api_limit_get_key_references =\n            _options->at(\"api-limit-get-key-references\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-htlc-by\") > 0) {\n      _app_options.api_limit_get_htlc_by =\n            _options->at(\"api-limit-get-htlc-by\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-full-accounts\") > 0) {\n      _app_options.api_limit_get_full_accounts =\n            _options->at(\"api-limit-get-full-accounts\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-full-accounts-lists\") > 0) {\n      _app_options.api_limit_get_full_accounts_lists =\n            _options->at(\"api-limit-get-full-accounts-lists\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-full-accounts-subscribe\") > 0) {\n      _app_options.api_limit_get_full_accounts_subscribe =\n            _options->at(\"api-limit-get-full-accounts-subscribe\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-top-voters\") > 0) {\n      _app_options.api_limit_get_top_voters =\n            _options->at(\"api-limit-get-top-voters\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-call-orders\") > 0) {\n      _app_options.api_limit_get_call_orders =\n            _options->at(\"api-limit-get-call-orders\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-settle-orders\") > 0) {\n      _app_options.api_limit_get_settle_orders =\n            _options->at(\"api-limit-get-settle-orders\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-assets\") > 0) {\n      _app_options.api_limit_get_assets =\n            _options->at(\"api-limit-get-assets\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-limit-orders\") > 0){\n      _app_options.api_limit_get_limit_orders =\n            _options->at(\"api-limit-get-limit-orders\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-limit-orders-by-account\") > 0){\n      _app_options.api_limit_get_limit_orders_by_account =\n            _options->at(\"api-limit-get-limit-orders-by-account\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-order-book\") > 0){\n      _app_options.api_limit_get_order_book =\n            _options->at(\"api-limit-get-order-book\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-list-htlcs\") > 0){\n      _app_options.api_limit_list_htlcs =\n            _options->at(\"api-limit-list-htlcs\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-lookup-accounts\") > 0) {\n      _app_options.api_limit_lookup_accounts =\n            _options->at(\"api-limit-lookup-accounts\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-lookup-witness-accounts\") > 0) {\n      _app_options.api_limit_lookup_witness_accounts =\n            _options->at(\"api-limit-lookup-witness-accounts\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-lookup-committee-member-accounts\") > 0) {\n      _app_options.api_limit_lookup_committee_member_accounts =\n            _options->at(\"api-limit-lookup-committee-member-accounts\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-lookup-vote-ids\") > 0) {\n      _app_options.api_limit_lookup_vote_ids =\n            _options->at(\"api-limit-lookup-vote-ids\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-account-limit-orders\") > 0) {\n      _app_options.api_limit_get_account_limit_orders =\n            _options->at(\"api-limit-get-account-limit-orders\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-collateral-bids\") > 0) {\n      _app_options.api_limit_get_collateral_bids =\n            _options->at(\"api-limit-get-collateral-bids\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-top-markets\") > 0) {\n      _app_options.api_limit_get_top_markets =\n            _options->at(\"api-limit-get-top-markets\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-trade-history\") > 0) {\n      _app_options.api_limit_get_trade_history =\n            _options->at(\"api-limit-get-trade-history\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-trade-history-by-sequence\") > 0) {\n      _app_options.api_limit_get_trade_history_by_sequence =\n            _options->at(\"api-limit-get-trade-history-by-sequence\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-withdraw-permissions-by-giver\") > 0) {\n      _app_options.api_limit_get_withdraw_permissions_by_giver =\n            _options->at(\"api-limit-get-withdraw-permissions-by-giver\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-withdraw-permissions-by-recipient\") > 0) {\n      _app_options.api_limit_get_withdraw_permissions_by_recipient =\n            _options->at(\"api-limit-get-withdraw-permissions-by-recipient\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-tickets\") > 0) {\n      _app_options.api_limit_get_tickets =\n            _options->at(\"api-limit-get-tickets\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-liquidity-pools\") > 0) {\n      _app_options.api_limit_get_liquidity_pools =\n            _options->at(\"api-limit-get-liquidity-pools\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-liquidity-pool-history\") > 0) {\n      _app_options.api_limit_get_liquidity_pool_history =\n            _options->at(\"api-limit-get-liquidity-pool-history\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-samet-funds\") > 0) {\n      _app_options.api_limit_get_samet_funds =\n            _options->at(\"api-limit-get-samet-funds\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-credit-offers\") > 0) {\n      _app_options.api_limit_get_credit_offers =\n            _options->at(\"api-limit-get-credit-offers\").as<uint32_t>();\n   }\n   if(_options->count(\"api-limit-get-storage-info\") > 0) {\n      _app_options.api_limit_get_storage_info =\n            _options->at(\"api-limit-get-storage-info\").as<uint32_t>();\n   }\n}\n\ngraphene::chain::genesis_state_type application_impl::initialize_genesis_state() const\n{\n   try {\n      ilog(\"Initializing database...\");\n      if( _options->count(\"genesis-json\") > 0 )\n      {\n         std::string genesis_str;\n         fc::read_file_contents( _options->at(\"genesis-json\").as<boost::filesystem::path>(), genesis_str );\n         auto genesis = fc::json::from_string( genesis_str ).as<graphene::chain::genesis_state_type>( 20 );\n         bool modified_genesis = false;\n         if( _options->count(\"genesis-timestamp\") > 0 )\n         {\n            genesis.initial_timestamp = fc::time_point_sec( fc::time_point::now() )\n                                      + genesis.initial_parameters.block_interval\n                                      + _options->at(\"genesis-timestamp\").as<uint32_t>();\n            genesis.initial_timestamp -= ( genesis.initial_timestamp.sec_since_epoch()\n                                           % genesis.initial_parameters.block_interval );\n            modified_genesis = true;\n\n            ilog(\n               \"Used genesis timestamp:  ${timestamp} (PLEASE RECORD THIS)\",\n               (\"timestamp\", genesis.initial_timestamp.to_iso_string())\n            );\n         }\n         if( _options->count(\"dbg-init-key\") > 0 )\n         {\n            std::string init_key = _options->at( \"dbg-init-key\" ).as<string>();\n            FC_ASSERT( genesis.initial_witness_candidates.size() >= genesis.initial_active_witnesses );\n            genesis.override_witness_signing_keys( init_key );\n            modified_genesis = true;\n            ilog(\"Set init witness key to ${init_key}\", (\"init_key\", init_key));\n         }\n         if( modified_genesis )\n         {\n            wlog(\"WARNING:  GENESIS WAS MODIFIED, YOUR CHAIN ID MAY BE DIFFERENT\");\n            genesis_str += \"BOGUS\";\n         }\n         genesis.initial_chain_id = fc::sha256::hash( genesis_str );\n         return genesis;\n      }\n      else\n      {\n         std::string egenesis_json;\n         graphene::egenesis::compute_egenesis_json( egenesis_json );\n         FC_ASSERT( egenesis_json != \"\" );\n         FC_ASSERT( graphene::egenesis::get_egenesis_json_hash() == fc::sha256::hash( egenesis_json ) );\n         auto genesis = fc::json::from_string( egenesis_json ).as<graphene::chain::genesis_state_type>( 20 );\n         genesis.initial_chain_id = fc::sha256::hash( egenesis_json );\n         return genesis;\n      }\n   } FC_CAPTURE_AND_RETHROW() // GCOVR_EXCL_LINE\n}\n\nvoid application_impl::open_chain_database() const\n{ try {\n   fc::create_directories(_data_dir / \"blockchain\");\n\n   if( _options->count(\"resync-blockchain\") > 0 )\n      _chain_db->wipe(_data_dir / \"blockchain\", true);\n\n   flat_map<uint32_t,block_id_type> loaded_checkpoints;\n   if( _options->count(\"checkpoint\") > 0 )\n   {\n      auto cps = _options->at(\"checkpoint\").as<vector<string>>();\n      loaded_checkpoints.reserve( cps.size() );\n      for( auto cp : cps )\n      {\n         auto item = fc::json::from_string(cp).as<std::pair<uint32_t,block_id_type> >( 2 );\n         loaded_checkpoints[item.first] = item.second;\n      }\n   }\n   _chain_db->add_checkpoints( loaded_checkpoints );\n\n   if( _options->count(\"enable-standby-votes-tracking\") > 0 )\n   {\n      _chain_db->enable_standby_votes_tracking( _options->at(\"enable-standby-votes-tracking\").as<bool>() );\n   }\n\n   if( _options->count(\"replay-blockchain\") > 0 || _options->count(\"revalidate-blockchain\") > 0 )\n      _chain_db->wipe( _data_dir / \"blockchain\", false );\n\n   try\n   {\n      // these flags are used in open() only, i. e. during replay\n      uint32_t skip;\n      if( _options->count(\"revalidate-blockchain\") > 0 ) // see also handle_block()\n      {\n         if( !loaded_checkpoints.empty() )\n            wlog( \"Warning - revalidate will not validate before last checkpoint\" );\n         if( _options->count(\"force-validate\") > 0 )\n            skip = graphene::chain::database::skip_nothing;\n         else\n            skip = graphene::chain::database::skip_transaction_signatures;\n      }\n      else // no revalidate, skip most checks\n         skip = graphene::chain::database::skip_witness_signature |\n                graphene::chain::database::skip_block_size_check |\n                graphene::chain::database::skip_merkle_check |\n                graphene::chain::database::skip_transaction_signatures |\n                graphene::chain::database::skip_transaction_dupe_check |\n                graphene::chain::database::skip_tapos_check |\n                graphene::chain::database::skip_witness_schedule_check;\n\n      auto genesis_loader = [this](){\n         return initialize_genesis_state();\n      };\n\n      graphene::chain::detail::with_skip_flags( *_chain_db, skip, [this, &genesis_loader] () {\n         _chain_db->open( _data_dir / \"blockchain\", genesis_loader, GRAPHENE_CURRENT_DB_VERSION );\n      });\n   }\n   catch( const fc::exception& e )\n   {\n      elog( \"Caught exception ${e} in open(), you might want to force a replay\", (\"e\", e.to_detail_string()) );\n      throw;\n   }\n} FC_LOG_AND_RETHROW() }\n\nvoid application_impl::startup()\n{ try {\n   bool enable_p2p_network = true;\n   if( _options->count(\"enable-p2p-network\") > 0 )\n      enable_p2p_network = _options->at(\"enable-p2p-network\").as<bool>();\n\n   open_chain_database();\n\n   startup_plugins();\n\n   if( enable_p2p_network && _active_plugins.find( \"delayed_node\" ) == _active_plugins.end() )\n      reset_p2p_node(_data_dir);\n\n   reset_websocket_server();\n   reset_websocket_tls_server();\n} FC_LOG_AND_RETHROW() }\n\noptional< api_access_info > application_impl::get_api_access_info(const string& username)const\n{\n   optional< api_access_info > result;\n   auto it = _apiaccess.permission_map.find(username);\n   if( it == _apiaccess.permission_map.end() )\n   {\n      it = _apiaccess.permission_map.find(\"*\");\n      if( it == _apiaccess.permission_map.end() )\n         return result;\n   }\n   return it->second;\n}\n\nvoid application_impl::set_api_access_info(const string& username, api_access_info&& permissions)\n{\n   _apiaccess.permission_map.insert(std::make_pair(username, std::move(permissions)));\n}\n\nbool application_impl::is_plugin_enabled(const string& name) const\n{\n   return !(_active_plugins.find(name) == _active_plugins.end());\n}\n\n/*\n * If delegate has the item, the network has no need to fetch it.\n */\nbool application_impl::has_item(const net::item_id& id)\n{\n   try\n   {\n      if( id.item_type == graphene::net::block_message_type )\n         return _chain_db->is_known_block(id.item_hash);\n      else\n         return _chain_db->is_known_transaction(id.item_hash);\n   }\n   FC_CAPTURE_AND_RETHROW( (id) ) // GCOVR_EXCL_LINE\n}\n\n/*\n * @brief allows the application to validate an item prior to broadcasting to peers.\n *\n * @param sync_mode true if the message was fetched through the sync process, false during normal operation\n * @returns true if this message caused the blockchain to switch forks, false if it did not\n *\n * @throws exception if error validating the item, otherwise the item is safe to broadcast on.\n */\nbool application_impl::handle_block(const graphene::net::block_message& blk_msg, bool sync_mode,\n                          std::vector<graphene::net::message_hash_type>& contained_transaction_msg_ids)\n{ try {\n\n   auto latency = fc::time_point::now() - blk_msg.block.timestamp;\n   if (!sync_mode || blk_msg.block.block_num() % 10000 == 0)\n   {\n      const auto& witness = blk_msg.block.witness(*_chain_db);\n      const auto& witness_account = witness.witness_account(*_chain_db);\n      auto last_irr = _chain_db->get_dynamic_global_properties().last_irreversible_block_num;\n      ilog(\"Got block: #${n} ${bid} time: ${t} transaction(s): ${x} \"\n           \"latency: ${l} ms from: ${w}  irreversible: ${i} (-${d})\",\n           (\"t\",blk_msg.block.timestamp)\n           (\"n\", blk_msg.block.block_num())\n           (\"bid\", blk_msg.block.id())\n           (\"x\", blk_msg.block.transactions.size())\n           (\"l\", (latency.count()/1000))\n           (\"w\",witness_account.name)\n           (\"i\",last_irr)(\"d\",blk_msg.block.block_num()-last_irr) );\n   }\n   GRAPHENE_ASSERT( latency.count()/1000 > -2500, // 2.5 seconds\n                    graphene::net::block_timestamp_in_future_exception,\n                    \"Rejecting block with timestamp in the future\", );\n\n   try {\n      const uint32_t skip = (_is_block_producer || _force_validate) ?\n                               database::skip_nothing : database::skip_transaction_signatures;\n      bool result = valve.do_serial( [this,&blk_msg,skip] () {\n         _chain_db->precompute_parallel( blk_msg.block, skip ).wait();\n      }, [this,&blk_msg,skip] () {\n         // TODO: in the case where this block is valid but on a fork that's too old for us to switch to,\n         // you can help the network code out by throwing a block_older_than_undo_history exception.\n         // when the net code sees that, it will stop trying to push blocks from that chain, but\n         // leave that peer connected so that they can get sync blocks from us\n         return _chain_db->push_block( blk_msg.block, skip );\n      });\n\n      // the block was accepted, so we now know all of the transactions contained in the block\n      if (!sync_mode)\n      {\n         // if we're not in sync mode, there's a chance we will be seeing some transactions\n         // included in blocks before we see the free-floating transaction itself.  If that\n         // happens, there's no reason to fetch the transactions, so  construct a list of the\n         // transaction message ids we no longer need.\n         // during sync, it is unlikely that we'll see any old\n         contained_transaction_msg_ids.reserve( contained_transaction_msg_ids.size()\n                                                    + blk_msg.block.transactions.size() );\n         for (const processed_transaction& ptrx : blk_msg.block.transactions)\n         {\n            graphene::net::trx_message transaction_message(ptrx);\n            contained_transaction_msg_ids.emplace_back(graphene::net::message(transaction_message).id());\n         }\n      }\n\n      return result;\n   } catch ( const graphene::chain::unlinkable_block_exception& e ) {\n      // translate to a graphene::net exception\n      elog(\"Error when pushing block:\\n${e}\", (\"e\", e.to_detail_string()));\n      FC_THROW_EXCEPTION( graphene::net::unlinkable_block_exception,\n                          \"Error when pushing block:\\n${e}\",\n                          (\"e\", e.to_detail_string()) );\n   } catch( const fc::exception& e ) {\n      elog(\"Error when pushing block:\\n${e}\", (\"e\", e.to_detail_string()));\n      throw;\n   }\n\n   if( !_is_finished_syncing && !sync_mode )\n   {\n      _is_finished_syncing = true;\n      _self.syncing_finished();\n   }\n} FC_CAPTURE_AND_RETHROW( (blk_msg)(sync_mode) ) return false; } // GCOVR_EXCL_LINE\n\nvoid application_impl::handle_transaction(const graphene::net::trx_message& transaction_message)\n{ try {\n   static fc::time_point last_call;\n   static int trx_count = 0;\n   ++trx_count;\n   auto now = fc::time_point::now();\n   if( now - last_call > fc::seconds(1) ) {\n      ilog(\"Got ${c} transactions from network\", (\"c\",trx_count) );\n      last_call = now;\n      trx_count = 0;\n   }\n\n   _chain_db->precompute_parallel( transaction_message.trx ).wait();\n   _chain_db->push_transaction( transaction_message.trx );\n} FC_CAPTURE_AND_RETHROW( (transaction_message) ) } // GCOVR_EXCL_LINE\n\nvoid application_impl::handle_message(const message& message_to_process)\n{\n   // not a transaction, not a block\n   FC_THROW( \"Invalid Message Type\" );\n}\n\nbool application_impl::is_included_block(const block_id_type& block_id)\n{\n  uint32_t block_num = block_header::num_from_id(block_id);\n  block_id_type block_id_in_preferred_chain = _chain_db->get_block_id_for_num(block_num);\n  return block_id == block_id_in_preferred_chain;\n}\n\n/*\n * Assuming all data elements are ordered in some way, this method should\n * return up to limit ids that occur *after* the last ID in synopsis that\n * we recognize.\n *\n * On return, remaining_item_count will be set to the number of items\n * in our blockchain after the last item returned in the result,\n * or 0 if the result contains the last item in the blockchain\n */\nstd::vector<item_hash_t> application_impl::get_block_ids(const std::vector<item_hash_t>& blockchain_synopsis,\n                                               uint32_t& remaining_item_count,\n                                               uint32_t limit)\n{ try {\n   vector<block_id_type> result;\n   remaining_item_count = 0;\n   if( _chain_db->head_block_num() == 0 )\n      return result;\n\n   result.reserve(limit);\n   block_id_type last_known_block_id;\n\n   if (blockchain_synopsis.empty() ||\n       (blockchain_synopsis.size() == 1 && blockchain_synopsis[0] == block_id_type()))\n   {\n     // peer has sent us an empty synopsis meaning they have no blocks.\n     // A bug in old versions would cause them to send a synopsis containing block 000000000\n     // when they had an empty blockchain, so pretend they sent the right thing here.\n\n     // do nothing, leave last_known_block_id set to zero\n   }\n   else\n   {\n     bool found_a_block_in_synopsis = false;\n     for (const item_hash_t& block_id_in_synopsis : boost::adaptors::reverse(blockchain_synopsis))\n       if (block_id_in_synopsis == block_id_type() ||\n           (_chain_db->is_known_block(block_id_in_synopsis) && is_included_block(block_id_in_synopsis)))\n       {\n         last_known_block_id = block_id_in_synopsis;\n         found_a_block_in_synopsis = true;\n         break;\n       }\n     if (!found_a_block_in_synopsis)\n       FC_THROW_EXCEPTION( graphene::net::peer_is_on_an_unreachable_fork,\n                           \"Unable to provide a list of blocks starting at any of the blocks in peer's synopsis\" );\n   }\n   for( uint32_t num = block_header::num_from_id(last_known_block_id);\n        num <= _chain_db->head_block_num() && result.size() < limit;\n        ++num )\n      if( num > 0 )\n         result.push_back(_chain_db->get_block_id_for_num(num));\n\n   if( !result.empty() && block_header::num_from_id(result.back()) < _chain_db->head_block_num() )\n      remaining_item_count = _chain_db->head_block_num() - block_header::num_from_id(result.back());\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (blockchain_synopsis)(remaining_item_count)(limit) ) } // GCOVR_EXCL_LINE\n\n/*\n * Given the hash of the requested data, fetch the body.\n */\nmessage application_impl::get_item(const item_id& id)\n{ try {\n  // ilog(\"Request for item ${id}\", (\"id\", id));\n   if( id.item_type == graphene::net::block_message_type )\n   {\n      auto opt_block = _chain_db->fetch_block_by_id(id.item_hash);\n      if( !opt_block )\n         elog(\"Couldn't find block ${id} -- corresponding ID in our chain is ${id2}\",\n              (\"id\", id.item_hash)(\"id2\", _chain_db->get_block_id_for_num(block_header::num_from_id(id.item_hash))));\n      FC_ASSERT( opt_block.valid() );\n      // ilog(\"Serving up block #${num}\", (\"num\", opt_block->block_num()));\n      return block_message(std::move(*opt_block));\n   }\n   return trx_message( _chain_db->get_recent_transaction( id.item_hash ) );\n} FC_CAPTURE_AND_RETHROW( (id) ) } // GCOVR_EXCL_LINE\n\nchain_id_type application_impl::get_chain_id() const\n{\n   return _chain_db->get_chain_id();\n}\n\n/*\n * Returns a synopsis of the blockchain used for syncing.  This consists of a list of\n * block hashes at intervals exponentially increasing towards the genesis block.\n * When syncing to a peer, the peer uses this data to determine if we're on the same\n * fork as they are, and if not, what blocks they need to send us to get us on their\n * fork.\n *\n * In the over-simplified case, this is a straighforward synopsis of our current\n * preferred blockchain; when we first connect up to a peer, this is what we will be sending.\n * It looks like this:\n *   If the blockchain is empty, it will return the empty list.\n *   If the blockchain has one block, it will return a list containing just that block.\n *   If it contains more than one block:\n *     the first element in the list will be the hash of the highest numbered block that\n *         we cannot undo\n *     the second element will be the hash of an item at the half way point in the undoable\n *         segment of the blockchain\n *     the third will be ~3/4 of the way through the undoable segment of the block chain\n *     the fourth will be at ~7/8...\n *       &c.\n *     the last item in the list will be the hash of the most recent block on our preferred chain\n * so if the blockchain had 26 blocks labeled a - z, the synopsis would be:\n *    a n u x z\n * the idea being that by sending a small (<30) number of block ids, we can summarize a huge\n * blockchain.  The block ids are more dense near the end of the chain where because we are\n * more likely to be almost in sync when we first connect, and forks are likely to be short.\n * If the peer we're syncing with in our example is on a fork that started at block 'v',\n * then they will reply to our synopsis with a list of all blocks starting from block 'u',\n * the last block they know that we had in common.\n *\n * In the real code, there are several complications.\n *\n * First, as an optimization, we don't usually send a synopsis of the entire blockchain, we\n * send a synopsis of only the segment of the blockchain that we have undo data for.  If their\n * fork doesn't build off of something in our undo history, we would be unable to switch, so there's\n * no reason to fetch the blocks.\n *\n * Second, when a peer replies to our initial synopsis and gives us a list of the blocks they think\n * we are missing, they only send a chunk of a few thousand blocks at once.  After we get those\n * block ids, we need to request more blocks by sending another synopsis (we can't just say \"send me\n * the next 2000 ids\" because they may have switched forks themselves and they don't track what\n * they've sent us).  For faster performance, we want to get a fairly long list of block ids first,\n * then start downloading the blocks.\n * The peer doesn't handle these follow-up block id requests any different from the initial request;\n * it treats the synopsis we send as our blockchain and bases its response entirely off that.  So to\n * get the response we want (the next chunk of block ids following the last one they sent us, or,\n * failing that, the shortest fork off of the last list of block ids they sent), we need to construct\n * a synopsis as if our blockchain was made up of:\n *    1. the blocks in our block chain up to the fork point (if there is a fork) or the head block (if no fork)\n *    2. the blocks we've already pushed from their fork (if there's a fork)\n *    3. the block ids they've previously sent us\n * Segment 3 is handled in the p2p code, it just tells us the number of blocks it has (in\n * number_of_blocks_after_reference_point) so we can leave space in the synopsis for them.\n * We're responsible for constructing the synopsis of Segments 1 and 2 from our active blockchain and\n * fork database.  The reference_point parameter is the last block from that peer that has been\n * successfully pushed to the blockchain, so that tells us whether the peer is on a fork or on\n * the main chain.\n */\nstd::vector<item_hash_t> application_impl::get_blockchain_synopsis(const item_hash_t& reference_point,\n                                                         uint32_t number_of_blocks_after_reference_point)\n{ try {\n    std::vector<item_hash_t> synopsis;\n    synopsis.reserve(30);\n    uint32_t high_block_num;\n    uint32_t non_fork_high_block_num;\n    uint32_t low_block_num = _chain_db->last_non_undoable_block_num();\n    std::vector<block_id_type> fork_history;\n\n    if (reference_point != item_hash_t())\n    {\n      // the node is asking for a summary of the block chain up to a specified\n      // block, which may or may not be on a fork\n      // for now, assume it's not on a fork\n      if (is_included_block(reference_point))\n      {\n        // reference_point is a block we know about and is on the main chain\n        uint32_t reference_point_block_num = block_header::num_from_id(reference_point);\n        assert(reference_point_block_num > 0);\n        high_block_num = reference_point_block_num;\n        non_fork_high_block_num = high_block_num;\n\n        if (reference_point_block_num < low_block_num)\n        {\n          // we're on the same fork (at least as far as reference_point) but we've passed\n          // reference point and could no longer undo that far if we diverged after that\n          // block.  This should probably only happen due to a race condition where\n          // the network thread calls this function, and then immediately pushes a bunch of blocks,\n          // then the main thread finally processes this function.\n          // with the current framework, there's not much we can do to tell the network\n          // thread what our current head block is, so we'll just pretend that\n          // our head is actually the reference point.\n          // this *may* enable us to fetch blocks that we're unable to push, but that should\n          // be a rare case (and correctly handled)\n          low_block_num = reference_point_block_num;\n        }\n      }\n      else\n      {\n        // block is a block we know about, but it is on a fork\n        try\n        {\n          fork_history = _chain_db->get_block_ids_on_fork(reference_point);\n          // returns a vector where the last element is the common ancestor with the preferred chain,\n          // and the first element is the reference point you passed in\n          assert(fork_history.size() >= 2);\n\n          if( fork_history.front() != reference_point )\n          {\n             edump( (fork_history)(reference_point) );\n             assert(fork_history.front() == reference_point);\n          }\n          block_id_type last_non_fork_block = fork_history.back();\n          fork_history.pop_back();  // remove the common ancestor\n          boost::reverse(fork_history);\n\n          if (last_non_fork_block == block_id_type()) // if the fork goes all the way back to genesis (does graphene's fork db allow this?)\n            non_fork_high_block_num = 0;\n          else\n            non_fork_high_block_num = block_header::num_from_id(last_non_fork_block);\n\n          high_block_num = non_fork_high_block_num + fork_history.size();\n          assert(high_block_num == block_header::num_from_id(fork_history.back()));\n        }\n        catch (const fc::exception& e)\n        {\n          // unable to get fork history for some reason.  maybe not linked?\n          // we can't return a synopsis of its chain\n          elog( \"Unable to construct a blockchain synopsis for reference hash ${hash}: ${exception}\",\n                (\"hash\", reference_point)(\"exception\", e) );\n          throw;\n        }\n        if (non_fork_high_block_num < low_block_num)\n        {\n          wlog(\"Unable to generate a usable synopsis because the peer we're generating it for forked too long ago \"\n               \"(our chains diverge after block #${non_fork_high_block_num} but only undoable to block #${low_block_num})\",\n               (\"low_block_num\", low_block_num)\n               (\"non_fork_high_block_num\", non_fork_high_block_num));\n          FC_THROW_EXCEPTION(graphene::net::block_older_than_undo_history, \"Peer is are on a fork I'm unable to switch to\");\n        }\n      }\n    }\n    else\n    {\n      // no reference point specified, summarize the whole block chain\n      high_block_num = _chain_db->head_block_num();\n      non_fork_high_block_num = high_block_num;\n      if (high_block_num == 0)\n        return synopsis; // we have no blocks\n    }\n\n    if( low_block_num == 0)\n       low_block_num = 1;\n\n    // at this point:\n    // low_block_num is the block before the first block we can undo,\n    // non_fork_high_block_num is the block before the fork (if the peer is on a fork, or otherwise it is the same as high_block_num)\n    // high_block_num is the block number of the reference block, or the end of the chain if no reference provided\n\n    // true_high_block_num is the ending block number after the network code appends any item ids it\n    // knows about that we don't\n    uint32_t true_high_block_num = high_block_num + number_of_blocks_after_reference_point;\n    do\n    {\n      // for each block in the synopsis, figure out where to pull the block id from.\n      // if it's <= non_fork_high_block_num, we grab it from the main blockchain;\n      // if it's not, we pull it from the fork history\n      if (low_block_num <= non_fork_high_block_num)\n        synopsis.push_back(_chain_db->get_block_id_for_num(low_block_num));\n      else\n        synopsis.push_back(fork_history[low_block_num - non_fork_high_block_num - 1]);\n      low_block_num += (true_high_block_num - low_block_num + 2) / 2;\n    }\n    while (low_block_num <= high_block_num);\n\n    //idump((synopsis));\n    return synopsis;\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\n/*\n * Call this after the call to handle_message succeeds.\n *\n * @param item_type the type of the item we're synchronizing, will be the same as item passed to the sync_from() call\n * @param item_count the number of items known to the node that haven't been sent to handle_item() yet.\n *                   After `item_count` more calls to handle_item(), the node will be in sync\n */\nvoid application_impl::sync_status(uint32_t item_type, uint32_t item_count)\n{\n   // any status reports to GUI go here\n}\n\n/*\n * Call any time the number of connected peers changes.\n */\nvoid application_impl::connection_count_changed(uint32_t c)\n{\n  // any status reports to GUI go here\n}\n\nuint32_t application_impl::get_block_number(const item_hash_t& block_id)\n{ try {\n   return block_header::num_from_id(block_id);\n} FC_CAPTURE_AND_RETHROW( (block_id) ) } // GCOVR_EXCL_LINE\n\n/*\n * Returns the time a block was produced (if block_id = 0, returns genesis time).\n * If we don't know about the block, returns time_point_sec::min()\n */\nfc::time_point_sec application_impl::get_block_time(const item_hash_t& block_id)\n{ try {\n   auto opt_block = _chain_db->fetch_block_by_id( block_id );\n   if( opt_block.valid() ) return opt_block->timestamp;\n   return fc::time_point_sec::min();\n} FC_CAPTURE_AND_RETHROW( (block_id) ) } // GCOVR_EXCL_LINE\n\nitem_hash_t application_impl::get_head_block_id() const\n{\n   return _chain_db->head_block_id();\n}\n\nuint32_t application_impl::estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const\n{\n   return 0; // there are no forks in graphene\n}\n\nvoid application_impl::error_encountered(const std::string& message, const fc::oexception& error)\n{\n   // notify GUI or something cool\n}\n\nuint8_t application_impl::get_current_block_interval_in_seconds() const\n{\n   FC_ASSERT( _chain_db, \"Chain database is not operational\" );\n   return _chain_db->get_global_properties().parameters.block_interval;\n}\n\nvoid application_impl::shutdown()\n{\n   ilog( \"Shutting down application\" );\n   if( _websocket_tls_server )\n      _websocket_tls_server.reset();\n   if( _websocket_server )\n      _websocket_server.reset();\n   // TODO wait until all connections are closed and messages handled?\n\n   // plugins E.G. witness_plugin may send data to p2p network, so shutdown them first\n   ilog( \"Shutting down plugins\" );\n   shutdown_plugins();\n\n   if( _p2p_network )\n   {\n      ilog( \"Disconnecting from P2P network\" );\n      // FIXME wait() is called in close() but it doesn't block this thread\n      _p2p_network->close();\n      _p2p_network.reset();\n   }\n   else\n      ilog( \"P2P network is disabled\" );\n\n   if( _chain_db )\n   {\n      ilog( \"Closing chain database\" );\n      _chain_db->close();\n      _chain_db.reset();\n   }\n   else\n      ilog( \"Chain database is not open\" );\n}\n\nvoid application_impl::enable_plugin( const string& name )\n{\n   FC_ASSERT(_available_plugins[name], \"Unknown plugin '\" + name + \"'\");\n   _active_plugins[name] = _available_plugins[name];\n}\n\nvoid application_impl::initialize_plugins() const\n{\n   for( const auto& entry : _active_plugins )\n   {\n      ilog( \"Initializing plugin ${name}\", ( \"name\", entry.second->plugin_name() ) );\n      entry.second->plugin_initialize( *_options );\n      ilog( \"Initialized plugin ${name}\", ( \"name\", entry.second->plugin_name() ) );\n   }\n}\n\nvoid application_impl::startup_plugins() const\n{\n   for( const auto& entry : _active_plugins )\n   {\n      ilog( \"Starting plugin ${name}\", ( \"name\", entry.second->plugin_name() ) );\n      entry.second->plugin_startup();\n      ilog( \"Started plugin ${name}\", ( \"name\", entry.second->plugin_name() ) );\n   }\n}\n\nvoid application_impl::shutdown_plugins() const\n{\n   for( const auto& entry : _active_plugins )\n   {\n      ilog( \"Stopping plugin ${name}\", ( \"name\", entry.second->plugin_name() ) );\n      entry.second->plugin_shutdown();\n      ilog( \"Stopped plugin ${name}\", ( \"name\", entry.second->plugin_name() ) );\n   }\n}\n\nvoid application_impl::add_available_plugin(std::shared_ptr<graphene::app::abstract_plugin> p)\n{\n   _available_plugins[p->plugin_name()] = p;\n}\n\nvoid application_impl::set_block_production(bool producing_blocks)\n{\n   _is_block_producer = producing_blocks;\n}\n\n} } } // namespace graphene namespace app namespace detail\n\nnamespace graphene { namespace app {\n\napplication::application()\n   : my(std::make_shared<detail::application_impl>(*this))\n{\n   //nothing else to do\n}\n\napplication::~application()\n{\n   ilog(\"Application quitting\");\n   my->shutdown();\n}\n\nvoid application::set_program_options(boost::program_options::options_description& command_line_options,\n                                      boost::program_options::options_description& configuration_file_options) const\n{\n   const auto& default_opts = application_options::get_default();\n   configuration_file_options.add_options()\n         (\"enable-p2p-network\", bpo::value<bool>()->implicit_value(true),\n          \"Whether to enable P2P network (default: true). Note: if delayed_node plugin is enabled, \"\n          \"this option will be ignored and P2P network will always be disabled.\")\n         (\"p2p-accept-incoming-connections\", bpo::value<bool>()->implicit_value(true),\n          \"Whether to accept incoming P2P connections (default: true)\")\n         (\"p2p-endpoint\", bpo::value<string>(),\n          \"The endpoint (local IP address:port) on which the node will listen for P2P connections. \"\n          \"Specify 0.0.0.0 as address to listen on all IP addresses\")\n         (\"p2p-inbound-endpoint\", bpo::value<string>(),\n          \"The endpoint (external IP address:port) that other P2P peers should connect to. \"\n          \"If the address is unknown or dynamic, specify 0.0.0.0\")\n         (\"p2p-connect-to-new-peers\", bpo::value<bool>()->implicit_value(true),\n          \"Whether the node will connect to new P2P peers advertised by other peers (default: true)\")\n         (\"p2p-advertise-peer-algorithm\", bpo::value<string>()->implicit_value(\"all\"),\n          \"Determines which P2P peers are advertised in response to address requests from other peers. \"\n          \"Algorithms: 'all', 'nothing', 'list', exclude_list'. (default: all)\")\n         (\"p2p-advertise-peer-endpoint\", bpo::value<vector<string>>()->composing(),\n          \"The endpoint (IP address:port) of the P2P peer to advertise, only takes effect when algorithm \"\n          \"is 'list' (may specify multiple times)\")\n         (\"p2p-exclude-peer-endpoint\", bpo::value<vector<string>>()->composing(),\n          \"The endpoint (IP address:port) of the P2P peer to not advertise, only takes effect when algorithm \"\n          \"is 'exclude_list' (may specify multiple times)\")\n         (\"seed-node,s\", bpo::value<vector<string>>()->composing(),\n          \"The endpoint (IP address:port) of the P2P peer to connect to on startup (may specify multiple times)\")\n         (\"seed-nodes\", bpo::value<string>()->composing(),\n          \"JSON array of P2P peers to connect to on startup\")\n         (\"checkpoint,c\", bpo::value<vector<string>>()->composing(),\n          \"Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.\")\n         (\"rpc-endpoint\", bpo::value<string>()->implicit_value(\"127.0.0.1:8090\"),\n          \"Endpoint for websocket RPC to listen on\")\n         (\"rpc-tls-endpoint\", bpo::value<string>()->implicit_value(\"127.0.0.1:8089\"),\n          \"Endpoint for TLS websocket RPC to listen on\")\n         (\"server-pem,p\", bpo::value<string>()->implicit_value(\"server.pem\"),\n          \"The TLS certificate file for this server\")\n         (\"server-pem-password,P\", bpo::value<string>()->implicit_value(\"\"), \"Password for this certificate\")\n         (\"proxy-forwarded-for-header\", bpo::value<string>()->implicit_value(\"X-Forwarded-For-Client\"),\n          \"A HTTP header similar to X-Forwarded-For (XFF), used by the RPC server to extract clients' address info, \"\n          \"usually added by a trusted reverse proxy\")\n         (\"genesis-json\", bpo::value<boost::filesystem::path>(), \"File to read Genesis State from\")\n         (\"dbg-init-key\", bpo::value<string>(),\n          \"Block signing key to use for init witnesses, overrides genesis file, for debug\")\n         (\"api-node-info\", bpo::value<string>(),\n          \"A string defined by the node operator, which can be retrieved via the login_api::get_info API\")\n         (\"api-access\", bpo::value<boost::filesystem::path>(), \"JSON file specifying API permissions\")\n         (\"io-threads\", bpo::value<uint16_t>()->implicit_value(0),\n          \"Number of IO threads, default to 0 for auto-configuration\")\n         (\"enable-subscribe-to-all\", bpo::value<bool>()->implicit_value(true),\n          \"Whether allow API clients to subscribe to universal object creation and removal events\")\n         (\"enable-standby-votes-tracking\", bpo::value<bool>()->implicit_value(true),\n          \"Whether to enable tracking of votes of standby witnesses and committee members. \"\n          \"Set it to true to provide accurate data to API clients, set to false for slightly better performance.\")\n         (\"api-limit-get-account-history-operations\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_account_history_operations),\n          \"For history_api::get_account_history_operations to set max limit value\")\n         (\"api-limit-get-account-history\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_account_history),\n          \"For history_api::get_account_history to set max limit value\")\n         (\"api-limit-get-grouped-limit-orders\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_grouped_limit_orders),\n          \"For orders_api::get_grouped_limit_orders to set max limit value\")\n         (\"api-limit-get-market-history\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_market_history),\n          \"Maximum number of records to return for the history_api::get_market_history API\")\n         (\"api-limit-get-relative-account-history\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_relative_account_history),\n          \"For history_api::get_relative_account_history to set max limit value\")\n         (\"api-limit-get-account-history-by-operations\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_account_history_by_operations),\n          \"For history_api::get_account_history_by_operations to set max limit value\")\n         (\"api-limit-get-asset-holders\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_asset_holders),\n          \"For asset_api::get_asset_holders to set max limit value\")\n         (\"api-limit-get-key-references\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_key_references),\n          \"For database_api_impl::get_key_references to set max limit value\")\n         (\"api-limit-get-htlc-by\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_htlc_by),\n          \"For database_api_impl::get_htlc_by_from and get_htlc_by_to to set max limit value\")\n         (\"api-limit-get-full-accounts\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_full_accounts),\n          \"For database_api_impl::get_full_accounts to set max accounts to query at once\")\n         (\"api-limit-get-full-accounts-lists\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_full_accounts_lists),\n          \"For database_api_impl::get_full_accounts to set max items to return in the lists\")\n         (\"api-limit-get-full-accounts-subscribe\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_full_accounts_subscribe),\n          \"Maximum number of accounts allowed to subscribe per connection with the get_full_accounts API\")\n         (\"api-limit-get-top-voters\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_top_voters),\n          \"For database_api_impl::get_top_voters to set max limit value\")\n         (\"api-limit-get-call-orders\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_call_orders),\n          \"For database_api_impl::get_call_orders and get_call_orders_by_account to set max limit value\")\n         (\"api-limit-get-settle-orders\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_settle_orders),\n          \"For database_api_impl::get_settle_orders and get_settle_orders_by_account to set max limit value\")\n         (\"api-limit-get-assets\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_assets),\n          \"For database_api_impl::list_assets and get_assets_by_issuer to set max limit value\")\n         (\"api-limit-get-limit-orders\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_limit_orders),\n          \"For database_api_impl::get_limit_orders to set max limit value\")\n         (\"api-limit-get-limit-orders-by-account\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_limit_orders_by_account),\n          \"For database_api_impl::get_limit_orders_by_account to set max limit value\")\n         (\"api-limit-get-order-book\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_order_book),\n          \"For database_api_impl::get_order_book to set max limit value\")\n         (\"api-limit-list-htlcs\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_list_htlcs),\n          \"For database_api_impl::list_htlcs to set max limit value\")\n         (\"api-limit-lookup-accounts\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_lookup_accounts),\n          \"For database_api_impl::lookup_accounts to set max limit value\")\n         (\"api-limit-lookup-witness-accounts\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_lookup_witness_accounts),\n          \"For database_api_impl::lookup_witness_accounts to set max limit value\")\n         (\"api-limit-lookup-committee-member-accounts\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_lookup_committee_member_accounts),\n          \"For database_api_impl::lookup_committee_member_accounts to set max limit value\")\n         (\"api-limit-lookup-vote-ids\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_lookup_vote_ids),\n          \"For database_api_impl::lookup_vote_ids to set max limit value\")\n         (\"api-limit-get-account-limit-orders\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_account_limit_orders),\n          \"For database_api_impl::get_account_limit_orders to set max limit value\")\n         (\"api-limit-get-collateral-bids\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_collateral_bids),\n          \"For database_api_impl::get_collateral_bids to set max limit value\")\n         (\"api-limit-get-top-markets\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_top_markets),\n          \"For database_api_impl::get_top_markets to set max limit value\")\n         (\"api-limit-get-trade-history\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_trade_history),\n          \"For database_api_impl::get_trade_history to set max limit value\")\n         (\"api-limit-get-trade-history-by-sequence\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_trade_history_by_sequence),\n          \"For database_api_impl::get_trade_history_by_sequence to set max limit value\")\n         (\"api-limit-get-withdraw-permissions-by-giver\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_withdraw_permissions_by_giver),\n          \"For database_api_impl::get_withdraw_permissions_by_giver to set max limit value\")\n         (\"api-limit-get-withdraw-permissions-by-recipient\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_withdraw_permissions_by_recipient),\n          \"For database_api_impl::get_withdraw_permissions_by_recipient to set max limit value\")\n         (\"api-limit-get-tickets\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_tickets),\n          \"Set maximum limit value for database APIs which query for tickets\")\n         (\"api-limit-get-liquidity-pools\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_liquidity_pools),\n          \"Set maximum limit value for database APIs which query for liquidity pools\")\n         (\"api-limit-get-liquidity-pool-history\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_liquidity_pool_history),\n          \"Set maximum limit value for APIs which query for history of liquidity pools\")\n         (\"api-limit-get-samet-funds\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_samet_funds),\n          \"Set maximum limit value for database APIs which query for SameT Funds\")\n         (\"api-limit-get-credit-offers\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_credit_offers),\n          \"Set maximum limit value for database APIs which query for credit offers or credit deals\")\n         (\"api-limit-get-storage-info\",\n          bpo::value<uint32_t>()->default_value(default_opts.api_limit_get_storage_info),\n          \"Set maximum limit value for APIs which query for account storage info\")\n         ;\n   command_line_options.add(configuration_file_options);\n   command_line_options.add_options()\n         (\"replay-blockchain\", \"Rebuild object graph by replaying all blocks without validation\")\n         (\"revalidate-blockchain\", \"Rebuild object graph by replaying all blocks with full validation\")\n         (\"resync-blockchain\", \"Delete all blocks and re-sync with network from scratch\")\n         (\"force-validate\", \"Force validation of all transactions during normal operation\")\n         (\"genesis-timestamp\", bpo::value<uint32_t>(),\n          \"Replace timestamp from genesis.json with current time plus this many seconds (experts only!)\")\n         ;\n   command_line_options.add(_cli_options);\n   configuration_file_options.add(_cfg_options);\n}\n\nvoid application::initialize(const fc::path& data_dir,\n                             std::shared_ptr<boost::program_options::variables_map> options) const\n{\n   ilog( \"Initializing application\" );\n   my->initialize( data_dir, options );\n   ilog( \"Done initializing application\" );\n}\n\nvoid application::startup()\n{\n   try {\n      ilog( \"Starting up application\" );\n      my->startup();\n      ilog( \"Done starting up application\" );\n   } catch ( const fc::exception& e ) {\n      elog( \"${e}\", (\"e\",e.to_detail_string()) );\n      throw;\n   } catch ( ... ) {\n      elog( \"unexpected exception\" );\n      throw;\n   }\n}\n\nvoid application::set_api_limit()\n{\n   try {\n      my->set_api_limit();\n   } catch ( const fc::exception& e ) {\n      elog( \"${e}\", (\"e\",e.to_detail_string()) );\n      throw;\n   } catch ( ... ) {\n      elog( \"unexpected exception\" );\n      throw;\n   }\n}\nstd::shared_ptr<abstract_plugin> application::get_plugin(const string& name) const\n{\n   return my->_active_plugins[name];\n}\n\nbool application::is_plugin_enabled(const string& name) const\n{\n   return my->is_plugin_enabled(name);\n}\n\nnet::node_ptr application::p2p_node()\n{\n   return my->_p2p_network;\n}\n\nstd::shared_ptr<chain::database> application::chain_database() const\n{\n   return my->_chain_db;\n}\n\nvoid application::set_block_production(bool producing_blocks)\n{\n   my->set_block_production(producing_blocks);\n}\n\noptional< api_access_info > application::get_api_access_info( const string& username )const\n{\n   return my->get_api_access_info( username );\n}\n\nvoid application::set_api_access_info(const string& username, api_access_info&& permissions)\n{\n   my->set_api_access_info(username, std::move(permissions));\n}\n\nbool application::is_finished_syncing() const\n{\n   return my->_is_finished_syncing;\n}\n\nvoid application::enable_plugin(const string& name) const\n{\n   my->enable_plugin(name);\n}\n\nvoid application::add_available_plugin(std::shared_ptr<graphene::app::abstract_plugin> p) const\n{\n   my->add_available_plugin(p);\n}\n\nconst application_options& application::get_options() const\n{\n   return my->_app_options;\n}\n\nconst string& application::get_node_info() const\n{\n   return my->_node_info;\n}\n\n// namespace detail\n} }\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::app::application_options )\n"
  },
  {
    "path": "libraries/app/application_impl.hxx",
    "content": "#pragma once\n\n#include <fc/network/http/websocket.hpp>\n#include <fc/thread/parallel.hpp>\n\n#include <graphene/app/application.hpp>\n#include <graphene/app/api_access.hpp>\n#include <graphene/chain/genesis_state.hpp>\n#include <graphene/protocol/types.hpp>\n#include <graphene/net/message.hpp>\n\nnamespace graphene { namespace app { namespace detail {\n\n\nclass application_impl : public net::node_delegate, public std::enable_shared_from_this<application_impl>\n   {\n   public:\n      fc::optional<fc::temp_file> _lock_file;\n      bool _is_block_producer = false;\n      bool _force_validate = false;\n      application_options _app_options;\n\n      void reset_p2p_node(const fc::path& data_dir);\n\n      void new_connection( const fc::http::websocket_connection_ptr& c );\n\n      void reset_websocket_server();\n\n      void reset_websocket_tls_server();\n\n      explicit application_impl(application& self)\n         : _self(self),\n           _chain_db(std::make_shared<chain::database>())\n      {\n      }\n\n      virtual ~application_impl();\n\n      void set_block_production(bool producing_blocks);\n\n      void set_api_limit();\n\n      void initialize(const fc::path& data_dir, std::shared_ptr<boost::program_options::variables_map> options);\n      void startup();\n\n      fc::optional< api_access_info > get_api_access_info(const string& username)const;\n\n      void set_api_access_info(const string& username, api_access_info&& permissions);\n\n      /**\n       * If delegate has the item, the network has no need to fetch it.\n       */\n      bool has_item(const net::item_id& id) override;\n\n      /**\n       * @brief allows the application to validate an item prior to broadcasting to peers.\n       *\n       * @param blk_msg the message which contains the block\n       * @param sync_mode true if the message was fetched through the sync process, false during normal operation\n       * @param contained_transaction_msg_ids container for the transactions to write back into\n       * @returns true if this message caused the blockchain to switch forks, false if it did not\n       *\n       * @throws exception if error validating the item, otherwise the item is safe to broadcast on.\n       */\n      bool handle_block(const graphene::net::block_message& blk_msg, bool sync_mode,\n                        std::vector<graphene::net::message_hash_type>& contained_transaction_msg_ids) override;\n\n      void handle_transaction(const graphene::net::trx_message& transaction_message) override;\n\n      void handle_message(const graphene::net::message& message_to_process) override;\n\n      bool is_included_block(const graphene::chain::block_id_type& block_id);\n\n      /**\n       * Assuming all data elements are ordered in some way, this method should\n       * return up to limit ids that occur *after* the last ID in synopsis that\n       * we recognize.\n       *\n       * On return, remaining_item_count will be set to the number of items\n       * in our blockchain after the last item returned in the result,\n       * or 0 if the result contains the last item in the blockchain\n       */\n      std::vector<graphene::net::item_hash_t> get_block_ids(\n            const std::vector<graphene::net::item_hash_t>& blockchain_synopsis,\n            uint32_t& remaining_item_count,\n            uint32_t limit) override;\n\n      /**\n       * Given the hash of the requested data, fetch the body.\n       */\n      graphene::net::message get_item(const graphene::net::item_id& id) override;\n\n      graphene::chain::chain_id_type get_chain_id()const override;\n\n      /**\n       * Returns a synopsis of the blockchain used for syncing.  This consists of a list of\n       * block hashes at intervals exponentially increasing towards the genesis block.\n       * When syncing to a peer, the peer uses this data to determine if we're on the same\n       * fork as they are, and if not, what blocks they need to send us to get us on their\n       * fork.\n       *\n       * In the over-simplified case, this is a straighforward synopsis of our current\n       * preferred blockchain; when we first connect up to a peer, this is what we will be sending.\n       * It looks like this:\n       *   If the blockchain is empty, it will return the empty list.\n       *   If the blockchain has one block, it will return a list containing just that block.\n       *   If it contains more than one block:\n       *     the first element in the list will be the hash of the highest numbered block that\n       *         we cannot undo\n       *     the second element will be the hash of an item at the half way point in the undoable\n       *         segment of the blockchain\n       *     the third will be ~3/4 of the way through the undoable segment of the block chain\n       *     the fourth will be at ~7/8...\n       *       &c.\n       *     the last item in the list will be the hash of the most recent block on our preferred chain\n       * so if the blockchain had 26 blocks labeled a - z, the synopsis would be:\n       *    a n u x z\n       * the idea being that by sending a small (<30) number of block ids, we can summarize a huge\n       * blockchain.  The block ids are more dense near the end of the chain where because we are\n       * more likely to be almost in sync when we first connect, and forks are likely to be short.\n       * If the peer we're syncing with in our example is on a fork that started at block 'v',\n       * then they will reply to our synopsis with a list of all blocks starting from block 'u',\n       * the last block they know that we had in common.\n       *\n       * In the real code, there are several complications.\n       *\n       * First, as an optimization, we don't usually send a synopsis of the entire blockchain, we\n       * send a synopsis of only the segment of the blockchain that we have undo data for.  If their\n       * fork doesn't build off of something in our undo history, we would be unable to switch, so there's\n       * no reason to fetch the blocks.\n       *\n       * Second, when a peer replies to our initial synopsis and gives us a list of the blocks they think\n       * we are missing, they only send a chunk of a few thousand blocks at once.  After we get those\n       * block ids, we need to request more blocks by sending another synopsis (we can't just say \"send me\n       * the next 2000 ids\" because they may have switched forks themselves and they don't track what\n       * they've sent us).  For faster performance, we want to get a fairly long list of block ids first,\n       * then start downloading the blocks.\n       * The peer doesn't handle these follow-up block id requests any different from the initial request;\n       * it treats the synopsis we send as our blockchain and bases its response entirely off that.  So to\n       * get the response we want (the next chunk of block ids following the last one they sent us, or,\n       * failing that, the shortest fork off of the last list of block ids they sent), we need to construct\n       * a synopsis as if our blockchain was made up of:\n       *    1. the blocks in our block chain up to the fork point (if there is a fork) or the head block (if no fork)\n       *    2. the blocks we've already pushed from their fork (if there's a fork)\n       *    3. the block ids they've previously sent us\n       * Segment 3 is handled in the p2p code, it just tells us the number of blocks it has (in\n       * number_of_blocks_after_reference_point) so we can leave space in the synopsis for them.\n       * We're responsible for constructing the synopsis of Segments 1 and 2 from our active blockchain and\n       * fork database.  The reference_point parameter is the last block from that peer that has been\n       * successfully pushed to the blockchain, so that tells us whether the peer is on a fork or on\n       * the main chain.\n       */\n      std::vector<graphene::net::item_hash_t> get_blockchain_synopsis(\n            const graphene::net::item_hash_t& reference_point,\n            uint32_t number_of_blocks_after_reference_point) override;\n\n      /**\n       * Call this after the call to handle_message succeeds.\n       *\n       * @param item_type the type of the item we're synchronizing,\n       *                  will be the same as item passed to the sync_from() call\n       * @param item_count the number of items known to the node that haven't been sent to handle_item() yet.\n       *                   After `item_count` more calls to handle_item(), the node will be in sync\n       */\n      void sync_status(uint32_t item_type, uint32_t item_count) override;\n\n      /**\n       * Call any time the number of connected peers changes.\n       */\n      void connection_count_changed(uint32_t c) override;\n\n      uint32_t get_block_number(const graphene::net::item_hash_t& block_id) override;\n\n      /**\n       * Returns the time a block was produced (if block_id = 0, returns genesis time).\n       * If we don't know about the block, returns time_point_sec::min()\n       */\n      fc::time_point_sec get_block_time(const graphene::net::item_hash_t& block_id) override;\n\n      graphene::net::item_hash_t get_head_block_id() const override;\n\n      uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const override;\n\n      void error_encountered(const std::string& message, const fc::oexception& error) override;\n\n      uint8_t get_current_block_interval_in_seconds() const override;\n\n      /// Add an available plugin\n      void add_available_plugin( std::shared_ptr<abstract_plugin> p );\n\n      /// Enables a plugin\n      void enable_plugin(const string& name);\n\n      /// Returns whether a plugin is enabled\n      bool is_plugin_enabled(const string& name) const;\n\n   private:\n      void shutdown();\n\n      void initialize_plugins() const;\n      void startup_plugins() const;\n      void shutdown_plugins() const;\n\n      /// Initialize genesis state. Called by open_chain_database().\n      graphene::chain::genesis_state_type initialize_genesis_state() const;\n      /// Open the chain database. Called by @ref startup.\n      void open_chain_database() const;\n\n      friend class graphene::app::application;\n\n      application& _self;\n\n      fc::path _data_dir;\n      std::shared_ptr<boost::program_options::variables_map> _options;\n      api_access _apiaccess;\n\n      std::shared_ptr<graphene::chain::database>            _chain_db;\n      std::shared_ptr<graphene::net::node>                  _p2p_network;\n      std::shared_ptr<fc::http::websocket_server>      _websocket_server;\n      std::shared_ptr<fc::http::websocket_tls_server>  _websocket_tls_server;\n\n      std::map<string, std::shared_ptr<abstract_plugin>> _active_plugins;\n      std::map<string, std::shared_ptr<abstract_plugin>> _available_plugins;\n\n      bool _is_finished_syncing = false;\n\n      /// A string defined by the node operator, which can be retrieved via the login_api::get_info API\n      string _node_info;\n\n      fc::serial_valve valve;\n   };\n\n}}} // namespace graphene namespace app namespace detail\n"
  },
  {
    "path": "libraries/app/config_util.cpp",
    "content": "/*\n * Copyright (c) 2018 Lubos Ilcik, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/app/config_util.hpp>\n#include <graphene/chain/config.hpp>\n\n#include <fc/reflect/variant.hpp>\n#include <fc/string.hpp>\n#include <fc/exception/exception.hpp>\n#include <fc/log/console_appender.hpp>\n#include <fc/log/file_appender.hpp>\n#include <fc/log/logger_config.hpp>\n\n#include <boost/property_tree/ptree.hpp>\n#include <boost/property_tree/ini_parser.hpp>\n#include <boost/algorithm/string/predicate.hpp>\n#include <boost/algorithm/string/split.hpp>\n#include <boost/algorithm/string/split.hpp>\n#include <boost/algorithm/string.hpp>\n\n#include <fstream>\n\nnamespace bpo = boost::program_options;\n\nnamespace graphene { namespace app { namespace detail {\n\nclass deduplicator\n{\npublic:\n   deduplicator() : modifier(nullptr) {}\n\n   deduplicator(const boost::shared_ptr<bpo::option_description> (*mod_fn)(const boost::shared_ptr<bpo::option_description>&))\n   : modifier(mod_fn) {}\n\n   const boost::shared_ptr<bpo::option_description> next(const boost::shared_ptr<bpo::option_description>& o)\n   {\n      const std::string name = o->long_name();\n      if( seen.find( name ) != seen.end() )\n         return nullptr;\n      seen.insert(name);\n      return modifier ? modifier(o) : o;\n   }\n\nprivate:\n   boost::container::flat_set<std::string> seen;\n   const boost::shared_ptr<bpo::option_description> (*modifier)(const boost::shared_ptr<bpo::option_description>&);\n};\n\n} } } // graphene::app::detail\n\n// Currently, you can only specify the filenames and logging levels, which\n// are all most users would want to change.  At a later time, options can\n// be added to control rotation intervals, compression, and other seldom-\n// used features\nstatic void write_default_logging_config_to_stream(std::ostream& out)\n{\n   out << \"# declare an appender named \\\"stderr\\\" that writes messages to the console\\n\"\n          \"[log.console_appender.stderr]\\n\"\n          \"stream=std_error\\n\\n\"\n          \"# declare an appender named \\\"default\\\" that writes messages to default.log\\n\"\n          \"[log.file_appender.default]\\n\"\n          \"# filename can be absolute or relative to this config file\\n\"\n          \"filename=logs/default/default.log\\n\"\n          \"# Rotate log every ? minutes, if leave out default to 60\\n\"\n          \"rotation_interval=60\\n\"\n          \"# how long will logs be kept (in days), if leave out default to 1\\n\"\n          \"rotation_limit=7\\n\\n\"\n          \"# declare an appender named \\\"p2p\\\" that writes messages to p2p.log\\n\"\n          \"[log.file_appender.p2p]\\n\"\n          \"# filename can be absolute or relative to this config file\\n\"\n          \"filename=logs/p2p/p2p.log\\n\"\n          \"# Rotate log every ? minutes, if leave out default to 60\\n\"\n          \"rotation_interval=60\\n\"\n          \"# how long will logs be kept (in days), if leave out default to 1\\n\"\n          \"rotation_limit=7\\n\\n\"\n          \"# declare an appender named \\\"rpc\\\" that writes messages to rpc.log\\n\"\n          \"[log.file_appender.rpc]\\n\"\n          \"# filename can be absolute or relative to this config file\\n\"\n          \"filename=logs/rpc/rpc.log\\n\"\n          \"# Rotate log every ? minutes, if leave out default to 60\\n\"\n          \"rotation_interval=60\\n\"\n          \"# how long will logs be kept (in days), if leave out default to 1\\n\"\n          \"rotation_limit=7\\n\\n\"\n          \"# route any messages logged to the default logger to the \\\"stderr\\\" appender and\\n\"\n          \"# \\\"default\\\" appender we declared above, if they are info level or higher\\n\"\n          \"[logger.default]\\n\"\n          \"level=info\\n\"\n          \"appenders=stderr,default\\n\\n\"\n          \"# route messages sent to the \\\"p2p\\\" logger to the \\\"p2p\\\" appender declared above\\n\"\n          \"[logger.p2p]\\n\"\n          \"level=warn\\n\"\n          \"appenders=p2p\\n\\n\"\n          \"# route messages sent to the \\\"rpc\\\" logger to the \\\"rpc\\\" appender declared above\\n\"\n          \"[logger.rpc]\\n\"\n          \"level=error\\n\"\n          \"appenders=rpc\\n\\n\";\n}\n\n// logging config is too complicated to be parsed by boost::program_options,\n// so we do it by hand\nstatic fc::optional<fc::logging_config> load_logging_config_from_ini_file(const fc::path& config_ini_filename)\n{\n   try\n   {\n      fc::logging_config logging_config;\n      bool found_logging_config = false;\n\n      boost::property_tree::ptree config_ini_tree;\n      boost::property_tree::ini_parser::read_ini(config_ini_filename.preferred_string().c_str(), config_ini_tree);\n      for (const auto& section : config_ini_tree)\n      {\n         const std::string& section_name = section.first;\n         const boost::property_tree::ptree& section_tree = section.second;\n\n         const std::string console_appender_section_prefix = \"log.console_appender.\";\n         const std::string file_appender_section_prefix = \"log.file_appender.\";\n         const std::string logger_section_prefix = \"logger.\";\n\n         if (boost::starts_with(section_name, console_appender_section_prefix))\n         {\n            std::string console_appender_name = section_name.substr(console_appender_section_prefix.length());\n            std::string stream_name = section_tree.get<std::string>(\"stream\");\n\n            // construct a default console appender config here\n            // stdout/stderr will be taken from ini file, everything else hard-coded here\n            fc::console_appender::config console_appender_config;\n            console_appender_config.level_colors.emplace_back(\n            fc::console_appender::level_color(fc::log_level::debug,\n                                              fc::console_appender::color::green));\n            console_appender_config.level_colors.emplace_back(\n            fc::console_appender::level_color(fc::log_level::warn,\n                                              fc::console_appender::color::brown));\n            console_appender_config.level_colors.emplace_back(\n            fc::console_appender::level_color(fc::log_level::error,\n                                              fc::console_appender::color::cyan));\n            console_appender_config.stream = fc::variant(stream_name).as<fc::console_appender::stream::type>(GRAPHENE_MAX_NESTED_OBJECTS);\n            logging_config.appenders.push_back(fc::appender_config(console_appender_name, \"console\", fc::variant(console_appender_config, GRAPHENE_MAX_NESTED_OBJECTS)));\n            found_logging_config = true;\n         }\n         else if (boost::starts_with(section_name, file_appender_section_prefix))\n         {\n            std::string file_appender_name = section_name.substr(file_appender_section_prefix.length());\n            fc::path file_name = section_tree.get<std::string>(\"filename\");\n            if (file_name.is_relative())\n               file_name = fc::absolute(config_ini_filename).parent_path() / file_name;\n\n            int interval = section_tree.get_optional<int>(\"rotation_interval\").get_value_or(60);\n            int limit = section_tree.get_optional<int>(\"rotation_limit\").get_value_or(1);\n\n            // construct a default file appender config here\n            // filename will be taken from ini file, everything else hard-coded here\n            fc::file_appender::config file_appender_config;\n            file_appender_config.filename = file_name;\n            file_appender_config.flush = true;\n            file_appender_config.rotate = true;\n            file_appender_config.rotation_interval = fc::minutes(interval);\n            file_appender_config.rotation_limit = fc::days(limit);\n            logging_config.appenders.push_back(fc::appender_config(file_appender_name, \"file\", fc::variant(file_appender_config, GRAPHENE_MAX_NESTED_OBJECTS)));\n            found_logging_config = true;\n         }\n         else if (boost::starts_with(section_name, logger_section_prefix))\n         {\n            std::string logger_name = section_name.substr(logger_section_prefix.length());\n            std::string level_string = section_tree.get<std::string>(\"level\");\n            std::string appenders_string = section_tree.get<std::string>(\"appenders\");\n            fc::logger_config logger_config(logger_name);\n            logger_config.level = fc::variant(level_string).as<fc::log_level>(5);\n            boost::split(logger_config.appenders, appenders_string,\n                         boost::is_any_of(\" ,\"),\n                         boost::token_compress_on);\n            logging_config.loggers.push_back(logger_config);\n            found_logging_config = true;\n         }\n      }\n      if (found_logging_config)\n         return logging_config;\n      else\n         return fc::optional<fc::logging_config>();\n   }\n   FC_RETHROW_EXCEPTIONS(warn, \"\")\n}\n\nstatic const boost::shared_ptr<bpo::option_description> new_option_description( const std::string& name, const bpo::value_semantic* value, const std::string& description )\n{\n   bpo::options_description helper(\"\");\n   helper.add_options()( name.c_str(), value, description.c_str() );\n   return helper.options()[0];\n}\n\n\nstatic void load_config_file(const fc::path& config_ini_path, const bpo::options_description& cfg_options,\n                             bpo::variables_map& options )\n{\n   graphene::app::detail::deduplicator dedup;\n   bpo::options_description unique_options(\"BitShares Witness Node\");\n   for( const boost::shared_ptr<bpo::option_description>& opt : cfg_options.options() )\n   {\n      const boost::shared_ptr<bpo::option_description> od = dedup.next(opt);\n      if( !od ) continue;\n      unique_options.add( od );\n   }\n\n   // get the basic options\n   bpo::store(bpo::parse_config_file<char>(config_ini_path.preferred_string().c_str(),\n                                           unique_options, true), options);\n}\n\nstatic bool load_logging_config_file(const fc::path& config_ini_path)\n{\n   // try to get logging options from the config file.\n   try\n   {\n      fc::optional<fc::logging_config> logging_config = load_logging_config_from_ini_file(config_ini_path);\n      if (logging_config)\n      {\n         fc::configure_logging(*logging_config);\n         return true;\n      }\n   }\n   catch (const fc::exception& ex)\n   {\n      wlog(\"Error parsing logging config from logging config file ${config}, using default config\", (\"config\", config_ini_path.preferred_string()));\n   }\n   return false;\n}\n\nstatic void create_new_config_file(const fc::path& config_ini_path, const fc::path& data_dir,\n                                   const bpo::options_description& cfg_options )\n{\n   ilog(\"Writing new config file at ${path}\", (\"path\", config_ini_path));\n   if( !fc::exists(data_dir) )\n      fc::create_directories(data_dir);\n\n   auto modify_option_defaults = [](const boost::shared_ptr<bpo::option_description>& o) -> const boost::shared_ptr<bpo::option_description> {\n       const std::string& name = o->long_name();\n       if( name == \"partial-operations\" )\n          return new_option_description(name, bpo::value<bool>()->default_value(true), o->description() );\n       if( name == \"max-ops-per-account\" )\n          return new_option_description(name, bpo::value<uint64_t>()->default_value(100), o->description() );\n       return o;\n   };\n   graphene::app::detail::deduplicator dedup(modify_option_defaults);\n   std::ofstream out_cfg(config_ini_path.preferred_string());\n   std::string plugin_header_surrounding( 78, '=' );\n   for( const boost::shared_ptr<bpo::option_description>& opt : cfg_options.options() )\n   {\n      const boost::shared_ptr<bpo::option_description> od = dedup.next(opt);\n      if( !od ) continue;\n\n      if( od->long_name().find(\"plugin-cfg-header-\") == 0 ) // it's a plugin header\n      {\n         out_cfg << \"\\n\";\n         out_cfg << \"# \" << plugin_header_surrounding << \"\\n\";\n         out_cfg << \"# \" << od->description() << \"\\n\";\n         out_cfg << \"# \" << plugin_header_surrounding << \"\\n\";\n         out_cfg << \"\\n\";\n         continue;\n      }\n\n      if( !od->description().empty() )\n         out_cfg << \"# \" << od->description() << \"\\n\";\n      boost::any store;\n      if( !od->semantic()->apply_default(store) )\n         out_cfg << \"# \" << od->long_name() << \" = \\n\";\n      else {\n         auto example = od->format_parameter();\n         if( example.empty() )\n            // This is a boolean switch\n            out_cfg << od->long_name() << \" = \" << \"false\\n\";\n         else {\n            // The string is formatted \"arg (=<interesting part>)\"\n            example.erase(0, 6);\n            example.erase(example.length()-1);\n            out_cfg << od->long_name() << \" = \" << example << \"\\n\";\n         }\n      }\n      out_cfg << \"\\n\";\n   }\n\n   out_cfg << \"\\n\"\n           << \"# \" << plugin_header_surrounding << \"\\n\"\n           << \"# logging options\\n\"\n           << \"# \" << plugin_header_surrounding << \"\\n\"\n           << \"#\\n\"\n           << \"# Logging configuration is loaded from logging.ini by default.\\n\"\n           << \"# If logging.ini exists, logging configuration added in this file will be ignored.\\n\";\n   out_cfg.close();\n}\n\nstatic void create_logging_config_file(const fc::path& config_ini_path, const fc::path& data_dir)\n{\n   ilog(\"Writing new config file at ${path}\", (\"path\", config_ini_path));\n   if (!exists(data_dir))\n   {\n      create_directories(data_dir);\n   }\n\n   std::ofstream out_cfg(config_ini_path.preferred_string());\n   write_default_logging_config_to_stream(out_cfg);\n   out_cfg.close();\n}\n\nnamespace graphene { namespace app {\n\n   void load_configuration_options(const fc::path& data_dir, const bpo::options_description& cfg_options, bpo::variables_map& options)\n   {\n      const auto config_ini_path = data_dir / \"config.ini\";\n      const auto logging_ini_path = data_dir / \"logging.ini\";\n\n      if(!exists(config_ini_path) && fc::exists(logging_ini_path))\n      {\n         // this is an uncommon case\n         create_new_config_file(config_ini_path, data_dir, cfg_options);\n      }\n      else if(!exists(config_ini_path))\n      {\n         // create default config.ini and logging.ini\n         create_new_config_file(config_ini_path, data_dir, cfg_options);\n         create_logging_config_file(logging_ini_path, data_dir);\n      }\n\n      // load witness node configuration\n      load_config_file(config_ini_path, cfg_options, options);\n\n      // load logging configuration\n      if (fc::exists(logging_ini_path))\n      {\n         load_logging_config_file(logging_ini_path);\n      }\n      else\n      {\n         // this is the legacy config.ini case\n         load_logging_config_file(config_ini_path);\n      }\n   }\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/database_api.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/app/database_api.hpp>\n\n#include \"database_api_impl.hxx\"\n\n#include <graphene/app/util.hpp>\n#include <graphene/chain/get_config.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/protocol/pts_address.hpp>\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include <fc/crypto/hex.hpp>\n#include <fc/rpc/api_connection.hpp>\n\n#include <boost/range/iterator_range.hpp>\n\n#include <cctype>\n\ntemplate class fc::api<graphene::app::database_api>;\n\nnamespace graphene { namespace app {\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Constructors                                                     //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\ndatabase_api::database_api( graphene::chain::database& db, const application_options* app_options )\n: my( std::make_shared<database_api_impl>( db, app_options ) )\n{ // Nothing else to do\n}\n\ndatabase_api::~database_api() = default;\n\ndatabase_api_helper::database_api_helper( graphene::chain::database& db, const application_options* app_options )\n:_db(db), _app_options(app_options)\n{ // Nothing else to do\n}\n\ndatabase_api_helper::database_api_helper( const graphene::app::application& app )\n:_db( *app.chain_database() ), _app_options( &app.get_options() )\n{ // Nothing else to do\n}\n\ndatabase_api_impl::database_api_impl( graphene::chain::database& db, const application_options* app_options )\n:database_api_helper( db, app_options )\n{\n   dlog(\"creating database api ${x}\", (\"x\",int64_t(this)) );\n   _new_connection = _db.new_objects.connect([this](const vector<object_id_type>& ids,\n                                                    const flat_set<account_id_type>& impacted_accounts) {\n                                on_objects_new(ids, impacted_accounts);\n                                });\n   _change_connection = _db.changed_objects.connect([this](const vector<object_id_type>& ids,\n                                                           const flat_set<account_id_type>& impacted_accounts) {\n                                on_objects_changed(ids, impacted_accounts);\n                                });\n   _removed_connection = _db.removed_objects.connect([this](const vector<object_id_type>& ids,\n                                                            const vector<const object*>& objs,\n                                                            const flat_set<account_id_type>& impacted_accounts) {\n                                on_objects_removed(ids, objs, impacted_accounts);\n                                });\n   _applied_block_connection = _db.applied_block.connect([this](const signed_block&){ on_applied_block(); });\n\n   _pending_trx_connection = _db.on_pending_transaction.connect([this](const signed_transaction& trx ){\n                                if( _pending_trx_callback )\n                                   _pending_trx_callback( fc::variant(trx, GRAPHENE_MAX_NESTED_OBJECTS) );\n                      });\n   try\n   {\n      amount_in_collateral_index = &_db.get_index_type< primary_index< call_order_index > >()\n                                    .get_secondary_index<graphene::api_helper_indexes::amount_in_collateral_index>();\n   }\n   catch( const fc::assert_exception& )\n   {\n      amount_in_collateral_index = nullptr;\n   }\n\n   try\n   {\n      asset_in_liquidity_pools_index = &_db.get_index_type< primary_index< liquidity_pool_index > >()\n            .get_secondary_index<graphene::api_helper_indexes::asset_in_liquidity_pools_index>();\n   }\n   catch( const fc::assert_exception& )\n   {\n      asset_in_liquidity_pools_index = nullptr;\n   }\n\n   try\n   {\n      next_object_ids_index = &_db.get_index_type< primary_index< simple_index< chain_property_object > > >()\n                                    .get_secondary_index<graphene::api_helper_indexes::next_object_ids_index>();\n   }\n   catch( const fc::assert_exception& )\n   {\n      next_object_ids_index = nullptr;\n   }\n\n}\n\ndatabase_api_impl::~database_api_impl()\n{\n   dlog(\"freeing database api ${x}\", (\"x\",int64_t(this)) );\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Objects                                                          //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nfc::variants database_api::get_objects( const vector<object_id_type>& ids, optional<bool> subscribe )const\n{\n   return my->get_objects( ids, subscribe );\n}\n\nfc::variants database_api_impl::get_objects( const vector<object_id_type>& ids, optional<bool> subscribe )const\n{\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n\n   fc::variants result;\n   result.reserve(ids.size());\n\n   std::transform(ids.begin(), ids.end(), std::back_inserter(result),\n                  [this,to_subscribe](object_id_type id) -> fc::variant {\n      if(auto obj = _db.find_object(id))\n      {\n         if( to_subscribe && !id.is<operation_history_id_type>() && !id.is<account_history_id_type>() )\n            this->subscribe_to_item( id );\n         return obj->to_variant();\n      }\n      return {};\n   });\n\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Subscriptions                                                    //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvoid database_api::set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create )\n{\n   my->set_subscribe_callback( cb, notify_remove_create );\n}\n\nvoid database_api_impl::set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create )\n{\n   if( notify_remove_create )\n   {\n      FC_ASSERT( _app_options && _app_options->enable_subscribe_to_all,\n                 \"Subscribing to universal object creation and removal is disallowed in this server.\" );\n   }\n\n   cancel_all_subscriptions(false, false);\n\n   _subscribe_callback = cb;\n   _notify_remove_create = notify_remove_create;\n}\n\nvoid database_api::set_auto_subscription( bool enable )\n{\n   my->set_auto_subscription( enable );\n}\n\nvoid database_api_impl::set_auto_subscription( bool enable )\n{\n   _enabled_auto_subscription = enable;\n}\n\nvoid database_api::set_pending_transaction_callback( std::function<void(const variant&)> cb )\n{\n   my->set_pending_transaction_callback( cb );\n}\n\nvoid database_api_impl::set_pending_transaction_callback( std::function<void(const variant&)> cb )\n{\n   _pending_trx_callback = cb;\n}\n\nvoid database_api::set_block_applied_callback( std::function<void(const variant& block_id)> cb )\n{\n   my->set_block_applied_callback( cb );\n}\n\nvoid database_api_impl::set_block_applied_callback( std::function<void(const variant& block_id)> cb )\n{\n   _block_applied_callback = cb;\n}\n\nvoid database_api::cancel_all_subscriptions()\n{\n   my->cancel_all_subscriptions(true, true);\n}\n\nvoid database_api_impl::cancel_all_subscriptions( bool reset_callback, bool reset_market_subscriptions )\n{\n   if ( reset_callback )\n      _subscribe_callback = std::function<void(const fc::variant&)>();\n\n   if ( reset_market_subscriptions )\n      _market_subscriptions.clear();\n\n   _notify_remove_create = false;\n   _subscribed_accounts.clear();\n   static fc::bloom_parameters param(10000, 1.0/100, 1024*8*8*2);\n   _subscribe_filter = fc::bloom_filter(param);\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Blocks and transactions                                          //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\noptional<maybe_signed_block_header> database_api::get_block_header(\n            uint32_t block_num, const optional<bool>& with_witness_signature )const\n{\n   bool with_signature = ( with_witness_signature.valid() && *with_witness_signature );\n   return my->get_block_header( block_num, with_signature );\n}\n\noptional<maybe_signed_block_header> database_api_impl::get_block_header(\n            uint32_t block_num, bool with_witness_signature )const\n{\n   auto result = _db.fetch_block_by_number(block_num);\n   if(result)\n      return maybe_signed_block_header( *result, with_witness_signature );\n   return {};\n}\n\nmap<uint32_t, optional<maybe_signed_block_header>> database_api::get_block_header_batch(\n            const vector<uint32_t>& block_nums, const optional<bool>& with_witness_signatures )const\n{\n   bool with_signatures = ( with_witness_signatures.valid() && *with_witness_signatures );\n   return my->get_block_header_batch( block_nums, with_signatures );\n}\n\nmap<uint32_t, optional<maybe_signed_block_header>> database_api_impl::get_block_header_batch(\n            const vector<uint32_t>& block_nums, bool with_witness_signatures )const\n{\n   map<uint32_t, optional<maybe_signed_block_header>> results;\n   for (const uint32_t block_num : block_nums)\n   {\n      results[block_num] = get_block_header( block_num, with_witness_signatures );\n   }\n   return results;\n}\n\noptional<signed_block> database_api::get_block(uint32_t block_num)const\n{\n   return my->get_block( block_num );\n}\n\noptional<signed_block> database_api_impl::get_block(uint32_t block_num)const\n{\n   return _db.fetch_block_by_number(block_num);\n}\n\nprocessed_transaction database_api::get_transaction( uint32_t block_num, uint32_t trx_in_block )const\n{\n   return my->get_transaction( block_num, trx_in_block );\n}\n\noptional<signed_transaction> database_api::get_recent_transaction_by_id( const transaction_id_type& id )const\n{\n   try {\n      return my->_db.get_recent_transaction( id );\n   } catch ( ... ) {\n      return optional<signed_transaction>();\n   }\n}\n\nprocessed_transaction database_api_impl::get_transaction(uint32_t block_num, uint32_t trx_num)const\n{\n   auto opt_block = _db.fetch_block_by_number(block_num);\n   FC_ASSERT( opt_block );\n   FC_ASSERT( opt_block->transactions.size() > trx_num );\n   return opt_block->transactions[trx_num];\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Globals                                                          //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nchain_property_object database_api::get_chain_properties()const\n{\n   return my->get_chain_properties();\n}\n\nchain_property_object database_api_impl::get_chain_properties()const\n{\n   return _db.get(chain_property_id_type());\n}\n\nglobal_property_object database_api::get_global_properties()const\n{\n   return my->get_global_properties();\n}\n\nglobal_property_object database_api_impl::get_global_properties()const\n{\n   return _db.get(global_property_id_type());\n}\n\nfc::variant_object database_api::get_config()const\n{\n   return my->get_config();\n}\n\nfc::variant_object database_api_impl::get_config()const\n{\n   return graphene::chain::get_config();\n}\n\nchain_id_type database_api::get_chain_id()const\n{\n   return my->get_chain_id();\n}\n\nchain_id_type database_api_impl::get_chain_id()const\n{\n   return _db.get_chain_id();\n}\n\ndynamic_global_property_object database_api::get_dynamic_global_properties()const\n{\n   return my->get_dynamic_global_properties();\n}\n\ndynamic_global_property_object database_api_impl::get_dynamic_global_properties()const\n{\n   return _db.get(dynamic_global_property_id_type());\n}\n\nobject_id_type database_api::get_next_object_id( uint8_t space_id, uint8_t type_id,\n                                                 bool with_pending_transactions )const\n{\n   return my->get_next_object_id( space_id, type_id, with_pending_transactions );\n}\n\nobject_id_type database_api_impl::get_next_object_id( uint8_t space_id, uint8_t type_id,\n                                                      bool with_pending_transactions )const\n{\n   if( with_pending_transactions )\n      return _db.get_index( space_id, type_id ).get_next_id();\n\n   FC_ASSERT( next_object_ids_index, \"api_helper_indexes plugin is not enabled on this server.\" );\n\n   return next_object_ids_index->get_next_id( space_id, type_id );\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Keys                                                             //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<flat_set<account_id_type>> database_api::get_key_references( vector<public_key_type> key )const\n{\n   return my->get_key_references( key );\n}\n\n/**\n *  @return all accounts that referr to the key or account id in their owner or active authorities.\n */\nvector<flat_set<account_id_type>> database_api_impl::get_key_references( vector<public_key_type> keys )const\n{\n   // api_helper_indexes plugin is required for accessing the secondary index\n   FC_ASSERT( _app_options && _app_options->has_api_helper_indexes_plugin,\n              \"api_helper_indexes plugin is not enabled on this server.\" );\n\n   const auto configured_limit = _app_options->api_limit_get_key_references;\n   FC_ASSERT( keys.size() <= configured_limit,\n              \"Number of querying keys can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& idx = _db.get_index_type<account_index>();\n   const auto& aidx = dynamic_cast<const base_primary_index&>(idx);\n   const auto& refs = aidx.get_secondary_index<graphene::chain::account_member_index>();\n\n   vector< flat_set<account_id_type> > final_result;\n   final_result.reserve(keys.size());\n\n   for( auto& key : keys )\n   {\n      address a1( pts_address(key, false) ); // version = 56 (default)\n      address a2( pts_address(key, true) ); // version = 56 (default)\n      address a3( pts_address(key, false, 0)  );\n      address a4( pts_address(key, true, 0)  );\n      address a5( key );\n\n      flat_set<account_id_type> result;\n\n      for( auto& a : {a1,a2,a3,a4,a5} )\n      {\n          auto itr = refs.account_to_address_memberships.find(a);\n          if( itr != refs.account_to_address_memberships.end() )\n          {\n             result.reserve( result.size() + itr->second.size() );\n             for( auto item : itr->second )\n             {\n                result.insert(item);\n             }\n          }\n      }\n\n      auto itr = refs.account_to_key_memberships.find(key);\n      if( itr != refs.account_to_key_memberships.end() )\n      {\n         result.reserve( result.size() + itr->second.size() );\n         for( auto item : itr->second ) result.insert(item);\n      }\n      final_result.emplace_back( std::move(result) );\n   }\n\n   return final_result;\n}\n\nbool database_api::is_public_key_registered(string public_key) const\n{\n    return my->is_public_key_registered(public_key);\n}\n\nbool database_api_impl::is_public_key_registered(string public_key) const\n{\n    // Short-circuit\n    if (public_key.empty()) {\n        return false;\n    }\n\n    // Search among all keys using an existing map of *current* account keys\n    public_key_type key;\n    try {\n        key = public_key_type(public_key);\n    } catch ( ... ) {\n        // An invalid public key was detected\n        return false;\n    }\n\n   // api_helper_indexes plugin is required for accessing the secondary index\n   FC_ASSERT( _app_options && _app_options->has_api_helper_indexes_plugin,\n              \"api_helper_indexes plugin is not enabled on this server.\" );\n\n    const auto& idx = _db.get_index_type<account_index>();\n    const auto& aidx = dynamic_cast<const base_primary_index&>(idx);\n    const auto& refs = aidx.get_secondary_index<graphene::chain::account_member_index>();\n    auto itr = refs.account_to_key_memberships.find(key);\n    bool is_known = itr != refs.account_to_key_memberships.end();\n\n    return is_known;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Accounts                                                         //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\naccount_id_type database_api::get_account_id_from_string(const std::string& name_or_id)const\n{\n   return my->get_account_from_string( name_or_id )->get_id();\n}\n\nvector<optional<account_object>> database_api::get_accounts( const vector<std::string>& account_names_or_ids,\n                                                             optional<bool> subscribe )const\n{\n   return my->get_accounts( account_names_or_ids, subscribe );\n}\n\nvector<optional<account_object>> database_api_impl::get_accounts( const vector<std::string>& account_names_or_ids,\n                                                                  optional<bool> subscribe )const\n{\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n   vector<optional<account_object>> result; result.reserve(account_names_or_ids.size());\n   std::transform(account_names_or_ids.begin(), account_names_or_ids.end(), std::back_inserter(result),\n                  [this,to_subscribe](std::string id_or_name) -> optional<account_object> {\n\n      const account_object *account = get_account_from_string(id_or_name, false);\n      if(account == nullptr)\n         return {};\n      if( to_subscribe )\n         subscribe_to_item( account->id );\n      return *account;\n   });\n   return result;\n}\n\nstd::map<string, full_account, std::less<>> database_api::get_full_accounts( const vector<string>& names_or_ids,\n                                                                             const optional<bool>& subscribe )const\n{\n   return my->get_full_accounts( names_or_ids, subscribe );\n}\n\nstd::map<std::string, full_account, std::less<>> database_api_impl::get_full_accounts(\n      const vector<std::string>& names_or_ids, const optional<bool>& subscribe )\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_full_accounts;\n   FC_ASSERT( names_or_ids.size() <= configured_limit,\n              \"Number of querying accounts can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n\n   std::map<std::string, full_account, std::less<>> results;\n\n   for (const std::string& account_name_or_id : names_or_ids)\n   {\n      const account_object* account = get_account_from_string(account_name_or_id, false);\n      if( !account )\n         continue;\n\n      if( to_subscribe && _subscribed_accounts.size() < _app_options->api_limit_get_full_accounts_subscribe )\n      {\n         _subscribed_accounts.insert( account->get_id() );\n         subscribe_to_item( account->id );\n      }\n\n      full_account acnt;\n      acnt.account = *account;\n      acnt.statistics = account->statistics(_db);\n      acnt.registrar_name = account->registrar(_db).name;\n      acnt.referrer_name = account->referrer(_db).name;\n      acnt.lifetime_referrer_name = account->lifetime_referrer(_db).name;\n      acnt.votes = lookup_vote_ids( vector<vote_id_type>( account->options.votes.begin(),\n                                                          account->options.votes.end() ) );\n\n      if (account->cashback_vb)\n      {\n         acnt.cashback_balance = account->cashback_balance(_db);\n      }\n\n      size_t api_limit_get_full_accounts_lists = static_cast<size_t>(\n                _app_options->api_limit_get_full_accounts_lists );\n\n      // Add the account's proposals (if the data is available)\n      if( _app_options && _app_options->has_api_helper_indexes_plugin )\n      {\n         const auto& proposal_idx = _db.get_index_type< primary_index< proposal_index > >();\n         const auto& proposals_by_account = proposal_idx.get_secondary_index<\n                                                  graphene::chain::required_approval_index>();\n\n         auto required_approvals_itr = proposals_by_account._account_to_proposals.find( account->get_id() );\n         if( required_approvals_itr != proposals_by_account._account_to_proposals.end() )\n         {\n            acnt.proposals.reserve( std::min(required_approvals_itr->second.size(),\n                                             api_limit_get_full_accounts_lists) );\n            for( auto proposal_id : required_approvals_itr->second )\n            {\n               if(acnt.proposals.size() >= api_limit_get_full_accounts_lists) {\n                  acnt.more_data_available.proposals = true;\n                  break;\n               }\n               acnt.proposals.push_back(proposal_id(_db));\n            }\n         }\n      }\n\n      // Add the account's balances\n      const auto& balances = _db.get_index_type< primary_index< account_balance_index > >().\n            get_secondary_index< balances_by_account_index >().get_account_balances( account->get_id() );\n      for( const auto& balance : balances )\n      {\n         if(acnt.balances.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.balances = true;\n            break;\n         }\n         acnt.balances.emplace_back(*balance.second);\n      }\n\n      // Add the account's vesting balances\n      auto vesting_range = _db.get_index_type<vesting_balance_index>().indices().get<by_account>()\n                              .equal_range(account->get_id());\n      for(auto itr = vesting_range.first; itr != vesting_range.second; ++itr)\n      {\n         if(acnt.vesting_balances.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.vesting_balances = true;\n            break;\n         }\n         acnt.vesting_balances.emplace_back(*itr);\n      }\n\n      // Add the account's orders\n      auto order_range = _db.get_index_type<limit_order_index>().indices().get<by_account>()\n                            .equal_range(account->get_id());\n      for(auto itr = order_range.first; itr != order_range.second; ++itr)\n      {\n         if(acnt.limit_orders.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.limit_orders = true;\n            break;\n         }\n         acnt.limit_orders.emplace_back(*itr);\n      }\n      auto call_range = _db.get_index_type<call_order_index>().indices().get<by_account>()\n                           .equal_range(account->get_id());\n      for(auto itr = call_range.first; itr != call_range.second; ++itr)\n      {\n         if(acnt.call_orders.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.call_orders = true;\n            break;\n         }\n         acnt.call_orders.emplace_back(*itr);\n      }\n      auto settle_range = _db.get_index_type<force_settlement_index>().indices().get<by_account>()\n                             .equal_range(account->get_id());\n      for(auto itr = settle_range.first; itr != settle_range.second; ++itr)\n      {\n         if(acnt.settle_orders.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.settle_orders = true;\n            break;\n         }\n         acnt.settle_orders.emplace_back(*itr);\n      }\n\n      // get assets issued by user\n      auto asset_range = _db.get_index_type<asset_index>().indices().get<by_issuer>().equal_range(account->get_id());\n      for(auto itr = asset_range.first; itr != asset_range.second; ++itr)\n      {\n         if(acnt.assets.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.assets = true;\n            break;\n         }\n         acnt.assets.emplace_back(itr->get_id());\n      }\n\n      // get withdraws permissions\n      auto withdraw_indices = _db.get_index_type<withdraw_permission_index>().indices();\n      auto withdraw_from_range = withdraw_indices.get<by_from>().equal_range(account->get_id());\n      for(auto itr = withdraw_from_range.first; itr != withdraw_from_range.second; ++itr)\n      {\n         if(acnt.withdraws_from.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.withdraws_from = true;\n            break;\n         }\n         acnt.withdraws_from.emplace_back(*itr);\n      }\n      auto withdraw_authorized_range = withdraw_indices.get<by_authorized>().equal_range(account->get_id());\n      for(auto itr = withdraw_authorized_range.first; itr != withdraw_authorized_range.second; ++itr)\n      {\n         if(acnt.withdraws_to.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.withdraws_to = true;\n            break;\n         }\n         acnt.withdraws_to.emplace_back(*itr);\n      }\n\n      // get htlcs\n      auto htlc_from_range = _db.get_index_type<htlc_index>().indices().get<by_from_id>()\n                                .equal_range(account->get_id());\n      for(auto itr = htlc_from_range.first; itr != htlc_from_range.second; ++itr)\n      {\n         if(acnt.htlcs_from.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.htlcs_from = true;\n            break;\n         }\n         acnt.htlcs_from.emplace_back(*itr);\n      }\n      auto htlc_to_range = _db.get_index_type<htlc_index>().indices().get<by_to_id>().equal_range(account->get_id());\n      for(auto itr = htlc_to_range.first; itr != htlc_to_range.second; ++itr)\n      {\n         if(acnt.htlcs_to.size() >= api_limit_get_full_accounts_lists) {\n            acnt.more_data_available.htlcs_to = true;\n            break;\n         }\n         acnt.htlcs_to.emplace_back(*itr);\n      }\n\n      results[account_name_or_id] = acnt;\n   }\n   return results;\n}\n\nvector<account_statistics_object> database_api::get_top_voters(uint32_t limit)const\n{\n   return my->get_top_voters( limit );\n}\n\nvector<account_statistics_object> database_api_impl::get_top_voters(uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_top_voters;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<account_statistics_object> result;\n\n   auto last_vote_tally_time = _db.get_dynamic_global_properties().last_vote_tally_time;\n   const auto& idx = _db.get_index_type<account_stats_index>().indices().get<by_voting_power_active>();\n\n   for(auto itr = idx.begin(); result.size() < limit && itr != idx.end() && itr->vote_tally_time >= last_vote_tally_time; ++itr)\n   {\n      result.emplace_back(*itr);\n   }\n\n   return result;\n}\n\noptional<account_object> database_api::get_account_by_name( string name )const\n{\n   return my->get_account_by_name( name );\n}\n\noptional<account_object> database_api_impl::get_account_by_name( string name )const\n{\n   const auto& idx = _db.get_index_type<account_index>().indices().get<by_name>();\n   auto itr = idx.find(name);\n   if (itr != idx.end())\n      return *itr;\n   return optional<account_object>();\n}\n\nvector<account_id_type> database_api::get_account_references( const std::string account_id_or_name )const\n{\n   return my->get_account_references( account_id_or_name );\n}\n\nvector<account_id_type> database_api_impl::get_account_references( const std::string account_id_or_name )const\n{\n   // api_helper_indexes plugin is required for accessing the secondary index\n   FC_ASSERT( _app_options && _app_options->has_api_helper_indexes_plugin,\n              \"api_helper_indexes plugin is not enabled on this server.\" );\n\n   const auto& idx = _db.get_index_type<account_index>();\n   const auto& aidx = dynamic_cast<const base_primary_index&>(idx);\n   const auto& refs = aidx.get_secondary_index<graphene::chain::account_member_index>();\n   const account_id_type account_id = get_account_from_string(account_id_or_name)->get_id();\n   auto itr = refs.account_to_account_memberships.find(account_id);\n   vector<account_id_type> result;\n\n   if( itr != refs.account_to_account_memberships.end() )\n   {\n      result.reserve( itr->second.size() );\n      for( auto item : itr->second ) result.push_back(item);\n   }\n   return result;\n}\n\nvector<optional<account_object>> database_api::lookup_account_names(const vector<string>& account_names)const\n{\n   return my->lookup_account_names( account_names );\n}\n\nvector<optional<account_object>> database_api_impl::lookup_account_names(const vector<string>& account_names)const\n{\n   return get_accounts( account_names, false );\n}\n\nmap<string, account_id_type, std::less<>> database_api::lookup_accounts( const string& lower_bound_name,\n                                                           uint32_t limit,\n                                                           const optional<bool>& subscribe )const\n{\n   return my->lookup_accounts( lower_bound_name, limit, subscribe );\n}\n\nmap<string, account_id_type, std::less<>> database_api_impl::lookup_accounts( const string& lower_bound_name,\n                                                                uint32_t limit,\n                                                                const optional<bool>& subscribe )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_lookup_accounts;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& accounts_by_name = _db.get_index_type<account_index>().indices().get<by_name>();\n   map<string, account_id_type, std::less<>> result;\n\n   if( limit == 0 ) // shortcut to save a database query\n      return result;\n   // In addition to the common auto-subscription rules, here we auto-subscribe if only look for one account\n   bool to_subscribe = (limit == 1 && get_whether_to_subscribe( subscribe ));\n   for( auto itr = accounts_by_name.lower_bound(lower_bound_name);\n        limit > 0 && itr != accounts_by_name.end();\n        ++itr, --limit )\n   {\n      result.insert(make_pair(itr->name, itr->get_id()));\n      if( to_subscribe )\n         subscribe_to_item( itr->id );\n   }\n\n   return result;\n}\n\nuint64_t database_api::get_account_count()const\n{\n   return my->get_account_count();\n}\n\nuint64_t database_api_impl::get_account_count()const\n{\n   return _db.get_index_type<account_index>().indices().size();\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Balances                                                         //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<asset> database_api::get_account_balances( const std::string& account_name_or_id,\n                                                  const flat_set<asset_id_type>& assets )const\n{\n   return my->get_account_balances( account_name_or_id, assets );\n}\n\nvector<asset> database_api_impl::get_account_balances( const std::string& account_name_or_id,\n                                                       const flat_set<asset_id_type>& assets )const\n{\n   const account_object* account = get_account_from_string(account_name_or_id);\n   account_id_type acnt = account->get_id();\n   vector<asset> result;\n   if (assets.empty())\n   {\n      // if the caller passes in an empty list of assets, return balances for all assets the account owns\n      const auto& balance_index = _db.get_index_type< primary_index< account_balance_index > >();\n      const auto& balances = balance_index.get_secondary_index< balances_by_account_index >()\n                                          .get_account_balances( acnt );\n      for( const auto& balance : balances )\n         result.push_back( balance.second->get_balance() );\n   }\n   else\n   {\n      result.reserve(assets.size());\n\n      std::transform(assets.begin(), assets.end(), std::back_inserter(result),\n                     [this, acnt](asset_id_type id) { return _db.get_balance(acnt, id); });\n   }\n\n   return result;\n}\n\nvector<asset> database_api::get_named_account_balances( const std::string& name,\n                                                        const flat_set<asset_id_type>& assets )const\n{\n   return my->get_account_balances( name, assets );\n}\n\nvector<balance_object> database_api::get_balance_objects( const vector<address>& addrs )const\n{\n   return my->get_balance_objects( addrs );\n}\n\nvector<balance_object> database_api_impl::get_balance_objects( const vector<address>& addrs )const\n{\n   try\n   {\n      const auto& bal_idx = _db.get_index_type<balance_index>();\n      const auto& by_owner_idx = bal_idx.indices().get<by_owner>();\n\n      vector<balance_object> result;\n\n      for( const auto& owner : addrs )\n      {\n         auto itr = by_owner_idx.lower_bound( boost::make_tuple( owner, asset_id_type(0) ) );\n         while( itr != by_owner_idx.end() && itr->owner == owner )\n         {\n            result.push_back( *itr );\n            ++itr;\n         }\n      }\n      return result;\n   }\n   FC_CAPTURE_AND_RETHROW( (addrs) )\n}\n\nvector<asset> database_api::get_vested_balances( const vector<balance_id_type>& objs )const\n{\n   return my->get_vested_balances( objs );\n}\n\nvector<asset> database_api_impl::get_vested_balances( const vector<balance_id_type>& objs )const\n{\n   try\n   {\n      vector<asset> result;\n      result.reserve( objs.size() );\n      auto now = _db.head_block_time();\n      for( auto obj : objs )\n         result.push_back( obj(_db).available( now ) );\n      return result;\n   } FC_CAPTURE_AND_RETHROW( (objs) )\n}\n\nvector<vesting_balance_object> database_api::get_vesting_balances( const std::string account_id_or_name )const\n{\n   return my->get_vesting_balances( account_id_or_name );\n}\n\nvector<vesting_balance_object> database_api_impl::get_vesting_balances( const std::string account_id_or_name )const\n{\n   try\n   {\n      const account_id_type account_id = get_account_from_string(account_id_or_name)->get_id();\n      vector<vesting_balance_object> result;\n      auto vesting_range = _db.get_index_type<vesting_balance_index>().indices().get<by_account>()\n                              .equal_range(account_id);\n      std::for_each(vesting_range.first, vesting_range.second,\n                    [&result](const vesting_balance_object& balance) {\n                       result.emplace_back(balance);\n                    });\n      return result;\n   }\n   FC_CAPTURE_AND_RETHROW( (account_id_or_name) );\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Assets                                                           //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nasset_id_type database_api::get_asset_id_from_string(const std::string& symbol_or_id)const\n{\n   return my->get_asset_from_string( symbol_or_id )->get_id();\n}\n\nvector<optional<extended_asset_object>> database_api::get_assets(\n      const vector<std::string>& asset_symbols_or_ids,\n      optional<bool> subscribe )const\n{\n   return my->get_assets( asset_symbols_or_ids, subscribe );\n}\n\nvector<optional<extended_asset_object>> database_api_impl::get_assets(\n      const vector<std::string>& asset_symbols_or_ids,\n      optional<bool> subscribe )const\n{\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n   vector<optional<extended_asset_object>> result; result.reserve(asset_symbols_or_ids.size());\n   std::transform(asset_symbols_or_ids.begin(), asset_symbols_or_ids.end(), std::back_inserter(result),\n                  [this,to_subscribe](std::string id_or_name) -> optional<extended_asset_object> {\n\n      const asset_object* asset_obj = get_asset_from_string( id_or_name, false );\n      if( asset_obj == nullptr )\n         return {};\n      if( to_subscribe )\n         subscribe_to_item( asset_obj->id );\n      return extend_asset( *asset_obj );\n   });\n   return result;\n}\n\nvector<extended_asset_object> database_api::list_assets(const string& lower_bound_symbol, uint32_t limit)const\n{\n   return my->list_assets( lower_bound_symbol, limit );\n}\n\nvector<extended_asset_object> database_api_impl::list_assets(const string& lower_bound_symbol, uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_assets;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& assets_by_symbol = _db.get_index_type<asset_index>().indices().get<by_symbol>();\n   vector<extended_asset_object> result;\n   result.reserve(limit);\n\n   auto itr = assets_by_symbol.lower_bound(lower_bound_symbol);\n   auto end = assets_by_symbol.end();\n   for( ; limit > 0 && itr != end; ++itr, --limit )\n      result.emplace_back( extend_asset( *itr ) );\n\n   return result;\n}\n\nuint64_t database_api::get_asset_count()const\n{\n   return my->get_asset_count();\n}\n\nuint64_t database_api_impl::get_asset_count()const\n{\n   return _db.get_index_type<asset_index>().indices().size();\n}\n\nvector<extended_asset_object> database_api::get_assets_by_issuer(const std::string& issuer_name_or_id,\n                                                                 asset_id_type start, uint32_t limit)const\n{\n   return my->get_assets_by_issuer(issuer_name_or_id, start, limit);\n}\n\nvector<extended_asset_object> database_api_impl::get_assets_by_issuer(const std::string& issuer_name_or_id,\n                                                                      asset_id_type start, uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_assets;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<extended_asset_object> result;\n   const account_id_type account = get_account_from_string(issuer_name_or_id)->get_id();\n   const auto& asset_idx = _db.get_index_type<asset_index>().indices().get<by_issuer>();\n   auto asset_index_end = asset_idx.end();\n   auto asset_itr = asset_idx.lower_bound(boost::make_tuple(account, object_id_type(start)));\n   while(asset_itr != asset_index_end && asset_itr->issuer == account && result.size() < limit)\n   {\n      result.emplace_back( extend_asset( *asset_itr ) );\n      ++asset_itr;\n   }\n   return result;\n}\n\nvector<optional<extended_asset_object>> database_api::lookup_asset_symbols(\n                                                         const vector<string>& symbols_or_ids )const\n{\n   return my->lookup_asset_symbols( symbols_or_ids );\n}\n\nvector<optional<extended_asset_object>> database_api_impl::lookup_asset_symbols(\n                                                         const vector<string>& symbols_or_ids )const\n{\n   return get_assets( symbols_or_ids, false );\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Markets / feeds                                                  //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<limit_order_object> database_api::get_limit_orders(std::string a, std::string b, uint32_t limit)const\n{\n   return my->get_limit_orders( a, b, limit );\n}\n\nvector<limit_order_object> database_api_impl::get_limit_orders( const std::string& a, const std::string& b,\n                                                                uint32_t limit )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_limit_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const asset_id_type asset_a_id = get_asset_from_string(a)->get_id();\n   const asset_id_type asset_b_id = get_asset_from_string(b)->get_id();\n\n   return get_limit_orders(asset_a_id, asset_b_id, limit);\n}\n\nvector<limit_order_object> database_api::get_limit_orders_by_account( const string& account_name_or_id,\n                              const optional<uint32_t>& limit, const optional<limit_order_id_type>& start_id )\n{\n   return my->get_limit_orders_by_account( account_name_or_id, limit, start_id );\n}\n\nvector<limit_order_object> database_api_impl::get_limit_orders_by_account( const string& account_name_or_id,\n                              const optional<uint32_t>& olimit, const optional<limit_order_id_type>& ostart_id )\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_limit_orders_by_account;\n   uint32_t limit = olimit.valid() ? *olimit : configured_limit;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<limit_order_object> results;\n\n   const account_object* account = get_account_from_string(account_name_or_id);\n   if (account == nullptr)\n      return results;\n\n   limit_order_id_type start_order_id = ostart_id.valid() ? *ostart_id : limit_order_id_type();\n   object_id_type start_id { start_order_id };\n\n   const auto& index_by_account = _db.get_index_type<limit_order_index>().indices().get<by_account>();\n   auto lower_itr = index_by_account.lower_bound( std::make_tuple( account->get_id(), start_id ) );\n   auto upper_itr = index_by_account.upper_bound( account->get_id() );\n\n   results.reserve( limit );\n   uint32_t count = 0;\n   for ( ; lower_itr != upper_itr && count < limit; ++lower_itr, ++count)\n   {\n      const limit_order_object &order = *lower_itr;\n      results.emplace_back(order);\n   }\n\n   return results;\n}\n\nvector<limit_order_object> database_api::get_account_limit_orders(\n                              const string& account_name_or_id, const string &base, const string &quote,\n                              uint32_t limit, optional<limit_order_id_type> ostart_id, optional<price> ostart_price )\n{\n   return my->get_account_limit_orders( account_name_or_id, base, quote, limit, ostart_id, ostart_price );\n}\n\nvector<limit_order_object> database_api_impl::get_account_limit_orders(\n                              const string& account_name_or_id, const string &base, const string &quote,\n                              uint32_t limit, optional<limit_order_id_type> ostart_id, optional<price> ostart_price )\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_account_limit_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<limit_order_object>   results;\n   uint32_t                     count = 0;\n\n   const account_object* account = get_account_from_string(account_name_or_id);\n   if (account == nullptr)\n      return results;\n\n   auto assets = lookup_asset_symbols( {base, quote} );\n   FC_ASSERT( assets[0], \"Invalid base asset symbol: ${s}\", (\"s\",base) );\n   FC_ASSERT( assets[1], \"Invalid quote asset symbol: ${s}\", (\"s\",quote) );\n\n   auto base_id = assets[0]->get_id();\n   auto quote_id = assets[1]->get_id();\n\n   if (ostart_price.valid()) {\n      FC_ASSERT(ostart_price->base.asset_id == base_id, \"Base asset inconsistent with start price\");\n      FC_ASSERT(ostart_price->quote.asset_id == quote_id, \"Quote asset inconsistent with start price\");\n   }\n\n   const auto& index_by_account = _db.get_index_type<limit_order_index>().indices().get<by_account_price>();\n   limit_order_multi_index_type::index<by_account_price>::type::const_iterator lower_itr;\n   limit_order_multi_index_type::index<by_account_price>::type::const_iterator upper_itr;\n\n   // if both order_id and price are invalid, query the first page\n   if ( !ostart_id.valid() && !ostart_price.valid() )\n   {\n      lower_itr = index_by_account.lower_bound(std::make_tuple(account->get_id(), price::max(base_id, quote_id)));\n   }\n   else if ( ostart_id.valid() )\n   {\n      object_id_type start_id { *ostart_id };\n      // in case of the order been deleted during page querying\n      const limit_order_object *p_loo = _db.find(*ostart_id);\n\n      if ( !p_loo )\n      {\n         if ( ostart_price.valid() )\n         {\n            lower_itr = index_by_account.lower_bound(std::make_tuple(account->get_id(), *ostart_price, start_id));\n         }\n         else\n         {\n            // start order id been deleted, yet not provided price either\n            FC_THROW(\"Order id invalid (maybe just been canceled?), and start price not provided\");\n         }\n      }\n      else\n      {\n         const limit_order_object &loo = *p_loo;\n\n         // in case of the order not belongs to specified account or market\n         FC_ASSERT(loo.sell_price.base.asset_id == base_id, \"Order base asset inconsistent\");\n         FC_ASSERT(loo.sell_price.quote.asset_id == quote_id, \"Order quote asset inconsistent with order\");\n         FC_ASSERT(loo.seller == account->get_id(), \"Order not owned by specified account\");\n\n         lower_itr = index_by_account.lower_bound(std::make_tuple(account->get_id(), loo.sell_price, start_id));\n      }\n   }\n   else\n   {\n      // if reach here start_price must be valid\n      lower_itr = index_by_account.lower_bound(std::make_tuple(account->get_id(), *ostart_price));\n   }\n\n   upper_itr = index_by_account.upper_bound(std::make_tuple(account->get_id(), price::min(base_id, quote_id)));\n\n   // Add the account's orders\n   for ( ; lower_itr != upper_itr && count < limit; ++lower_itr, ++count)\n   {\n      const limit_order_object &order = *lower_itr;\n      results.emplace_back(order);\n   }\n\n   return results;\n}\n\nvector<call_order_object> database_api::get_call_orders(const std::string& a, uint32_t limit)const\n{\n   return my->get_call_orders( a, limit );\n}\n\nvector<call_order_object> database_api_impl::get_call_orders(const std::string& a, uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_call_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const asset_object* mia = get_asset_from_string(a);\n   const auto& call_index = _db.get_index_type<call_order_index>().indices().get<by_collateral>();\n   price index_price = price::min( mia->bitasset_data(_db).options.short_backing_asset, mia->get_id() );\n\n   vector< call_order_object> result;\n   auto itr_min = call_index.lower_bound(index_price);\n   auto itr_max = call_index.upper_bound(index_price.max());\n   while( itr_min != itr_max && result.size() < limit )\n   {\n      result.emplace_back(*itr_min);\n      ++itr_min;\n   }\n   return result;\n}\n\nvector<call_order_object> database_api::get_call_orders_by_account(const std::string& account_name_or_id,\n                                                                   asset_id_type start, uint32_t limit)const\n{\n   return my->get_call_orders_by_account( account_name_or_id, start, limit );\n}\n\nvector<call_order_object> database_api_impl::get_call_orders_by_account(const std::string& account_name_or_id,\n                                                                        asset_id_type start, uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_call_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<call_order_object> result;\n   const account_id_type account = get_account_from_string(account_name_or_id)->get_id();\n   const auto& call_idx = _db.get_index_type<call_order_index>().indices().get<by_account>();\n   auto call_index_end = call_idx.end();\n   auto call_itr = call_idx.lower_bound(boost::make_tuple(account, start));\n   while(call_itr != call_index_end && call_itr->borrower == account && result.size() < limit)\n   {\n      result.push_back(*call_itr);\n      ++call_itr;\n   }\n   return result;\n}\n\nvector<force_settlement_object> database_api::get_settle_orders(const std::string& a, uint32_t limit)const\n{\n   return my->get_settle_orders( a, limit );\n}\n\nvector<force_settlement_object> database_api_impl::get_settle_orders(const std::string& a, uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_settle_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const asset_id_type asset_a_id = get_asset_from_string(a)->get_id();\n   const auto& settle_index = _db.get_index_type<force_settlement_index>().indices().get<by_expiration>();\n   const asset_object& mia = _db.get(asset_a_id);\n\n   vector<force_settlement_object> result;\n   auto itr_min = settle_index.lower_bound(mia.get_id());\n   auto itr_max = settle_index.upper_bound(mia.get_id());\n   while( itr_min != itr_max && result.size() < limit )\n   {\n      result.emplace_back(*itr_min);\n      ++itr_min;\n   }\n   return result;\n}\n\nvector<force_settlement_object> database_api::get_settle_orders_by_account(\n      const std::string& account_name_or_id,\n      force_settlement_id_type start,\n      uint32_t limit )const\n{\n   return my->get_settle_orders_by_account( account_name_or_id, start, limit);\n}\n\nvector<force_settlement_object> database_api_impl::get_settle_orders_by_account(\n      const std::string& account_name_or_id,\n      force_settlement_id_type start,\n      uint32_t limit )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_settle_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<force_settlement_object> result;\n   const account_id_type account = get_account_from_string(account_name_or_id)->get_id();\n   const auto& settle_idx = _db.get_index_type<force_settlement_index>().indices().get<by_account>();\n   auto settle_index_end = settle_idx.end();\n   auto settle_itr = settle_idx.lower_bound(boost::make_tuple(account, object_id_type(start)));\n   while(settle_itr != settle_index_end && settle_itr->owner == account && result.size() < limit)\n   {\n      result.push_back(*settle_itr);\n      ++settle_itr;\n   }\n   return result;\n}\n\n\nvector<call_order_object> database_api::get_margin_positions( const std::string& account_name_or_id )const\n{\n   FC_ASSERT( my->_app_options, \"Internal error\" );\n   return my->get_call_orders_by_account( account_name_or_id, asset_id_type(),\n                                          my->_app_options->api_limit_get_call_orders );\n}\n\nvector<collateral_bid_object> database_api::get_collateral_bids( const std::string& asset,\n                                                                 uint32_t limit, uint32_t start )const\n{\n   return my->get_collateral_bids( asset, limit, start );\n}\n\nvector<collateral_bid_object> database_api_impl::get_collateral_bids( const std::string& asset_id_or_symbol,\n                                                                      uint32_t limit, uint32_t skip )const\n{ try {\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_collateral_bids;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const asset_object& swan = *get_asset_from_string(asset_id_or_symbol);\n   FC_ASSERT( swan.is_market_issued(), \"Asset is not a MPA\" );\n   const asset_id_type asset_id = swan.get_id();\n   const auto& idx = _db.get_index_type<collateral_bid_index>().indices().get<by_price>();\n   auto itr = idx.lower_bound( asset_id );\n   auto end = idx.upper_bound( asset_id );\n   vector<collateral_bid_object> result;\n   while( skip > 0 && itr != end ) { ++itr; --skip; }\n   for( ; itr != end && limit > 0; ++itr, --limit )\n   {\n      result.push_back(*itr);\n   }\n   return result;\n} FC_CAPTURE_AND_RETHROW( (asset_id_or_symbol)(limit)(skip) ) }\n\nvoid database_api::subscribe_to_market( std::function<void(const variant&)> callback,\n                                        const std::string& a, const std::string& b )\n{\n   my->subscribe_to_market( callback, a, b );\n}\n\nvoid database_api_impl::subscribe_to_market( std::function<void(const variant&)> callback,\n                                             const std::string& a, const std::string& b )\n{\n   auto asset_a_id = get_asset_from_string(a)->get_id();\n   auto asset_b_id = get_asset_from_string(b)->get_id();\n\n   if(asset_a_id > asset_b_id) std::swap(asset_a_id,asset_b_id);\n   FC_ASSERT(asset_a_id != asset_b_id);\n   _market_subscriptions[ std::make_pair(asset_a_id,asset_b_id) ] = callback;\n}\n\nvoid database_api::unsubscribe_from_market(const std::string& a, const std::string& b)\n{\n   my->unsubscribe_from_market( a, b );\n}\n\nvoid database_api_impl::unsubscribe_from_market(const std::string& a, const std::string& b)\n{\n   auto asset_a_id = get_asset_from_string(a)->get_id();\n   auto asset_b_id = get_asset_from_string(b)->get_id();\n\n   if(a > b) std::swap(asset_a_id,asset_b_id);\n   FC_ASSERT(asset_a_id != asset_b_id);\n   _market_subscriptions.erase(std::make_pair(asset_a_id,asset_b_id));\n}\n\nmarket_ticker database_api::get_ticker( const string& base, const string& quote )const\n{\n    return my->get_ticker( base, quote );\n}\n\nmarket_ticker database_api_impl::get_ticker( const string& base, const string& quote, bool skip_order_book )const\n{\n   FC_ASSERT( _app_options && _app_options->has_market_history_plugin, \"Market history plugin is not enabled.\" );\n\n   const auto assets = lookup_asset_symbols( {base, quote} );\n\n   FC_ASSERT( assets[0], \"Invalid base asset symbol: ${s}\", (\"s\",base) );\n   FC_ASSERT( assets[1], \"Invalid quote asset symbol: ${s}\", (\"s\",quote) );\n\n   auto base_id = assets[0]->get_id();\n   auto quote_id = assets[1]->get_id();\n   if( base_id > quote_id ) std::swap( base_id, quote_id );\n   const auto& ticker_idx = _db.get_index_type<market_ticker_index>().indices().get<by_market>();\n   auto itr = ticker_idx.find( std::make_tuple( base_id, quote_id ) );\n   const fc::time_point_sec now = _db.head_block_time();\n   if( itr != ticker_idx.end() )\n   {\n      order_book orders;\n      if (!skip_order_book)\n      {\n         orders = get_order_book(assets[0]->symbol, assets[1]->symbol, 1);\n      }\n      return market_ticker(*itr, now, *assets[0], *assets[1], orders);\n   }\n   // if no ticker is found for this market we return an empty ticker\n   market_ticker empty_result(now, *assets[0], *assets[1]);\n   return empty_result;\n}\n\nmarket_volume database_api::get_24_volume( const string& base, const string& quote )const\n{\n    return my->get_24_volume( base, quote );\n}\n\nmarket_volume database_api_impl::get_24_volume( const string& base, const string& quote )const\n{\n   const auto& ticker = get_ticker( base, quote, true );\n\n   market_volume result;\n   result.time = ticker.time;\n   result.base = ticker.base;\n   result.quote = ticker.quote;\n   result.base_volume = ticker.base_volume;\n   result.quote_volume = ticker.quote_volume;\n\n   return result;\n}\n\norder_book database_api::get_order_book( const string& base, const string& quote, uint32_t limit )const\n{\n   return my->get_order_book( base, quote, limit );\n}\n\norder_book database_api_impl::get_order_book( const string& base, const string& quote, uint32_t limit )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_order_book;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   order_book result( base, quote );\n\n   auto assets = lookup_asset_symbols( {base, quote} );\n   FC_ASSERT( assets[0], \"Invalid base asset symbol: ${s}\", (\"s\",base) );\n   FC_ASSERT( assets[1], \"Invalid quote asset symbol: ${s}\", (\"s\",quote) );\n\n   auto base_id = assets[0]->get_id();\n   auto quote_id = assets[1]->get_id();\n   auto orders = get_limit_orders( base_id, quote_id, limit );\n\n   for( const auto& o : orders )\n   {\n      auto order_price = price_to_string( o.sell_price, *assets[0], *assets[1] );\n      if( o.sell_price.base.asset_id == base_id )\n      {\n         auto quote_amt = assets[1]->amount_to_string( share_type( fc::uint128_t( o.for_sale.value )\n                                                              * o.sell_price.quote.amount.value\n                                                              / o.sell_price.base.amount.value ) );\n         auto base_amt = assets[0]->amount_to_string( o.for_sale );\n         result.bids.emplace_back( order_price, quote_amt, base_amt, o.get_id(),\n                                   o.seller, o.seller(_db).name, o.expiration );\n      }\n      else\n      {\n         auto quote_amt = assets[1]->amount_to_string( o.for_sale );\n         auto base_amt = assets[0]->amount_to_string( share_type( fc::uint128_t( o.for_sale.value )\n                                                             * o.sell_price.quote.amount.value\n                                                             / o.sell_price.base.amount.value ) );\n         result.asks.emplace_back( order_price, quote_amt, base_amt, o.get_id(),\n                                   o.seller, o.seller(_db).name, o.expiration );\n      }\n   }\n\n   return result;\n}\n\nvector<market_ticker> database_api::get_top_markets(uint32_t limit)const\n{\n   return my->get_top_markets(limit);\n}\n\nvector<market_ticker> database_api_impl::get_top_markets(uint32_t limit)const\n{\n   FC_ASSERT( _app_options && _app_options->has_market_history_plugin, \"Market history plugin is not enabled.\" );\n\n   const auto configured_limit = _app_options->api_limit_get_top_markets;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& volume_idx = _db.get_index_type<market_ticker_index>().indices().get<by_volume>();\n   auto itr = volume_idx.rbegin();\n   vector<market_ticker> result;\n   result.reserve(limit);\n   const fc::time_point_sec now = _db.head_block_time();\n\n   while( itr != volume_idx.rend() && result.size() < limit)\n   {\n      const asset_object base = itr->base(_db);\n      const asset_object quote = itr->quote(_db);\n      order_book orders;\n      orders = get_order_book(base.symbol, quote.symbol, 1);\n\n      result.emplace_back(market_ticker(*itr, now, base, quote, orders));\n      ++itr;\n   }\n   return result;\n}\n\nvector<market_trade> database_api::get_trade_history( const string& base,\n                                                      const string& quote,\n                                                      fc::time_point_sec start,\n                                                      fc::time_point_sec stop,\n                                                      uint32_t limit )const\n{\n   return my->get_trade_history( base, quote, start, stop, limit );\n}\n\nvector<market_trade> database_api_impl::get_trade_history( const string& base,\n                                                           const string& quote,\n                                                           fc::time_point_sec start,\n                                                           fc::time_point_sec stop,\n                                                           uint32_t limit )const\n{\n   FC_ASSERT( _app_options && _app_options->has_market_history_plugin, \"Market history plugin is not enabled.\" );\n\n   const auto configured_limit = _app_options->api_limit_get_trade_history;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   auto assets = lookup_asset_symbols( {base, quote} );\n   FC_ASSERT( assets[0], \"Invalid base asset symbol: ${s}\", (\"s\",base) );\n   FC_ASSERT( assets[1], \"Invalid quote asset symbol: ${s}\", (\"s\",quote) );\n\n   auto base_id = assets[0]->get_id();\n   auto quote_id = assets[1]->get_id();\n\n   if( base_id > quote_id ) std::swap( base_id, quote_id );\n\n   if ( start.sec_since_epoch() == 0 )\n      start = fc::time_point_sec( fc::time_point::now() );\n\n   uint32_t count = 0;\n   const auto& history_idx = _db.get_index_type<market_history::history_index>().indices().get<by_market_time>();\n   auto itr = history_idx.lower_bound( std::make_tuple( base_id, quote_id, start ) );\n   vector<market_trade> result;\n\n   while( itr != history_idx.end() && count < limit\n          && !( itr->key.base != base_id || itr->key.quote != quote_id || itr->time < stop ) )\n   {\n      {\n         market_trade trade;\n\n         if( assets[0]->id == itr->op.receives.asset_id )\n         {\n            trade.amount = assets[1]->amount_to_string( itr->op.pays );\n            trade.value = assets[0]->amount_to_string( itr->op.receives );\n         }\n         else\n         {\n            trade.amount = assets[1]->amount_to_string( itr->op.receives );\n            trade.value = assets[0]->amount_to_string( itr->op.pays );\n         }\n\n         trade.date = itr->time;\n         trade.price = price_to_string( itr->op.fill_price, *assets[0], *assets[1] );\n\n         if( itr->op.is_maker )\n         {\n            trade.sequence = -itr->key.sequence;\n            trade.side1_account_id = itr->op.account_id;\n            if(itr->op.receives.asset_id == assets[0]->id)\n               trade.type = \"sell\";\n            else\n               trade.type = \"buy\";\n         }\n         else\n            trade.side2_account_id = itr->op.account_id;\n\n         auto next_itr = std::next(itr);\n         // Trades are usually tracked in each direction, exception: for global settlement only one side is recorded\n         if( next_itr != history_idx.end() && next_itr->key.base == base_id && next_itr->key.quote == quote_id\n             && next_itr->time == itr->time && next_itr->op.is_maker != itr->op.is_maker )\n         {  // next_itr now could be the other direction // FIXME not 100% sure\n            if( next_itr->op.is_maker )\n            {\n               trade.sequence = -next_itr->key.sequence;\n               trade.side1_account_id = next_itr->op.account_id;\n               if(next_itr->op.receives.asset_id == assets[0]->id)\n                  trade.type = \"sell\";\n               else\n                  trade.type = \"buy\";\n            }\n            else\n               trade.side2_account_id = next_itr->op.account_id;\n            // skip the other direction\n            itr = next_itr;\n         }\n\n         result.push_back( trade );\n         ++count;\n      }\n\n      ++itr;\n   }\n\n   return result;\n}\n\nvector<market_trade> database_api::get_trade_history_by_sequence(\n                                                      const string& base,\n                                                      const string& quote,\n                                                      int64_t start,\n                                                      fc::time_point_sec stop,\n                                                      uint32_t limit )const\n{\n   return my->get_trade_history_by_sequence( base, quote, start, stop, limit );\n}\n\nvector<market_trade> database_api_impl::get_trade_history_by_sequence(\n                                                           const string& base,\n                                                           const string& quote,\n                                                           int64_t start,\n                                                           fc::time_point_sec stop,\n                                                           uint32_t limit )const\n{\n   FC_ASSERT( _app_options && _app_options->has_market_history_plugin, \"Market history plugin is not enabled.\" );\n\n   const auto configured_limit = _app_options->api_limit_get_trade_history_by_sequence;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   FC_ASSERT( start >= 0 );\n   int64_t start_seq = -start;\n\n   auto assets = lookup_asset_symbols( {base, quote} );\n   FC_ASSERT( assets[0], \"Invalid base asset symbol: ${s}\", (\"s\",base) );\n   FC_ASSERT( assets[1], \"Invalid quote asset symbol: ${s}\", (\"s\",quote) );\n\n   auto base_id = assets[0]->get_id();\n   auto quote_id = assets[1]->get_id();\n\n   if( base_id > quote_id ) std::swap( base_id, quote_id );\n   const auto& history_idx = _db.get_index_type<graphene::market_history::history_index>().indices().get<by_key>();\n   history_key hkey;\n   hkey.base = base_id;\n   hkey.quote = quote_id;\n   hkey.sequence = start_seq;\n\n   uint32_t count = 0;\n   auto itr = history_idx.lower_bound( hkey );\n   vector<market_trade> result;\n\n   while( itr != history_idx.end() && count < limit\n          && !( itr->key.base != base_id || itr->key.quote != quote_id || itr->time < stop ) )\n   {\n      if( itr->key.sequence == start_seq ) // found the key, should skip this and the other direction if found\n      {\n         auto next_itr = std::next(itr);\n         if( next_itr != history_idx.end() && next_itr->key.base == base_id && next_itr->key.quote == quote_id\n             && next_itr->time == itr->time && next_itr->op.is_maker != itr->op.is_maker )\n         {  // next_itr now could be the other direction // FIXME not 100% sure\n            // skip the other direction\n            itr = next_itr;\n         }\n      }\n      else\n      {\n         market_trade trade;\n\n         if( assets[0]->id == itr->op.receives.asset_id )\n         {\n            trade.amount = assets[1]->amount_to_string( itr->op.pays );\n            trade.value = assets[0]->amount_to_string( itr->op.receives );\n         }\n         else\n         {\n            trade.amount = assets[1]->amount_to_string( itr->op.receives );\n            trade.value = assets[0]->amount_to_string( itr->op.pays );\n         }\n\n         trade.date = itr->time;\n         trade.price = price_to_string( itr->op.fill_price, *assets[0], *assets[1] );\n\n         if( itr->op.is_maker )\n         {\n            trade.sequence = -itr->key.sequence;\n            trade.side1_account_id = itr->op.account_id;\n            if(itr->op.receives.asset_id == assets[0]->id)\n               trade.type = \"sell\";\n            else\n               trade.type = \"buy\";\n         }\n         else\n            trade.side2_account_id = itr->op.account_id;\n\n         auto next_itr = std::next(itr);\n         // Trades are usually tracked in each direction, exception: for global settlement only one side is recorded\n         if( next_itr != history_idx.end() && next_itr->key.base == base_id && next_itr->key.quote == quote_id\n             && next_itr->time == itr->time && next_itr->op.is_maker != itr->op.is_maker )\n         {  // next_itr now could be the other direction // FIXME not 100% sure\n            if( next_itr->op.is_maker )\n            {\n               trade.sequence = -next_itr->key.sequence;\n               trade.side1_account_id = next_itr->op.account_id;\n               if(next_itr->op.receives.asset_id == assets[0]->id)\n                  trade.type = \"sell\";\n               else\n                  trade.type = \"buy\";\n            }\n            else\n               trade.side2_account_id = next_itr->op.account_id;\n            // skip the other direction\n            itr = next_itr;\n         }\n\n         result.push_back( trade );\n         ++count;\n      }\n\n      ++itr;\n   }\n\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Liquidity pools                                                  //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<extended_liquidity_pool_object> database_api::list_liquidity_pools(\n            const optional<uint32_t>& limit,\n            const optional<liquidity_pool_id_type>& start_id,\n            const optional<bool>& with_statistics )const\n{\n   return my->get_liquidity_pools_by_asset_x<by_id>(\n            limit,\n            start_id,\n            with_statistics );\n}\n\nvector<extended_liquidity_pool_object> database_api::get_liquidity_pools_by_asset_a(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<liquidity_pool_id_type>& start_id,\n            const optional<bool>& with_statistics )const\n{\n   asset_id_type asset_id = my->get_asset_from_string(asset_symbol_or_id)->get_id();\n   return my->get_liquidity_pools_by_asset_x<by_asset_a>(\n            limit,\n            start_id,\n            with_statistics,\n            asset_id );\n}\n\nvector<extended_liquidity_pool_object> database_api::get_liquidity_pools_by_asset_b(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<liquidity_pool_id_type>& start_id,\n            const optional<bool>& with_statistics )const\n{\n   asset_id_type asset_id = my->get_asset_from_string(asset_symbol_or_id)->get_id();\n   return my->get_liquidity_pools_by_asset_x<by_asset_b>(\n            limit,\n            start_id,\n            with_statistics,\n            asset_id );\n}\n\nvector<extended_liquidity_pool_object> database_api::get_liquidity_pools_by_one_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<liquidity_pool_id_type>& start_id,\n            const optional<bool>& with_statistics )const\n{\n   return my->get_liquidity_pools_by_one_asset(\n            asset_symbol_or_id,\n            limit,\n            start_id,\n            with_statistics );\n}\n\nvector<extended_liquidity_pool_object> database_api_impl::get_liquidity_pools_by_one_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& olimit,\n            const optional<liquidity_pool_id_type>& ostart_id,\n            const optional<bool>& with_statistics )const\n{\n   // api_helper_indexes plugin is required for accessing the secondary index\n   FC_ASSERT( _app_options && _app_options->has_api_helper_indexes_plugin,\n              \"api_helper_indexes plugin is not enabled on this server.\" );\n\n   const auto configured_limit = _app_options->api_limit_get_liquidity_pools;\n   uint32_t limit = olimit.valid() ? *olimit : configured_limit;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   asset_id_type aid = get_asset_from_string(asset_symbol_or_id)->get_id();\n\n   FC_ASSERT( asset_in_liquidity_pools_index, \"Internal error\" );\n   const auto& pools = asset_in_liquidity_pools_index->get_liquidity_pools_by_asset( aid );\n\n   liquidity_pool_id_type start_id = ostart_id.valid() ? *ostart_id : liquidity_pool_id_type();\n\n   auto itr = pools.lower_bound( start_id );\n\n   bool with_stats = ( with_statistics.valid() && *with_statistics );\n\n   vector<extended_liquidity_pool_object> results;\n\n   results.reserve( limit );\n   while( itr != pools.end() && results.size() < limit )\n   {\n      results.emplace_back( extend_liquidity_pool( (*itr)(_db), with_stats ) );\n      ++itr;\n   }\n\n   return results;\n}\n\nvector<extended_liquidity_pool_object> database_api::get_liquidity_pools_by_both_assets(\n            const std::string& asset_symbol_or_id_a,\n            const std::string& asset_symbol_or_id_b,\n            const optional<uint32_t>& limit,\n            const optional<liquidity_pool_id_type>& start_id,\n            const optional<bool>& with_statistics )const\n{\n   asset_id_type asset_id_a = my->get_asset_from_string(asset_symbol_or_id_a)->get_id();\n   asset_id_type asset_id_b = my->get_asset_from_string(asset_symbol_or_id_b)->get_id();\n   if( asset_id_a > asset_id_b )\n      std::swap( asset_id_a, asset_id_b );\n   return my->get_liquidity_pools_by_asset_x<by_asset_ab>(\n            limit,\n            start_id,\n            with_statistics,\n            asset_id_a,\n            asset_id_b );\n}\n\nvector<optional<extended_liquidity_pool_object>> database_api::get_liquidity_pools(\n            const vector<liquidity_pool_id_type>& ids,\n            const optional<bool>& subscribe,\n            const optional<bool>& with_statistics )const\n{\n   return my->get_liquidity_pools(\n            ids,\n            subscribe,\n            with_statistics );\n}\n\nvector<optional<extended_liquidity_pool_object>> database_api_impl::get_liquidity_pools(\n            const vector<liquidity_pool_id_type>& ids,\n            const optional<bool>& subscribe,\n            const optional<bool>& with_statistics )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_liquidity_pools;\n   FC_ASSERT( ids.size() <= configured_limit,\n              \"size of the querying list can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   bool with_stats = ( with_statistics.valid() && *with_statistics );\n\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n   vector<optional<extended_liquidity_pool_object>> result; result.reserve(ids.size());\n   std::transform(ids.begin(), ids.end(), std::back_inserter(result),\n                  [this,to_subscribe,with_stats](liquidity_pool_id_type id)\n                     -> optional<extended_liquidity_pool_object> {\n\n      if(auto o = _db.find(id))\n      {\n         auto ext_obj = extend_liquidity_pool( *o, with_stats );\n         if( to_subscribe )\n         {\n            subscribe_to_item( id );\n            if( ext_obj.statistics.valid() )\n               subscribe_to_item( ext_obj.statistics->id );\n         }\n         return ext_obj;\n      }\n      return {};\n   });\n   return result;\n}\n\nvector<optional<extended_liquidity_pool_object>> database_api::get_liquidity_pools_by_share_asset(\n            const vector<std::string>& asset_symbols_or_ids,\n            const optional<bool>& subscribe,\n            const optional<bool>& with_statistics )const\n{\n   return my->get_liquidity_pools_by_share_asset(\n            asset_symbols_or_ids,\n            subscribe,\n            with_statistics );\n}\n\nvector<optional<extended_liquidity_pool_object>> database_api_impl::get_liquidity_pools_by_share_asset(\n            const vector<std::string>& asset_symbols_or_ids,\n            const optional<bool>& subscribe,\n            const optional<bool>& with_statistics )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_liquidity_pools;\n   FC_ASSERT( asset_symbols_or_ids.size() <= configured_limit,\n              \"size of the querying list can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   bool with_stats = ( with_statistics.valid() && *with_statistics );\n\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n   vector<optional<extended_liquidity_pool_object>> result; result.reserve(asset_symbols_or_ids.size());\n   std::transform(asset_symbols_or_ids.begin(), asset_symbols_or_ids.end(), std::back_inserter(result),\n                  [this,to_subscribe,with_stats](std::string id_or_name) -> optional<extended_liquidity_pool_object> {\n\n      const asset_object* asset_obj = get_asset_from_string( id_or_name, false );\n      if( asset_obj == nullptr || !asset_obj->is_liquidity_pool_share_asset() )\n         return {};\n      const liquidity_pool_object& lp_obj = (*asset_obj->for_liquidity_pool)(_db);\n      auto ext_obj = extend_liquidity_pool( lp_obj, with_stats );\n      if( to_subscribe )\n      {\n         subscribe_to_item( lp_obj.id );\n         if( ext_obj.statistics.valid() )\n            subscribe_to_item( ext_obj.statistics->id );\n      }\n      return ext_obj;\n   });\n   return result;\n}\n\nvector<extended_liquidity_pool_object> database_api::get_liquidity_pools_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<asset_id_type>& start_id,\n            const optional<bool>& with_statistics )const\n{\n   return my->get_liquidity_pools_by_owner(\n            account_name_or_id,\n            limit,\n            start_id,\n            with_statistics );\n}\n\nvector<extended_liquidity_pool_object> database_api_impl::get_liquidity_pools_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& olimit,\n            const optional<asset_id_type>& ostart_id,\n            const optional<bool>& with_statistics )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_liquidity_pools;\n   uint32_t limit = olimit.valid() ? *olimit : configured_limit;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   bool with_stats = ( with_statistics.valid() && *with_statistics );\n\n   vector<extended_liquidity_pool_object> results;\n\n   account_id_type owner = get_account_from_string(account_name_or_id)->get_id();\n\n   asset_id_type start_asset_id = ostart_id.valid() ? *ostart_id : asset_id_type();\n   object_id_type start_id { start_asset_id };\n\n   // get assets owned by account\n   const auto& idx = _db.get_index_type<asset_index>().indices().get<by_issuer>();\n   auto lower_itr = idx.lower_bound( std::make_tuple( owner, start_id ) );\n   auto upper_itr = idx.upper_bound( owner );\n\n   results.reserve( limit );\n   for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr )\n   {\n      const asset_object& asset_obj = *lower_itr;\n      if( !asset_obj.is_liquidity_pool_share_asset() ) // TODO improve performance\n         continue;\n      results.emplace_back( extend_liquidity_pool( (*asset_obj.for_liquidity_pool)(_db), with_stats ) );\n   }\n\n   return results;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// SameT Funds                                                      //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<samet_fund_object> database_api::list_samet_funds(\n            const optional<uint32_t>& limit,\n            const optional<samet_fund_id_type>& start_id )const\n{\n   const auto& idx = my->_db.get_index_type<samet_fund_index>().indices().get<by_id>();\n   return my->get_objects_by_x< samet_fund_object,\n                                samet_fund_id_type\n                               >( &application_options::api_limit_get_samet_funds,\n                                  idx, limit, start_id );\n}\n\nvector<samet_fund_object> database_api::get_samet_funds_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<samet_fund_id_type>& start_id )const\n{\n   account_id_type owner = my->get_account_from_string(account_name_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<samet_fund_index>().indices().get<by_owner>();\n   return my->get_objects_by_x< samet_fund_object,\n                                samet_fund_id_type\n                               >( &application_options::api_limit_get_samet_funds,\n                                  idx, limit, start_id, owner );\n}\n\nvector<samet_fund_object> database_api::get_samet_funds_by_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<samet_fund_id_type>& start_id )const\n{\n   asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<samet_fund_index>().indices().get<by_asset_type>();\n   return my->get_objects_by_x< samet_fund_object,\n                                samet_fund_id_type\n                               >( &application_options::api_limit_get_samet_funds,\n                                  idx, limit, start_id, asset_type );\n}\n\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Credit offers and credit deals                                   //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<credit_offer_object> database_api::list_credit_offers(\n            const optional<uint32_t>& limit,\n            const optional<credit_offer_id_type>& start_id )const\n{\n   const auto& idx = my->_db.get_index_type<credit_offer_index>().indices().get<by_id>();\n   return my->get_objects_by_x< credit_offer_object,\n                                credit_offer_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id );\n}\n\nvector<credit_offer_object> database_api::get_credit_offers_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_offer_id_type>& start_id )const\n{\n   account_id_type owner = my->get_account_from_string(account_name_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<credit_offer_index>().indices().get<by_owner>();\n   return my->get_objects_by_x< credit_offer_object,\n                                credit_offer_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, owner );\n}\n\nvector<credit_offer_object> database_api::get_credit_offers_by_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_offer_id_type>& start_id )const\n{\n   asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<credit_offer_index>().indices().get<by_asset_type>();\n   return my->get_objects_by_x< credit_offer_object,\n                                credit_offer_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, asset_type );\n}\n\nvector<credit_deal_object> database_api::list_credit_deals(\n            const optional<uint32_t>& limit,\n            const optional<credit_deal_id_type>& start_id )const\n{\n   const auto& idx = my->_db.get_index_type<credit_deal_index>().indices().get<by_id>();\n   return my->get_objects_by_x< credit_deal_object,\n                                credit_deal_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id );\n}\n\nvector<credit_deal_object> database_api::get_credit_deals_by_offer_id(\n            const credit_offer_id_type& offer_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_deal_id_type>& start_id )const\n{\n   const auto& idx = my->_db.get_index_type<credit_deal_index>().indices().get<by_offer_id>();\n   return my->get_objects_by_x< credit_deal_object,\n                                credit_deal_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, offer_id );\n}\n\nvector<credit_deal_object> database_api::get_credit_deals_by_offer_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_deal_id_type>& start_id )const\n{\n   account_id_type owner = my->get_account_from_string(account_name_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<credit_deal_index>().indices().get<by_offer_owner>();\n   return my->get_objects_by_x< credit_deal_object,\n                                credit_deal_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, owner );\n}\n\nvector<credit_deal_object> database_api::get_credit_deals_by_borrower(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_deal_id_type>& start_id )const\n{\n   account_id_type borrower = my->get_account_from_string(account_name_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<credit_deal_index>().indices().get<by_borrower>();\n   return my->get_objects_by_x< credit_deal_object,\n                                credit_deal_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, borrower );\n}\n\nvector<credit_deal_object> database_api::get_credit_deals_by_debt_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_deal_id_type>& start_id )const\n{\n   asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<credit_deal_index>().indices().get<by_debt_asset>();\n   return my->get_objects_by_x< credit_deal_object,\n                                credit_deal_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, asset_type );\n}\n\nvector<credit_deal_object> database_api::get_credit_deals_by_collateral_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<credit_deal_id_type>& start_id )const\n{\n   asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<credit_deal_index>().indices().get<by_collateral_asset>();\n   return my->get_objects_by_x< credit_deal_object,\n                                credit_deal_id_type\n                               >( &application_options::api_limit_get_credit_offers,\n                                  idx, limit, start_id, asset_type );\n}\n\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Witnesses                                                        //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<optional<witness_object>> database_api::get_witnesses(const vector<witness_id_type>& witness_ids)const\n{\n   return my->get_witnesses( witness_ids );\n}\n\nvector<optional<witness_object>> database_api_impl::get_witnesses(const vector<witness_id_type>& witness_ids)const\n{\n   vector<optional<witness_object>> result; result.reserve(witness_ids.size());\n   std::transform(witness_ids.begin(), witness_ids.end(), std::back_inserter(result),\n                  [this](witness_id_type id) -> optional<witness_object> {\n      if(auto o = _db.find(id))\n         return *o;\n      return {};\n   });\n   return result;\n}\n\nfc::optional<witness_object> database_api::get_witness_by_account(const std::string& account_id_or_name)const\n{\n   return my->get_witness_by_account( account_id_or_name );\n}\n\nfc::optional<witness_object> database_api_impl::get_witness_by_account(const std::string& account_id_or_name) const\n{\n   const auto& idx = _db.get_index_type<witness_index>().indices().get<by_account>();\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto itr = idx.find(account);\n   if( itr != idx.end() )\n      return *itr;\n   return {};\n}\n\nmap<string, witness_id_type, std::less<>> database_api::lookup_witness_accounts( const string& lower_bound_name,\n                                                                                 uint32_t limit )const\n{\n   return my->lookup_witness_accounts( lower_bound_name, limit );\n}\n\nmap<string, witness_id_type, std::less<>> database_api_impl::lookup_witness_accounts( const string& lower_bound_name,\n                                                                                      uint32_t limit )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_lookup_witness_accounts;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& witnesses_by_id = _db.get_index_type<witness_index>().indices().get<by_id>();\n\n   // we want to order witnesses by account name, but that name is in the account object\n   // so the witness_index doesn't have a quick way to access it.\n   // get all the names and look them all up, sort them, then figure out what\n   // records to return.  This could be optimized, but we expect the\n   // number of witnesses to be few and the frequency of calls to be rare\n   // TODO optimize\n   std::map<std::string, witness_id_type, std::less<>> witnesses_by_account_name;\n   for (const witness_object& witness : witnesses_by_id)\n       if (auto account_iter = _db.find(witness.witness_account))\n           if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name\n               witnesses_by_account_name.insert(std::make_pair(account_iter->name, witness.get_id()));\n\n   auto end_iter = witnesses_by_account_name.begin();\n   while( end_iter != witnesses_by_account_name.end() && limit > 0 )\n   {\n      ++end_iter;\n      --limit;\n   }\n   witnesses_by_account_name.erase(end_iter, witnesses_by_account_name.end());\n   return witnesses_by_account_name;\n}\n\nuint64_t database_api::get_witness_count()const\n{\n   return my->get_witness_count();\n}\n\nuint64_t database_api_impl::get_witness_count()const\n{\n   return _db.get_index_type<witness_index>().indices().size();\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Committee members                                                //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<optional<committee_member_object>> database_api::get_committee_members(\n                                             const vector<committee_member_id_type>& committee_member_ids )const\n{\n   return my->get_committee_members( committee_member_ids );\n}\n\nvector<optional<committee_member_object>> database_api_impl::get_committee_members(\n                                             const vector<committee_member_id_type>& committee_member_ids )const\n{\n   vector<optional<committee_member_object>> result; result.reserve(committee_member_ids.size());\n   std::transform(committee_member_ids.begin(), committee_member_ids.end(), std::back_inserter(result),\n                  [this](committee_member_id_type id) -> optional<committee_member_object> {\n      if(auto o = _db.find(id))\n         return *o;\n      return {};\n   });\n   return result;\n}\n\nfc::optional<committee_member_object> database_api::get_committee_member_by_account(\n                                         const std::string& account_id_or_name )const\n{\n   return my->get_committee_member_by_account( account_id_or_name );\n}\n\nfc::optional<committee_member_object> database_api_impl::get_committee_member_by_account(\n                                         const std::string& account_id_or_name )const\n{\n   const auto& idx = _db.get_index_type<committee_member_index>().indices().get<by_account>();\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto itr = idx.find(account);\n   if( itr != idx.end() )\n      return *itr;\n   return {};\n}\n\nmap<string, committee_member_id_type, std::less<>> database_api::lookup_committee_member_accounts(\n                                         const string& lower_bound_name, uint32_t limit )const\n{\n   return my->lookup_committee_member_accounts( lower_bound_name, limit );\n}\n\nmap<string, committee_member_id_type, std::less<>> database_api_impl::lookup_committee_member_accounts(\n                                         const string& lower_bound_name, uint32_t limit )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_lookup_committee_member_accounts;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& committee_members_by_id = _db.get_index_type<committee_member_index>().indices().get<by_id>();\n\n   // we want to order committee_members by account name, but that name is in the account object\n   // so the committee_member_index doesn't have a quick way to access it.\n   // get all the names and look them all up, sort them, then figure out what\n   // records to return.  This could be optimized, but we expect the\n   // number of committee_members to be few and the frequency of calls to be rare\n   // TODO optimize\n   std::map<std::string, committee_member_id_type, std::less<>> committee_members_by_account_name;\n   for (const committee_member_object& committee_member : committee_members_by_id)\n      if (auto account_iter = _db.find(committee_member.committee_member_account))\n         if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name\n            committee_members_by_account_name.insert(std::make_pair(account_iter->name, committee_member.get_id()));\n\n   auto end_iter = committee_members_by_account_name.begin();\n   while( end_iter != committee_members_by_account_name.end() && limit > 0 )\n   {\n      ++end_iter;\n      --limit;\n   }\n   committee_members_by_account_name.erase(end_iter, committee_members_by_account_name.end());\n   return committee_members_by_account_name;\n}\n\nuint64_t database_api::get_committee_count()const\n{\n    return my->get_committee_count();\n}\n\nuint64_t database_api_impl::get_committee_count()const\n{\n    return _db.get_index_type<committee_member_index>().indices().size();\n}\n\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Workers                                                          //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<worker_object> database_api::get_all_workers( const optional<bool>& is_expired )const\n{\n   return my->get_all_workers( is_expired );\n}\n\nvector<worker_object> database_api_impl::get_all_workers( const optional<bool>& is_expired )const\n{\n   vector<worker_object> result;\n\n   if( !is_expired.valid() ) // query for all workers\n   {\n      const auto& workers_idx = _db.get_index_type<worker_index>().indices().get<by_id>();\n      result.reserve( workers_idx.size() );\n      for( const auto& w : workers_idx )\n      {\n         result.push_back( w );\n      }\n   }\n   else // query for workers that are expired only or not expired only\n   {\n      const time_point_sec now = _db.head_block_time();\n      const auto& workers_idx = _db.get_index_type<worker_index>().indices().get<by_end_date>();\n      auto itr = *is_expired ? workers_idx.begin() : workers_idx.lower_bound( now );\n      auto end = *is_expired ? workers_idx.upper_bound( now ) : workers_idx.end();\n      for( ; itr != end; ++itr )\n      {\n         result.push_back( *itr );\n      }\n   }\n\n   return result;\n}\n\nvector<worker_object> database_api::get_workers_by_account(const std::string& account_id_or_name)const\n{\n   return my->get_workers_by_account( account_id_or_name );\n}\n\nvector<worker_object> database_api_impl::get_workers_by_account(const std::string& account_id_or_name)const\n{\n   vector<worker_object> result;\n   const auto& workers_idx = _db.get_index_type<worker_index>().indices().get<by_account>();\n\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto range = workers_idx.equal_range(account);\n   for(auto itr = range.first; itr != range.second; ++itr)\n   {\n      result.push_back( *itr );\n   }\n   return result;\n}\n\nuint64_t database_api::get_worker_count()const\n{\n    return my->get_worker_count();\n}\n\nuint64_t database_api_impl::get_worker_count()const\n{\n    return _db.get_index_type<worker_index>().indices().size();\n}\n\n\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Votes                                                            //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<variant> database_api::lookup_vote_ids( const vector<vote_id_type>& votes )const\n{\n   return my->lookup_vote_ids( votes );\n}\n\nvector<variant> database_api_impl::lookup_vote_ids( const vector<vote_id_type>& votes )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_lookup_vote_ids;\n   FC_ASSERT( votes.size() <= configured_limit,\n              \"Number of querying votes can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& witness_idx = _db.get_index_type<witness_index>().indices().get<by_vote_id>();\n   const auto& committee_idx = _db.get_index_type<committee_member_index>().indices().get<by_vote_id>();\n   const auto& for_worker_idx = _db.get_index_type<worker_index>().indices().get<by_vote_for>();\n   const auto& against_worker_idx = _db.get_index_type<worker_index>().indices().get<by_vote_against>();\n\n   vector<variant> result;\n   result.reserve( votes.size() );\n   for( auto id : votes )\n   {\n      switch( id.type() )\n      {\n         case vote_id_type::committee:\n         {\n            auto itr = committee_idx.find( id );\n            if( itr != committee_idx.end() )\n               result.emplace_back( variant( *itr, 2 ) ); // Depth of committee_member_object is 1, add 1 to be safe\n            else\n               result.emplace_back( variant() );\n            break;\n         }\n         case vote_id_type::witness:\n         {\n            auto itr = witness_idx.find( id );\n            if( itr != witness_idx.end() )\n               result.emplace_back( variant( *itr, 2 ) ); // Depth of witness_object is 1, add 1 here to be safe\n            else\n               result.emplace_back( variant() );\n            break;\n         }\n         case vote_id_type::worker:\n         {\n            auto itr = for_worker_idx.find( id );\n            if( itr != for_worker_idx.end() ) {\n               result.emplace_back( variant( *itr, 4 ) ); // Depth of worker_object is 3, add 1 here to be safe.\n                                                          // If we want to extract the balance object inside,\n                                                          //   need to increase this value\n            }\n            else {\n               auto itr = against_worker_idx.find( id );\n               if( itr != against_worker_idx.end() ) {\n                  result.emplace_back( variant( *itr, 4 ) ); // Depth of worker_object is 3, add 1 here to be safe.\n                                                             // If we want to extract the balance object inside,\n                                                             //   need to increase this value\n               }\n               else {\n                  result.emplace_back( variant() );\n               }\n            }\n            break;\n         }\n         case vote_id_type::VOTE_TYPE_COUNT: break; // supress unused enum value warnings\n         default:\n            FC_CAPTURE_AND_THROW( fc::out_of_range_exception, (id) );\n      }\n   }\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Authority / validation                                           //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nstd::string database_api::get_transaction_hex(const signed_transaction& trx)const\n{\n   return my->get_transaction_hex( trx );\n}\n\nstd::string database_api_impl::get_transaction_hex(const signed_transaction& trx)const\n{\n   return fc::to_hex(fc::raw::pack(trx));\n}\n\nstd::string database_api::get_transaction_hex_without_sig(\n   const transaction &trx) const\n{\n   return my->get_transaction_hex_without_sig(trx);\n}\n\nstd::string database_api_impl::get_transaction_hex_without_sig(\n   const transaction &trx) const\n{\n   return fc::to_hex(fc::raw::pack(trx));\n}\n\nset<public_key_type> database_api::get_required_signatures( const signed_transaction& trx,\n                                                            const flat_set<public_key_type>& available_keys )const\n{\n   return my->get_required_signatures( trx, available_keys );\n}\n\nset<public_key_type> database_api_impl::get_required_signatures( const signed_transaction& trx,\n                                                            const flat_set<public_key_type>& available_keys )const\n{\n   auto chain_time = _db.head_block_time();\n   bool allow_non_immediate_owner = ( chain_time >= HARDFORK_CORE_584_TIME );\n   bool ignore_custom_op_reqd_auths = MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( chain_time );\n   auto result = trx.get_required_signatures( _db.get_chain_id(),\n                                       available_keys,\n                                       [&]( account_id_type id ){ return &id(_db).active; },\n                                       [&]( account_id_type id ){ return &id(_db).owner; },\n                                       allow_non_immediate_owner,\n                                       ignore_custom_op_reqd_auths,\n                                       _db.get_global_properties().parameters.max_authority_depth );\n   return result;\n}\n\nset<public_key_type> database_api::get_potential_signatures( const signed_transaction& trx )const\n{\n   return my->get_potential_signatures( trx );\n}\nset<address> database_api::get_potential_address_signatures( const signed_transaction& trx )const\n{\n   return my->get_potential_address_signatures( trx );\n}\n\nset<public_key_type> database_api_impl::get_potential_signatures( const signed_transaction& trx )const\n{\n   auto chain_time = _db.head_block_time();\n   bool allow_non_immediate_owner = ( chain_time >= HARDFORK_CORE_584_TIME );\n   bool ignore_custom_op_reqd_auths = MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( chain_time );\n\n   set<public_key_type> result;\n   auto get_active = [this, &result]( account_id_type id ){\n      const auto& auth = id( _db ).active;\n      for( const auto& k : auth.get_keys() )\n         result.insert( k );\n      return &auth;\n   };\n   auto get_owner = [this, &result]( account_id_type id ){\n      const auto& auth = id( _db ).owner;\n      for( const auto& k : auth.get_keys() )\n         result.insert( k );\n      return &auth;\n   };\n\n   trx.get_required_signatures( _db.get_chain_id(),\n                                flat_set<public_key_type>(),\n                                get_active, get_owner,\n                                allow_non_immediate_owner,\n                                ignore_custom_op_reqd_auths,\n                                _db.get_global_properties().parameters.max_authority_depth );\n\n   // Insert keys in required \"other\" authories\n   flat_set<account_id_type> required_active;\n   flat_set<account_id_type> required_owner;\n   vector<authority> other;\n   trx.get_required_authorities( required_active, required_owner, other, ignore_custom_op_reqd_auths );\n   for( const auto& auth : other )\n      for( const auto& key : auth.get_keys() )\n         result.insert( key );\n\n   return result;\n}\n\nset<address> database_api_impl::get_potential_address_signatures( const signed_transaction& trx )const\n{\n   auto chain_time = _db.head_block_time();\n   bool allow_non_immediate_owner = ( chain_time >= HARDFORK_CORE_584_TIME );\n   bool ignore_custom_op_reqd_auths = MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( chain_time );\n\n   set<address> result;\n   auto get_active = [this, &result]( account_id_type id ){\n      const auto& auth = id( _db ).active;\n      for( const auto& k : auth.get_addresses() )\n         result.insert( k );\n      return &auth;\n   };\n   auto get_owner = [this, &result]( account_id_type id ) {\n      const auto& auth = id( _db ).owner;\n      for (const auto& k : auth.get_addresses())\n         result.insert( k );\n      return &auth;\n   };\n\n   trx.get_required_signatures( _db.get_chain_id(),\n                                flat_set<public_key_type>(),\n                                get_active, get_owner,\n                                allow_non_immediate_owner,\n                                ignore_custom_op_reqd_auths,\n                                _db.get_global_properties().parameters.max_authority_depth );\n   return result;\n}\n\nbool database_api::verify_authority( const signed_transaction& trx )const\n{\n   return my->verify_authority( trx );\n}\n\nbool database_api_impl::verify_authority( const signed_transaction& trx )const\n{\n   bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME );\n   trx.verify_authority( _db.get_chain_id(),\n                         [this]( account_id_type id ){ return &id(_db).active; },\n                         [this]( account_id_type id ){ return &id(_db).owner; },\n                         [this]( account_id_type id, const operation& op, rejected_predicate_map* rejects ) {\n                           return _db.get_viable_custom_authorities(id, op, rejects); },\n                         allow_non_immediate_owner,\n                         _db.get_global_properties().parameters.max_authority_depth );\n   return true;\n}\n\nbool database_api::verify_account_authority( const string& account_name_or_id,\n                                             const flat_set<public_key_type>& signers )const\n{\n   return my->verify_account_authority( account_name_or_id, signers );\n}\n\nbool database_api_impl::verify_account_authority( const string& account_name_or_id,\n      const flat_set<public_key_type>& keys )const\n{\n   // create a dummy transfer\n   transfer_operation op;\n   op.from = get_account_from_string(account_name_or_id)->get_id();\n   std::vector<operation> ops;\n   ops.emplace_back(op);\n\n   try\n   {\n      graphene::chain::verify_authority(ops, keys,\n            [this]( account_id_type id ){ return &id(_db).active; },\n            [this]( account_id_type id ){ return &id(_db).owner; },\n            // Use a no-op lookup for custom authorities; we don't want it even if one does apply for our dummy op\n            [](auto, auto, auto*) { return vector<authority>(); },\n            true, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(_db.head_block_time()) );\n   }\n   catch (fc::exception& ex)\n   {\n      return false;\n   }\n\n   return true;\n}\n\nprocessed_transaction database_api::validate_transaction( const signed_transaction& trx )const\n{\n   return my->validate_transaction( trx );\n}\n\nprocessed_transaction database_api_impl::validate_transaction( const signed_transaction& trx )const\n{\n   return _db.validate_transaction(trx);\n}\n\nvector< fc::variant > database_api::get_required_fees( const vector<operation>& ops,\n                                                       const std::string& asset_id_or_symbol )const\n{\n   return my->get_required_fees( ops, asset_id_or_symbol );\n}\n\n/**\n * Container method for mutually recursive functions used to\n * implement get_required_fees() with potentially nested proposals.\n */\nstruct get_required_fees_helper\n{\n   get_required_fees_helper(\n      const fee_schedule& _current_fee_schedule,\n      const price& _core_exchange_rate,\n      uint32_t _max_recursion\n      )\n      : current_fee_schedule(_current_fee_schedule),\n        core_exchange_rate(_core_exchange_rate),\n        max_recursion(_max_recursion)\n   {}\n\n   fc::variant set_op_fees( operation& op )\n   {\n      if( op.is_type<proposal_create_operation>() )\n      {\n         return set_proposal_create_op_fees( op );\n      }\n      else\n      {\n         asset fee = current_fee_schedule.set_fee( op, core_exchange_rate );\n         fc::variant result;\n         fc::to_variant( fee, result, GRAPHENE_NET_MAX_NESTED_OBJECTS );\n         return result;\n      }\n   }\n\n   fc::variant set_proposal_create_op_fees( operation& proposal_create_op )\n   {\n      proposal_create_operation& op = proposal_create_op.get<proposal_create_operation>();\n      std::pair< asset, fc::variants > result;\n      for( op_wrapper& prop_op : op.proposed_ops )\n      {\n         FC_ASSERT( current_recursion < max_recursion );\n         ++current_recursion;\n         result.second.push_back( set_op_fees( prop_op.op ) );\n         --current_recursion;\n      }\n      // we need to do this on the boxed version, which is why we use\n      // two mutually recursive functions instead of a visitor\n      result.first = current_fee_schedule.set_fee( proposal_create_op, core_exchange_rate );\n      fc::variant vresult;\n      fc::to_variant( result, vresult, GRAPHENE_NET_MAX_NESTED_OBJECTS );\n      return vresult;\n   }\n\n   const fee_schedule& current_fee_schedule;\n   const price& core_exchange_rate;\n   uint32_t max_recursion;\n   uint32_t current_recursion = 0;\n};\n\nvector< fc::variant > database_api_impl::get_required_fees( const vector<operation>& ops,\n                                                            const std::string& asset_id_or_symbol )const\n{\n   vector< operation > _ops = ops;\n   //\n   // we copy the ops because we need to mutate an operation to reliably\n   // determine its fee, see #435\n   //\n\n   vector< fc::variant > result;\n   result.reserve(ops.size());\n   const asset_object& a = *get_asset_from_string(asset_id_or_symbol);\n   get_required_fees_helper helper(\n      _db.current_fee_schedule(),\n      a.options.core_exchange_rate,\n      GET_REQUIRED_FEES_MAX_RECURSION );\n   for( operation& op : _ops )\n   {\n      result.push_back( helper.set_op_fees( op ) );\n   }\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Proposed transactions                                            //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<proposal_object> database_api::get_proposed_transactions( const std::string account_id_or_name )const\n{\n   return my->get_proposed_transactions( account_id_or_name );\n}\n\nvector<proposal_object> database_api_impl::get_proposed_transactions( const std::string account_id_or_name )const\n{\n   // api_helper_indexes plugin is required for accessing the secondary index\n   FC_ASSERT( _app_options && _app_options->has_api_helper_indexes_plugin,\n              \"api_helper_indexes plugin is not enabled on this server.\" );\n\n   const auto& proposal_idx = _db.get_index_type< primary_index< proposal_index > >();\n   const auto& proposals_by_account = proposal_idx.get_secondary_index<graphene::chain::required_approval_index>();\n\n   vector<proposal_object> result;\n   const account_id_type id = get_account_from_string(account_id_or_name)->get_id();\n\n   auto required_approvals_itr = proposals_by_account._account_to_proposals.find( id );\n   if( required_approvals_itr != proposals_by_account._account_to_proposals.end() )\n   {\n      result.reserve( required_approvals_itr->second.size() );\n      for( auto proposal_id : required_approvals_itr->second )\n      {\n         result.push_back( proposal_id(_db) );\n      }\n   }\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Blinded balances                                                 //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<blinded_balance_object> database_api::get_blinded_balances(\n                                  const flat_set<commitment_type>& commitments )const\n{\n   return my->get_blinded_balances( commitments );\n}\n\nvector<blinded_balance_object> database_api_impl::get_blinded_balances(\n                                  const flat_set<commitment_type>& commitments )const\n{\n   vector<blinded_balance_object> result; result.reserve(commitments.size());\n   const auto& bal_idx = _db.get_index_type<blinded_balance_index>();\n   const auto& by_commitment_idx = bal_idx.indices().get<by_commitment>();\n   for( const auto& c : commitments )\n   {\n      auto itr = by_commitment_idx.find( c );\n      if( itr != by_commitment_idx.end() )\n         result.push_back( *itr );\n   }\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n//  Withdrawals                                                     //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<withdraw_permission_object> database_api::get_withdraw_permissions_by_giver(\n                                      const std::string account_id_or_name,\n                                      withdraw_permission_id_type start,\n                                      uint32_t limit)const\n{\n   return my->get_withdraw_permissions_by_giver( account_id_or_name, start, limit );\n}\n\nvector<withdraw_permission_object> database_api_impl::get_withdraw_permissions_by_giver(\n                                      const std::string account_id_or_name,\n                                      withdraw_permission_id_type start,\n                                      uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_withdraw_permissions_by_giver;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<withdraw_permission_object> result;\n\n   const auto& withdraw_idx = _db.get_index_type<withdraw_permission_index>().indices().get<by_from>();\n   auto withdraw_index_end = withdraw_idx.end();\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto withdraw_itr = withdraw_idx.lower_bound(boost::make_tuple(account, object_id_type(start)));\n   while( withdraw_itr != withdraw_index_end && withdraw_itr->withdraw_from_account == account\n          && result.size() < limit )\n   {\n      result.push_back(*withdraw_itr);\n      ++withdraw_itr;\n   }\n   return result;\n}\n\nvector<withdraw_permission_object> database_api::get_withdraw_permissions_by_recipient(\n                                      const std::string account_id_or_name,\n                                      withdraw_permission_id_type start,\n                                      uint32_t limit)const\n{\n   return my->get_withdraw_permissions_by_recipient( account_id_or_name, start, limit );\n}\n\nvector<withdraw_permission_object> database_api_impl::get_withdraw_permissions_by_recipient(\n                                      const std::string account_id_or_name,\n                                      withdraw_permission_id_type start,\n                                      uint32_t limit)const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_withdraw_permissions_by_recipient;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<withdraw_permission_object> result;\n\n   const auto& withdraw_idx = _db.get_index_type<withdraw_permission_index>().indices().get<by_authorized>();\n   auto withdraw_index_end = withdraw_idx.end();\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto withdraw_itr = withdraw_idx.lower_bound(boost::make_tuple(account, object_id_type(start)));\n   while(withdraw_itr != withdraw_index_end && withdraw_itr->authorized_account == account && result.size() < limit)\n   {\n      result.push_back(*withdraw_itr);\n      ++withdraw_itr;\n   }\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n//  HTLC                                                            //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\noptional<htlc_object> database_api::get_htlc( htlc_id_type id, optional<bool> subscribe )const\n{\n   return my->get_htlc( id, subscribe );\n}\n\nfc::optional<htlc_object> database_api_impl::get_htlc( htlc_id_type id, optional<bool> subscribe )const\n{\n   auto obj = get_objects( { object_id_type(id) }, subscribe ).front();\n   if ( !obj.is_null() )\n   {\n      return fc::optional<htlc_object>(obj.template as<htlc_object>(GRAPHENE_MAX_NESTED_OBJECTS));\n   }\n   return fc::optional<htlc_object>();\n}\n\nvector<htlc_object> database_api::get_htlc_by_from( const std::string account_id_or_name,\n                                                    htlc_id_type start, uint32_t limit )const\n{\n   return my->get_htlc_by_from(account_id_or_name, start, limit);\n}\n\nvector<htlc_object> database_api_impl::get_htlc_by_from( const std::string account_id_or_name,\n                                                         htlc_id_type start, uint32_t limit ) const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_htlc_by;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<htlc_object> result;\n\n   const auto& htlc_idx = _db.get_index_type< htlc_index >().indices().get< by_from_id >();\n   auto htlc_index_end = htlc_idx.end();\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto htlc_itr = htlc_idx.lower_bound(boost::make_tuple(account, object_id_type(start)));\n\n   while(htlc_itr != htlc_index_end && htlc_itr->transfer.from == account && result.size() < limit)\n   {\n      result.push_back(*htlc_itr);\n      ++htlc_itr;\n   }\n   return result;\n}\n\nvector<htlc_object> database_api::get_htlc_by_to( const std::string account_id_or_name,\n                                                  htlc_id_type start, uint32_t limit )const\n{\n   return my->get_htlc_by_to(account_id_or_name, start, limit);\n}\n\nvector<htlc_object> database_api_impl::get_htlc_by_to( const std::string account_id_or_name,\n                                                       htlc_id_type start, uint32_t limit ) const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_htlc_by;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<htlc_object> result;\n\n   const auto& htlc_idx = _db.get_index_type< htlc_index >().indices().get< by_to_id >();\n   auto htlc_index_end = htlc_idx.end();\n   const account_id_type account = get_account_from_string(account_id_or_name)->get_id();\n   auto htlc_itr = htlc_idx.lower_bound(boost::make_tuple(account, object_id_type(start)));\n\n   while(htlc_itr != htlc_index_end && htlc_itr->transfer.to == account && result.size() < limit)\n   {\n      result.push_back(*htlc_itr);\n      ++htlc_itr;\n   }\n   return result;\n}\n\nvector<htlc_object> database_api::list_htlcs(const htlc_id_type start, uint32_t limit)const\n{\n   return my->list_htlcs(start, limit);\n}\n\nvector<htlc_object> database_api_impl::list_htlcs(const htlc_id_type start, uint32_t limit) const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_list_htlcs;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   vector<htlc_object> result;\n   const auto& htlc_idx = _db.get_index_type<htlc_index>().indices().get<by_id>();\n   auto itr = htlc_idx.lower_bound(object_id_type(start));\n   while(itr != htlc_idx.end() && result.size() < limit)\n   {\n      result.push_back(*itr);\n      ++itr;\n   }\n   return result;\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Tickets                                                          //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nvector<ticket_object> database_api::list_tickets(\n            const optional<uint32_t>& limit,\n            const optional<ticket_id_type>& start_id )const\n{\n   const auto& idx = my->_db.get_index_type<ticket_index>().indices().get<by_id>();\n   return my->get_objects_by_x< ticket_object,\n                                ticket_id_type\n                               >( &application_options::api_limit_get_tickets,\n                                  idx, limit, start_id );\n}\n\nvector<ticket_object> database_api::get_tickets_by_account(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<ticket_id_type>& start_id )const\n{\n   account_id_type account = my->get_account_from_string(account_name_or_id)->get_id();\n   const auto& idx = my->_db.get_index_type<ticket_index>().indices().get<by_account>();\n   return my->get_objects_by_x< ticket_object,\n                                ticket_id_type\n                               >( &application_options::api_limit_get_tickets,\n                                  idx, limit, start_id, account );\n}\n\n//////////////////////////////////////////////////////////////////////\n//                                                                  //\n// Private methods                                                  //\n//                                                                  //\n//////////////////////////////////////////////////////////////////////\n\nconst account_object* database_api_helper::get_account_from_string( const std::string& name_or_id,\n                                                                  bool throw_if_not_found ) const\n{\n   // TODO cache the result to avoid repeatly fetching from db\n   if( name_or_id.empty() )\n   {\n      if( throw_if_not_found )\n         FC_THROW_EXCEPTION( fc::assert_exception, \"no such account\" );\n      else\n         return nullptr;\n   }\n   const account_object* account_ptr = nullptr;\n   if( 0 != std::isdigit(name_or_id[0]) )\n      account_ptr = _db.find(fc::variant(name_or_id, 1).as<account_id_type>(1));\n   else\n   {\n      const auto& idx = _db.get_index_type<account_index>().indices().get<by_name>();\n      auto itr = idx.find(name_or_id);\n      if (itr != idx.end())\n         account_ptr = &(*itr);\n   }\n   if(throw_if_not_found)\n      FC_ASSERT( account_ptr, \"no such account\" );\n   return account_ptr;\n}\n\nconst asset_object* database_api_helper::get_asset_from_string( const std::string& symbol_or_id,\n                                                              bool throw_if_not_found ) const\n{\n   // TODO cache the result to avoid repeatly fetching from db\n   if( symbol_or_id.empty() )\n   {\n      if( throw_if_not_found )\n         FC_THROW_EXCEPTION( fc::assert_exception, \"no such asset\" );\n      else\n         return nullptr;\n   }\n   const asset_object* asset_ptr = nullptr;\n   if( 0 != std::isdigit(symbol_or_id[0]) )\n      asset_ptr = _db.find(fc::variant(symbol_or_id, 1).as<asset_id_type>(1));\n   else\n   {\n      const auto& idx = _db.get_index_type<asset_index>().indices().get<by_symbol>();\n      auto itr = idx.find(symbol_or_id);\n      if (itr != idx.end())\n         asset_ptr = &(*itr);\n   }\n   if(throw_if_not_found)\n      FC_ASSERT( asset_ptr, \"no such asset\" );\n   return asset_ptr;\n}\n\n// helper function\nvector<optional<extended_asset_object>> database_api_impl::get_assets( const vector<asset_id_type>& asset_ids,\n                                                                       optional<bool> subscribe )const\n{\n   bool to_subscribe = get_whether_to_subscribe( subscribe );\n   vector<optional<extended_asset_object>> result; result.reserve(asset_ids.size());\n   std::transform(asset_ids.begin(), asset_ids.end(), std::back_inserter(result),\n           [this,to_subscribe](asset_id_type id) -> optional<extended_asset_object> {\n      if(auto o = _db.find(id))\n      {\n         if( to_subscribe )\n            subscribe_to_item( id );\n         return extend_asset( *o );\n      }\n      return {};\n   });\n   return result;\n}\n\n// helper function\nvector<limit_order_object> database_api_impl::get_limit_orders( const asset_id_type a, const asset_id_type b,\n                                                                const uint32_t limit )const\n{\n   FC_ASSERT( _app_options, \"Internal error\" );\n   const auto configured_limit = _app_options->api_limit_get_limit_orders;\n   FC_ASSERT( limit <= configured_limit,\n              \"limit can not be greater than ${configured_limit}\",\n              (\"configured_limit\", configured_limit) );\n\n   const auto& limit_order_idx = _db.get_index_type<limit_order_index>();\n   const auto& limit_price_idx = limit_order_idx.indices().get<by_price>();\n\n   vector<limit_order_object> result;\n   result.reserve(limit*2);\n\n   uint32_t count = 0;\n   auto limit_itr = limit_price_idx.lower_bound(price::max(a,b));\n   auto limit_end = limit_price_idx.upper_bound(price::min(a,b));\n   while(limit_itr != limit_end && count < limit)\n   {\n      result.push_back(*limit_itr);\n      ++limit_itr;\n      ++count;\n   }\n   count = 0;\n   limit_itr = limit_price_idx.lower_bound(price::max(b,a));\n   limit_end = limit_price_idx.upper_bound(price::min(b,a));\n   while(limit_itr != limit_end && count < limit)\n   {\n      result.push_back(*limit_itr);\n      ++limit_itr;\n      ++count;\n   }\n\n   return result;\n}\n\nbool database_api_impl::is_impacted_account( const flat_set<account_id_type>& accounts)\n{\n   if( _subscribed_accounts.empty() || accounts.empty() )\n      return false;\n\n   return std::any_of(accounts.begin(), accounts.end(), [this](const account_id_type& account) {\n      return _subscribed_accounts.find(account) != _subscribed_accounts.end();\n   });\n}\n\nvoid database_api_impl::broadcast_updates( const vector<variant>& updates )\n{\n   if( !updates.empty() && _subscribe_callback ) {\n      auto capture_this = shared_from_this();\n      fc::async([capture_this,updates](){\n          if(capture_this->_subscribe_callback)\n            capture_this->_subscribe_callback( fc::variant(updates) );\n      });\n   }\n}\n\nvoid database_api_impl::broadcast_market_updates( const market_queue_type& queue)\n{\n   if( !queue.empty() )\n   {\n      auto capture_this = shared_from_this();\n      fc::async([capture_this, this, queue](){\n          for( const auto& item : queue )\n          {\n            auto sub = _market_subscriptions.find(item.first);\n            if( sub != _market_subscriptions.end() )\n                sub->second( fc::variant(item.second ) );\n          }\n      });\n   }\n}\n\nvoid database_api_impl::on_objects_removed( const vector<object_id_type>& ids,\n                                            const vector<const object*>& objs,\n                                            const flat_set<account_id_type>& impacted_accounts )\n{\n   handle_object_changed(_notify_remove_create, false, ids, impacted_accounts,\n      [objs](object_id_type id) -> const object* {\n         auto it = std::find_if(\n               objs.begin(), objs.end(),\n               [id](const object* o) {return o != nullptr && o->id == id;});\n\n         if (it != objs.end())\n            return *it;\n\n         return nullptr;\n      }\n   );\n}\n\nvoid database_api_impl::on_objects_new( const vector<object_id_type>& ids,\n                                        const flat_set<account_id_type>& impacted_accounts )\n{\n   handle_object_changed(_notify_remove_create, true, ids, impacted_accounts,\n      std::bind(&object_database::find_object, &_db, std::placeholders::_1)\n   );\n}\n\nvoid database_api_impl::on_objects_changed( const vector<object_id_type>& ids,\n                                            const flat_set<account_id_type>& impacted_accounts )\n{\n   handle_object_changed(false, true, ids, impacted_accounts,\n      std::bind(&object_database::find_object, &_db, std::placeholders::_1)\n   );\n}\n\nvoid database_api_impl::handle_object_changed( bool force_notify,\n                                               bool full_object,\n                                               const vector<object_id_type>& ids,\n                                               const flat_set<account_id_type>& impacted_accounts,\n                                               std::function<const object*(object_id_type id)> find_object )\n{\n   if( _subscribe_callback )\n   {\n      vector<variant> updates;\n\n      for(auto id : ids)\n      {\n         if( force_notify || is_subscribed_to_item(id) || is_impacted_account(impacted_accounts) )\n         {\n            if( full_object )\n            {\n               auto obj = find_object(id);\n               if( obj )\n               {\n                  updates.emplace_back( obj->to_variant() );\n               }\n            }\n            else\n            {\n               updates.emplace_back( fc::variant( id, 1 ) );\n            }\n         }\n      }\n\n      if( !updates.empty() )\n         broadcast_updates(updates);\n   }\n\n   if( !_market_subscriptions.empty() )\n   {\n      market_queue_type broadcast_queue;\n\n      for(auto id : ids)\n      {\n         if( id.is<call_order_id_type>() )\n         {\n            enqueue_if_subscribed_to_market<call_order_object>( find_object(id), broadcast_queue, full_object );\n         }\n         else if( id.is<limit_order_id_type>() )\n         {\n            enqueue_if_subscribed_to_market<limit_order_object>( find_object(id), broadcast_queue, full_object );\n         }\n         else if( id.is<force_settlement_id_type>() )\n         {\n            enqueue_if_subscribed_to_market<force_settlement_object>( find_object(id), broadcast_queue,\n                                                                      full_object );\n         }\n      }\n\n      if( !broadcast_queue.empty() )\n         broadcast_market_updates(broadcast_queue);\n   }\n}\n\n/** note: this method cannot yield because it is called in the middle of\n * apply a block.\n */\nvoid database_api_impl::on_applied_block()\n{\n   if (_block_applied_callback)\n   {\n      auto capture_this = shared_from_this();\n      block_id_type block_id = _db.head_block_id();\n      fc::async([this,capture_this,block_id](){\n         _block_applied_callback(fc::variant(block_id, 1));\n      });\n   }\n\n   if( _market_subscriptions.empty() )\n      return;\n\n   const auto& ops = _db.get_applied_operations();\n   map< std::pair<asset_id_type,asset_id_type>, vector<pair<operation, operation_result>> > subscribed_markets_ops;\n   for(const optional< operation_history_object >& o_op : ops)\n   {\n      if( !o_op.valid() )\n         continue;\n      const operation_history_object& op = *o_op;\n\n      optional< std::pair<asset_id_type,asset_id_type> > market;\n      switch(op.op.which())\n      {\n         /*  This is sent via the object_changed callback\n         case operation::tag<limit_order_create_operation>::value:\n            market = op.op.get<limit_order_create_operation>().get_market();\n            break;\n         */\n         case operation::tag<fill_order_operation>::value:\n            market = op.op.get<fill_order_operation>().get_market();\n            break;\n            /*\n         case operation::tag<limit_order_cancel_operation>::value:\n         */\n         default: break;\n      }\n      if( market.valid() && _market_subscriptions.count(*market) > 0 )\n         // FIXME this may cause fill_order_operation be pushed before order creation\n         subscribed_markets_ops[*market].emplace_back(std::make_pair(op.op, op.result));\n   }\n   /// we need to ensure the database_api is not deleted for the life of the async operation\n   auto capture_this = shared_from_this();\n   fc::async([this,capture_this,subscribed_markets_ops](){\n      for(auto item : subscribed_markets_ops)\n      {\n         auto itr = _market_subscriptions.find(item.first);\n         if(itr != _market_subscriptions.end())\n            itr->second(fc::variant(item.second, GRAPHENE_NET_MAX_NESTED_OBJECTS));\n      }\n   });\n}\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/database_api_helper.hxx",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\nnamespace graphene { namespace app {\n\nclass database_api_helper\n{\npublic:\n   database_api_helper( graphene::chain::database& db, const application_options* app_options );\n   explicit database_api_helper( const graphene::app::application& app );\n\n   // Member variables\n   graphene::chain::database& _db;\n   const application_options* _app_options = nullptr;\n\n   // Accounts\n   const account_object* get_account_from_string( const std::string& name_or_id,\n                                                  bool throw_if_not_found = true ) const;\n\n   // Assets\n   const asset_object* get_asset_from_string( const std::string& symbol_or_id,\n                                              bool throw_if_not_found = true ) const;\n\n   /// Template functions for simple list_X and get_X_by_T APIs, to reduce duplicate code\n   /// @{\n   template <typename X>\n   auto make_tuple_if_multiple(X x) const\n   { return x; }\n\n   template <typename... X>\n   auto make_tuple_if_multiple(X... x) const\n   { return std::make_tuple( x... ); }\n\n   template <typename T>\n   auto call_end_or_upper_bound( const T& t ) const\n   { return std::end( t ); }\n\n   template <typename T, typename... X>\n   auto call_end_or_upper_bound( const T& t, X... x ) const\n   { return t.upper_bound( make_tuple_if_multiple( x... ) ); }\n\n   template <typename OBJ_TYPE, typename OBJ_ID_TYPE, typename INDEX_TYPE, typename T, typename... X >\n   vector<OBJ_TYPE> get_objects_by_x(\n               T application_options::* app_opt_member_ptr,\n               const INDEX_TYPE& idx,\n               const optional<uint32_t>& olimit,\n               const optional<OBJ_ID_TYPE>& ostart_id,\n               X... x ) const\n   {\n      FC_ASSERT( _app_options, \"Internal error\" );\n      const auto configured_limit = _app_options->*app_opt_member_ptr;\n      uint64_t limit = olimit.valid() ? *olimit : configured_limit;\n      FC_ASSERT( limit <= configured_limit,\n                 \"limit can not be greater than ${configured_limit}\",\n                 (\"configured_limit\", configured_limit) );\n\n      vector<OBJ_TYPE> results;\n\n      OBJ_ID_TYPE start_obj_id = ostart_id.valid() ? *ostart_id : OBJ_ID_TYPE();\n      object_id_type start_id { start_obj_id };\n\n      auto lower_itr = idx.lower_bound( make_tuple_if_multiple( x..., start_id ) );\n      auto upper_itr = call_end_or_upper_bound( idx, x... );\n\n      results.reserve( limit );\n      while( lower_itr != upper_itr && results.size() < limit )\n      {\n         results.emplace_back( *lower_itr );\n         ++lower_itr;\n      }\n\n      return results;\n   }\n   /// @}\n\n};\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/database_api_impl.hxx",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/bloom_filter.hpp>\n#include \"database_api_helper.hxx\"\n\n#define GET_REQUIRED_FEES_MAX_RECURSION 4\n\nnamespace graphene { namespace app {\n\nusing market_queue_type = std::map< std::pair<graphene::chain::asset_id_type, graphene::chain::asset_id_type>,\n                                    std::vector<fc::variant> >;\n\nclass database_api_impl : public std::enable_shared_from_this<database_api_impl>, public database_api_helper\n{\n   public:\n      database_api_impl( graphene::chain::database& db, const application_options* app_options );\n      virtual ~database_api_impl();\n\n      // Objects\n      fc::variants get_objects( const vector<object_id_type>& ids, optional<bool> subscribe )const;\n\n      // Subscriptions\n      void set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create );\n      void set_auto_subscription( bool enable );\n      void set_pending_transaction_callback( std::function<void(const variant&)> cb );\n      void set_block_applied_callback( std::function<void(const variant& block_id)> cb );\n      void cancel_all_subscriptions(bool reset_callback, bool reset_market_subscriptions);\n\n      // Blocks and transactions\n      optional<maybe_signed_block_header> get_block_header( uint32_t block_num, bool with_witness_signature )const;\n      map<uint32_t, optional<maybe_signed_block_header>> get_block_header_batch(\n            const vector<uint32_t>& block_nums, bool with_witness_signatures )const;\n      optional<signed_block> get_block(uint32_t block_num)const;\n      processed_transaction get_transaction( uint32_t block_num, uint32_t trx_in_block )const;\n\n      // Globals\n      chain_property_object get_chain_properties()const;\n      global_property_object get_global_properties()const;\n      fc::variant_object get_config()const;\n      chain_id_type get_chain_id()const;\n      dynamic_global_property_object get_dynamic_global_properties()const;\n      object_id_type get_next_object_id( uint8_t space_id, uint8_t type_id, bool with_pending_transactions )const;\n\n      // Keys\n      vector<flat_set<account_id_type>> get_key_references( vector<public_key_type> key )const;\n      bool is_public_key_registered(string public_key) const;\n\n      // Accounts\n      vector<optional<account_object>> get_accounts( const vector<std::string>& account_names_or_ids,\n                                                     optional<bool> subscribe )const;\n      map<string, full_account, std::less<>> get_full_accounts( const vector<string>& names_or_ids,\n                                                                const optional<bool>& subscribe );\n      vector<account_statistics_object> get_top_voters(uint32_t limit)const;\n      optional<account_object> get_account_by_name( string name )const;\n      vector<account_id_type> get_account_references( const std::string account_id_or_name )const;\n      vector<optional<account_object>> lookup_account_names(const vector<string>& account_names)const;\n      map<string, account_id_type, std::less<>> lookup_accounts( const string& lower_bound_name,\n                                                   uint32_t limit,\n                                                   const optional<bool>& subscribe )const;\n      uint64_t get_account_count()const;\n\n      // Balances\n      vector<asset> get_account_balances( const std::string& account_name_or_id,\n                                          const flat_set<asset_id_type>& assets )const;\n      vector<asset> get_named_account_balances(const std::string& name, const flat_set<asset_id_type>& assets)const;\n      vector<balance_object> get_balance_objects( const vector<address>& addrs )const;\n      vector<asset> get_vested_balances( const vector<balance_id_type>& objs )const;\n      vector<vesting_balance_object> get_vesting_balances( const std::string account_id_or_name )const;\n\n      // Assets\n      uint64_t get_asset_count()const;\n      vector<optional<extended_asset_object>> get_assets( const vector<std::string>& asset_symbols_or_ids,\n                                                          optional<bool> subscribe )const;\n      vector<extended_asset_object>           list_assets(const string& lower_bound_symbol, uint32_t limit)const;\n      vector<optional<extended_asset_object>> lookup_asset_symbols(const vector<string>& symbols_or_ids)const;\n      vector<extended_asset_object>           get_assets_by_issuer(const std::string& issuer_name_or_id,\n                                                                   asset_id_type start, uint32_t limit)const;\n\n      // Markets / feeds\n      vector<limit_order_object>         get_limit_orders( const std::string& a, const std::string& b,\n                                                           uint32_t limit)const;\n      vector<limit_order_object>         get_limit_orders_by_account( const string& account_name_or_id,\n                                                                      const optional<uint32_t>& limit,\n                                                                      const optional<limit_order_id_type>& start_id );\n      vector<limit_order_object>         get_account_limit_orders( const string& account_name_or_id,\n                                                                   const string &base,\n                                                                   const string &quote, uint32_t limit,\n                                                                   optional<limit_order_id_type> ostart_id,\n                                                                   optional<price> ostart_price );\n      vector<call_order_object>          get_call_orders(const std::string& a, uint32_t limit)const;\n      vector<call_order_object>          get_call_orders_by_account(const std::string& account_name_or_id,\n                                                                    asset_id_type start, uint32_t limit)const;\n      vector<force_settlement_object>    get_settle_orders(const std::string& a, uint32_t limit)const;\n      vector<force_settlement_object>    get_settle_orders_by_account(const std::string& account_name_or_id,\n                                                                      force_settlement_id_type start,\n                                                                      uint32_t limit)const;\n      vector<collateral_bid_object>      get_collateral_bids( const std::string& asset,\n                                                              uint32_t limit, uint32_t start)const;\n\n      void subscribe_to_market( std::function<void(const variant&)> callback,\n                                const std::string& a, const std::string& b );\n      void unsubscribe_from_market(const std::string& a, const std::string& b);\n\n      market_ticker                      get_ticker( const string& base, const string& quote,\n                                                     bool skip_order_book = false )const;\n      market_volume                      get_24_volume( const string& base, const string& quote )const;\n      order_book                         get_order_book( const string& base, const string& quote,\n                                                         uint32_t limit )const;\n      vector<market_ticker>              get_top_markets( uint32_t limit )const;\n      vector<market_trade>               get_trade_history( const string& base, const string& quote,\n                                                            fc::time_point_sec start, fc::time_point_sec stop,\n                                                            uint32_t limit )const;\n      vector<market_trade>               get_trade_history_by_sequence( const string& base, const string& quote,\n                                                                        int64_t start, fc::time_point_sec stop,\n                                                                        uint32_t limit )const;\n\n      // Liquidity pools\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_one_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit,\n            const optional<liquidity_pool_id_type>& start_id,\n            const optional<bool>& with_statistics )const;\n      vector<optional<extended_liquidity_pool_object>> get_liquidity_pools(\n            const vector<liquidity_pool_id_type>& ids,\n            const optional<bool>& subscribe,\n            const optional<bool>& with_statistics )const;\n      vector<optional<extended_liquidity_pool_object>> get_liquidity_pools_by_share_asset(\n            const vector<std::string>& asset_symbols_or_ids,\n            const optional<bool>& subscribe,\n            const optional<bool>& with_statistics )const;\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit,\n            const optional<asset_id_type>& start_id,\n            const optional<bool>& with_statistics )const;\n\n      // Witnesses\n      vector<optional<witness_object>> get_witnesses(const vector<witness_id_type>& witness_ids)const;\n      fc::optional<witness_object> get_witness_by_account(const std::string& account_id_or_name)const;\n      map<string, witness_id_type, std::less<>> lookup_witness_accounts(\n            const string& lower_bound_name, uint32_t limit )const;\n      uint64_t get_witness_count()const;\n\n      // Committee members\n      vector<optional<committee_member_object>> get_committee_members(\n            const vector<committee_member_id_type>& committee_member_ids )const;\n      fc::optional<committee_member_object> get_committee_member_by_account(\n            const std::string& account_id_or_name )const;\n      map<string, committee_member_id_type, std::less<>> lookup_committee_member_accounts(\n            const string& lower_bound_name, uint32_t limit )const;\n      uint64_t get_committee_count()const;\n\n      // Workers\n      vector<worker_object> get_all_workers( const optional<bool>& is_expired )const;\n      vector<worker_object> get_workers_by_account(const std::string& account_id_or_name)const;\n      uint64_t get_worker_count()const;\n\n      // Votes\n      vector<variant> lookup_vote_ids( const vector<vote_id_type>& votes )const;\n\n      // Authority / validation\n      std::string get_transaction_hex(const signed_transaction& trx)const;\n      std::string get_transaction_hex_without_sig(const transaction& trx)const;\n\n      set<public_key_type> get_required_signatures( const signed_transaction& trx,\n                                                    const flat_set<public_key_type>& available_keys )const;\n      set<public_key_type> get_potential_signatures( const signed_transaction& trx )const;\n      set<address> get_potential_address_signatures( const signed_transaction& trx )const;\n      bool verify_authority( const signed_transaction& trx )const;\n      bool verify_account_authority( const string& account_name_or_id,\n                                     const flat_set<public_key_type>& signers )const;\n      processed_transaction validate_transaction( const signed_transaction& trx )const;\n      vector< fc::variant > get_required_fees( const vector<operation>& ops,\n                                               const std::string& asset_id_or_symbol )const;\n\n      // Proposed transactions\n      vector<proposal_object> get_proposed_transactions( const std::string account_id_or_name )const;\n\n      // Blinded balances\n      vector<blinded_balance_object> get_blinded_balances( const flat_set<commitment_type>& commitments )const;\n\n      // Withdrawals\n      vector<withdraw_permission_object> get_withdraw_permissions_by_giver( const std::string account_id_or_name,\n                                                                            withdraw_permission_id_type start,\n                                                                            uint32_t limit )const;\n      vector<withdraw_permission_object> get_withdraw_permissions_by_recipient( const std::string account_id_or_name,\n                                                                                withdraw_permission_id_type start,\n                                                                                uint32_t limit )const;\n\n      // HTLC\n      optional<htlc_object> get_htlc( htlc_id_type id, optional<bool> subscribe ) const;\n      vector<htlc_object> get_htlc_by_from( const std::string account_id_or_name,\n                                            htlc_id_type start, uint32_t limit ) const;\n      vector<htlc_object> get_htlc_by_to( const std::string account_id_or_name,\n                                          htlc_id_type start, uint32_t limit) const;\n      vector<htlc_object> list_htlcs(const htlc_id_type lower_bound_id, uint32_t limit) const;\n\n   //private:\n\n      ////////////////////////////////////////////////\n      // Accounts\n      ////////////////////////////////////////////////\n\n      ////////////////////////////////////////////////\n      // Assets\n      ////////////////////////////////////////////////\n\n      template<class ASSET>\n      extended_asset_object extend_asset( ASSET&& a )const\n      {\n         asset_id_type id = a.get_id();\n         extended_asset_object result = extended_asset_object( std::forward<ASSET>( a ) );\n         if( amount_in_collateral_index )\n         {\n            result.total_in_collateral = amount_in_collateral_index->get_amount_in_collateral( id );\n            if( result.bitasset_data_id.valid() )\n               result.total_backing_collateral = amount_in_collateral_index->get_backing_collateral( id );\n         }\n         return result;\n      }\n\n      // helper function\n      vector<optional<extended_asset_object>> get_assets( const vector<asset_id_type>& asset_ids,\n                                                          optional<bool> subscribe = optional<bool>() )const;\n\n      ////////////////////////////////////////////////\n      // Markets\n      ////////////////////////////////////////////////\n\n      // helper function\n      vector<limit_order_object> get_limit_orders( const asset_id_type a, const asset_id_type b,\n                                                   const uint32_t limit )const;\n\n      ////////////////////////////////////////////////\n      // Liquidity pools\n      ////////////////////////////////////////////////\n\n      template<class LP>\n      extended_liquidity_pool_object extend_liquidity_pool( LP&& a, bool with_stats )const\n      {\n         liquidity_pool_id_type id = a.get_id();\n         extended_liquidity_pool_object result = extended_liquidity_pool_object( std::forward<LP>( a ) );\n         if( with_stats && _app_options && _app_options->has_market_history_plugin )\n         {\n            liquidity_pool_ticker_id_type ticker_id( id.instance );\n            const liquidity_pool_ticker_object* ticker = _db.find( ticker_id );\n            if( ticker )\n               result.statistics = *ticker;\n         }\n         return result;\n      }\n\n      // template function to reduce duplicate code\n      template <typename INDEX_TAG, typename... X>\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_asset_x(\n                  const optional<uint32_t>& olimit,\n                  const optional<liquidity_pool_id_type>& ostart_id,\n                  const optional<bool>& with_statistics,\n                  X... x )const\n      {\n         FC_ASSERT( _app_options, \"Internal error\" );\n         const auto configured_limit = _app_options->api_limit_get_liquidity_pools;\n         uint32_t limit = olimit.valid() ? *olimit : configured_limit;\n         FC_ASSERT( limit <= configured_limit,\n                    \"limit can not be greater than ${configured_limit}\",\n                    (\"configured_limit\", configured_limit) );\n\n         bool with_stats = ( with_statistics.valid() && *with_statistics );\n\n         vector<extended_liquidity_pool_object> results;\n\n         liquidity_pool_id_type start_pool_id = ostart_id.valid() ? *ostart_id : liquidity_pool_id_type();\n         object_id_type start_id { start_pool_id };\n\n         const auto& idx = _db.get_index_type<liquidity_pool_index>().indices().get<INDEX_TAG>();\n\n         auto lower_itr = idx.lower_bound( make_tuple_if_multiple( x..., start_id ) );\n         auto upper_itr = call_end_or_upper_bound( idx, x... );\n\n         results.reserve( limit );\n         for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr )\n         {\n            results.emplace_back( extend_liquidity_pool( *lower_itr, with_stats ) );\n         }\n\n         return results;\n      }\n\n      ////////////////////////////////////////////////\n      // Subscription\n      ////////////////////////////////////////////////\n\n      // Decides whether to subscribe using member variables and given parameter\n      bool get_whether_to_subscribe( optional<bool> subscribe )const\n      {\n         if( !_subscribe_callback )\n            return false;\n         if( subscribe.valid() )\n            return *subscribe;\n         return _enabled_auto_subscription;\n      }\n\n      // Note:\n      //   Different type of object_id<T> objects could become identical after packed.\n      //   For example, both `account_id_type a=1.2.0` and `asset_id_type b=1.3.0` will become `0` after packed.\n      //   In order to avoid collision, we don't use a template function here, instead, we implicitly convert all\n      //   object IDs to `object_id_type` when subscribing.\n      //\n      //   If need to subscribe to other data types, override this function with the types as parameter.\n      //   For example, we had a `get_subscription_key( const public_key_type& item )` function here, which was\n      //   removed lately since we no longer subscribe to public keys.\n      vector<char> get_subscription_key( const object_id_type& item )const\n      {\n         return fc::raw::pack(item);\n      }\n\n      template<typename T>\n      void subscribe_to_item( const T& item )const\n      {\n         if( !_subscribe_callback )\n            return;\n\n         vector<char> key = get_subscription_key( object_id_type(item) );\n         if( !_subscribe_filter.contains( key.data(), key.size() ) )\n         {\n            _subscribe_filter.insert( key.data(), key.size() );\n         }\n      }\n\n      template<typename T>\n      bool is_subscribed_to_item( const T& item )const\n      {\n         if( !_subscribe_callback )\n            return false;\n\n         vector<char> key = get_subscription_key( object_id_type(item) );\n         return _subscribe_filter.contains( key.data(), key.size() );\n      }\n\n      // for full-account subscription\n      bool is_impacted_account( const flat_set<account_id_type>& accounts );\n\n      // for market subscription\n      template<typename T>\n      const std::pair<asset_id_type,asset_id_type> get_order_market( const T& order )\n      {\n         return order.get_market();\n      }\n\n      // for market subscription\n      const std::pair<asset_id_type,asset_id_type> get_order_market( const force_settlement_object& order )\n      {\n         // TODO cache the result to avoid repeatly fetching from db\n         asset_id_type backing_id = order.balance.asset_id( _db ).bitasset_data( _db ).options.short_backing_asset;\n         auto tmp = std::make_pair( order.balance.asset_id, backing_id );\n         if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );\n         return tmp;\n      }\n\n      template<typename T>\n      void enqueue_if_subscribed_to_market(const object* obj, market_queue_type& queue, bool full_object=true)\n      {\n         const T* order = dynamic_cast<const T*>(obj);\n         FC_ASSERT( order != nullptr);\n\n         const auto& market = get_order_market( *order );\n\n         auto sub = _market_subscriptions.find( market );\n         if( sub != _market_subscriptions.end() ) {\n            queue[market].emplace_back( full_object ? obj->to_variant() : fc::variant(obj->id, 1) );\n         }\n      }\n\n      void broadcast_updates( const vector<variant>& updates );\n      void broadcast_market_updates( const market_queue_type& queue);\n      void handle_object_changed( bool force_notify,\n                                  bool full_object,\n                                  const vector<object_id_type>& ids,\n                                  const flat_set<account_id_type>& impacted_accounts,\n                                  std::function<const object*(object_id_type id)> find_object );\n\n      /** called every time a block is applied to report the objects that were changed */\n      void on_objects_new(const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts);\n      void on_objects_changed(const vector<object_id_type>& ids, const flat_set<account_id_type>& impacted_accounts);\n      void on_objects_removed(const vector<object_id_type>& ids, const vector<const object*>& objs,\n                              const flat_set<account_id_type>& impacted_accounts);\n      void on_applied_block();\n\n      ////////////////////////////////////////////////\n      // Member variables\n      ////////////////////////////////////////////////\n\n      bool _notify_remove_create = false;\n      bool _enabled_auto_subscription = true;\n\n      mutable fc::bloom_filter  _subscribe_filter;\n      std::set<account_id_type> _subscribed_accounts;\n\n      std::function<void(const fc::variant&)> _subscribe_callback;\n      std::function<void(const fc::variant&)> _pending_trx_callback;\n      std::function<void(const fc::variant&)> _block_applied_callback;\n\n      boost::signals2::scoped_connection _new_connection;\n      boost::signals2::scoped_connection _change_connection;\n      boost::signals2::scoped_connection _removed_connection;\n      boost::signals2::scoped_connection _applied_block_connection;\n      boost::signals2::scoped_connection _pending_trx_connection;\n\n      map< pair<asset_id_type,asset_id_type>, std::function<void(const variant&)> > _market_subscriptions;\n\n      const graphene::api_helper_indexes::amount_in_collateral_index* amount_in_collateral_index;\n      const graphene::api_helper_indexes::asset_in_liquidity_pools_index* asset_in_liquidity_pools_index;\n      const graphene::api_helper_indexes::next_object_ids_index* next_object_ids_index;\n};\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/include/graphene/app/api.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/database_api.hpp>\n\n#include <graphene/protocol/types.hpp>\n\n#include <graphene/market_history/market_history_plugin.hpp>\n#include <graphene/grouped_orders/grouped_orders_plugin.hpp>\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n\n#include <graphene/elasticsearch/elasticsearch_plugin.hpp>\n\n#include <graphene/debug_witness/debug_api.hpp>\n\n#include <graphene/net/node.hpp>\n\n#include <fc/api.hpp>\n#include <fc/optional.hpp>\n#include <fc/crypto/elliptic.hpp>\n#include <fc/network/ip.hpp>\n\n#include <boost/container/flat_set.hpp>\n\n#include <functional>\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace graphene { namespace app {\n   using namespace graphene::chain;\n   using namespace graphene::market_history;\n   using namespace graphene::grouped_orders;\n   using namespace graphene::custom_operations;\n\n   using std::string;\n   using std::vector;\n   using std::map;\n\n   class application;\n\n   /**\n    * @brief The history_api class implements the RPC API for account history\n    *\n    * This API contains methods to access account histories\n    */\n   class history_api\n   {\n      public:\n         explicit history_api(application& app);\n\n         struct history_operation_detail\n         {\n            uint32_t total_count = 0;\n            vector<operation_history_object> operation_history_objs;\n         };\n\n         /**\n          * @brief Get the history of operations related to the specified account\n          * @param account_name_or_id The account name or ID whose history should be queried\n          * @param stop ID of the earliest operation to retrieve\n          * @param limit Maximum number of operations to retrieve, must not exceed the configured value of\n          *              @a api_limit_get_account_history\n          * @param start ID of the most recent operation to retrieve\n          * @return A list of operations related to the specified account, ordered from most recent to oldest.\n          */\n         vector<operation_history_object> get_account_history(\n            const std::string& account_name_or_id,\n            operation_history_id_type stop = operation_history_id_type(),\n            uint32_t limit = application_options::get_default().api_limit_get_account_history,\n            operation_history_id_type start = operation_history_id_type()\n         )const;\n\n         /**\n          * @brief Get the history of operations related to the specified account no later than the specified time\n          * @param account_name_or_id The account name or ID whose history should be queried\n          * @param limit Maximum number of operations to retrieve, must not exceed the configured value of\n          *              @a api_limit_get_account_history\n          * @param start the time point to start looping back through history\n          * @return A list of operations related to the specified account, ordered from most recent to oldest.\n          *\n          * @note\n          * 1. If @p account_name_or_id cannot be tied to an account, an empty list will be returned\n          * 2. @p limit can be omitted or be @a null, if so the configured value of\n          *       @a api_limit_get_account_history will be used\n          * 3. @p start can be omitted or be @a null, if so the api will return the \"first page\" of the history\n          * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n          *    parameters in the middle cannot be omitted (but can be @a null).\n          */\n         vector<operation_history_object> get_account_history_by_time(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<fc::time_point_sec>& start = optional<fc::time_point_sec>()\n         )const;\n\n         /**\n          * @brief Get the history of operations related to the specified account filtering by operation types\n          * @param account_name_or_id The account name or ID whose history should be queried\n          * @param operation_types The IDs of the operation we want to get operations in the account\n          *                        ( 0 = transfer , 1 = limit order create, ...)\n          * @param start the sequence number where to start looping back through the history\n          * @param limit the max number of entries to return (from start number), must not exceed the configured\n          *              value of @a api_limit_get_account_history_by_operations\n          * @return history_operation_detail\n          */\n         history_operation_detail get_account_history_by_operations(\n            const std::string& account_name_or_id,\n            const flat_set<uint16_t>& operation_types,\n            uint32_t start,\n            uint32_t limit\n         )const;\n\n         /**\n          * @brief Get the history of operations related to the specified account filtering by operation type\n          * @param account_name_or_id The account name or ID whose history should be queried\n          * @param operation_type The type of the operation we want to get operations in the account\n          *                       ( 0 = transfer , 1 = limit order create, ...)\n          * @param stop ID of the earliest operation to retrieve\n          * @param limit Maximum number of operations to retrieve, must not exceed the configured value of\n          *              @a api_limit_get_account_history_operations\n          * @param start ID of the most recent operation to retrieve\n          * @return A list of operations related to the specified account, ordered from most recent to oldest.\n          */\n         vector<operation_history_object> get_account_history_operations(\n            const std::string& account_name_or_id,\n            int64_t operation_type,\n            operation_history_id_type start = operation_history_id_type(),\n            operation_history_id_type stop = operation_history_id_type(),\n            uint32_t limit = application_options::get_default().api_limit_get_account_history_operations\n         )const;\n\n         /**\n          * @brief Get the history of operations related to the specified account referenced\n          *        by an event numbering specific to the account. The current number of operations\n          *        for the account can be found in the account statistics (or use 0 for start).\n          * @param account_name_or_id The account name or ID whose history should be queried\n          * @param stop Sequence number of earliest operation. 0 is default and will\n          *             query 'limit' number of operations.\n          * @param limit Maximum number of operations to retrieve, must not exceed the configured value of\n          *              @a api_limit_get_relative_account_history\n          * @param start Sequence number of the most recent operation to retrieve.\n          *              0 is default, which will start querying from the most recent operation.\n          * @return A list of operations related to the specified account, ordered from most recent to oldest.\n          */\n         vector<operation_history_object> get_relative_account_history(\n               const std::string& account_name_or_id,\n               uint64_t stop = 0,\n               uint32_t limit = application_options::get_default().api_limit_get_relative_account_history,\n               uint64_t start = 0) const;\n\n         /**\n          * @brief Get all operations within a block or a transaction, including virtual operations\n          * @param block_num the number (height) of the block to fetch\n          * @param trx_in_block the sequence of a transaction in the block, starts from @a 0, optional.\n          *                     If specified, will return only operations of that transaction.\n          *                     If omitted, will return all operations in the specified block.\n          * @return a list of @a operation_history objects ordered by ID\n          *\n          * @note the data is fetched from the @a account_history plugin, so results may be\n          *       incomplete due to the @a partial-operations option configured in the API node.\n          *       To get complete data, it is recommended to query from ElasticSearch where the data is\n          *       maintained by the @a elastic_search plugin.\n          */\n         vector<operation_history_object> get_block_operation_history(\n               uint32_t block_num,\n               const optional<uint16_t>& trx_in_block = {} ) const;\n\n         /**\n          * @brief Get all operations, including virtual operations, within the most recent block\n          *        (no later than the specified time) containing at least one operation\n          * @param start time point, optional, if omitted, the data of the latest block containing at least\n          *              one operation will be returned\n          * @return a list of @a operation_history objects ordered by ID in descending order\n          *\n          * @note the data is fetched from the @a account_history plugin, so results may be\n          *       incomplete or incorrect due to the @a partial-operations option configured in the API node.\n          *       To get complete data, it is recommended to query from ElasticSearch where the data is\n          *       maintained by the @a elastic_search plugin.\n          */\n         vector<operation_history_object> get_block_operations_by_time(\n               const optional<fc::time_point_sec>& start = optional<fc::time_point_sec>() ) const;\n\n         /**\n          * @brief Get details of order executions occurred most recently in a trading pair\n          * @param a Asset symbol or ID in a trading pair\n          * @param b The other asset symbol or ID in the trading pair\n          * @param limit Maximum records to return\n          * @return a list of order_history objects, in \"most recent first\" order\n          */\n         vector<order_history_object> get_fill_order_history(\n               const std::string& a,\n               const std::string& b,\n               uint32_t limit )const;\n\n         /**\n          * @brief Get OHLCV data of a trading pair in a time range\n          * @param a Asset symbol or ID in a trading pair\n          * @param b The other asset symbol or ID in the trading pair\n          * @param bucket_seconds Length of each time bucket in seconds.\n          * Note: it need to be within result of get_market_history_buckets() API, otherwise no data will be returned\n          * @param start The start of a time range, E.G. \"2018-01-01T00:00:00\"\n          * @param end The end of the time range\n          * @return A list of OHLCV data, in \"least recent first\" order.\n          * If there are more records in the specified time range than the configured value of\n          *    @a api_limit_get_market_history, only the first records will be returned.\n          */\n         vector<bucket_object> get_market_history( const std::string& a, const std::string& b,\n                                                   uint32_t bucket_seconds,\n                                                   const fc::time_point_sec& start,\n                                                   const fc::time_point_sec& end )const;\n\n         /**\n          * @brief Get OHLCV time bucket lengths supported (configured) by this API server\n          * @return A list of time bucket lengths in seconds. E.G. if the result contains a number \"300\",\n          * it means this API server supports OHLCV data aggregated in 5-minute buckets.\n          */\n         flat_set<uint32_t> get_market_history_buckets()const;\n\n         /**\n          * @brief Get history of a liquidity pool\n          * @param pool_id ID of the liquidity pool to query\n          * @param start A UNIX timestamp. Optional.\n          *              If specified, only the operations occurred not later than this time will be returned.\n          * @param stop  A UNIX timestamp. Optional.\n          *              If specified, only the operations occurred later than this time will be returned.\n          * @param limit Maximum quantity of operations in the history to retrieve. Optional.\n          *              If not specified, the configured value of\n          *                @a api_limit_get_liquidity_pool_history will be used.\n          *              If specified, it must not exceed the configured value of\n          *                @a api_limit_get_liquidity_pool_history.\n          * @param operation_type Optional. If specified, only the operations whose type is the specified type\n          *                       will be returned. Otherwise all operations will be returned.\n          * @return operation history of the liquidity pool, ordered by time, most recent first.\n          *\n          * @note\n          * 1. The time must be UTC. The range is (stop, start].\n          * 2. In case when there are more operations than @p limit occurred in the same second, this API only returns\n          *    the most recent records, the rest records can be retrieved with the\n          *    @ref get_liquidity_pool_history_by_sequence API.\n          * 3. List of operation type code: 59-creation, 60-deletion, 61-deposit, 62-withdrawal, 63-exchange.\n          * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n          *    parameters in the middle cannot be omitted (but can be @a null).\n          */\n         vector<liquidity_pool_history_object> get_liquidity_pool_history(\n               liquidity_pool_id_type pool_id,\n               const optional<fc::time_point_sec>& start = optional<fc::time_point_sec>(),\n               const optional<fc::time_point_sec>& stop = optional<fc::time_point_sec>(),\n               const optional<uint32_t>& limit = optional<uint32_t>(),\n               const optional<int64_t>& operation_type = optional<int64_t>() )const;\n\n         /**\n          * @brief Get history of a liquidity pool\n          * @param pool_id ID of the liquidity pool to query\n          * @param start An Integer. Optional.\n          *              If specified, only the operations whose sequences are not greater than this will be returned.\n          * @param stop  A UNIX timestamp. Optional.\n          *              If specified, only operations occurred later than this time will be returned.\n          * @param limit Maximum quantity of operations in the history to retrieve. Optional.\n          *              If not specified, the configured value of\n          *                @a api_limit_get_liquidity_pool_history will be used.\n          *              If specified, it must not exceed the configured value of\n          *                @a api_limit_get_liquidity_pool_history.\n          * @param operation_type Optional. If specified, only the operations whose type is the specified type\n          *                       will be returned. Otherwise all operations will be returned.\n          * @return operation history of the liquidity pool, ordered by time, most recent first.\n          *\n          * @note\n          * 1. The time must be UTC. The range is (stop, start].\n          * 2. List of operation type code: 59-creation, 60-deletion, 61-deposit, 62-withdrawal, 63-exchange.\n          * 3. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n          *    parameters in the middle cannot be omitted (but can be @a null).\n          */\n         vector<liquidity_pool_history_object> get_liquidity_pool_history_by_sequence(\n               liquidity_pool_id_type pool_id,\n               const optional<uint64_t>& start = optional<uint64_t>(),\n               const optional<fc::time_point_sec>& stop = optional<fc::time_point_sec>(),\n               const optional<uint32_t>& limit = optional<uint32_t>(),\n               const optional<int64_t>& operation_type = optional<int64_t>() )const;\n\n      private:\n           application& _app;\n   };\n\n   /**\n    * @brief Block api\n    */\n   class block_api\n   {\n   public:\n      explicit block_api(const graphene::chain::database& db);\n\n      /**\n          * @brief Get signed blocks\n          * @param block_num_from The lowest block number\n          * @param block_num_to The highest block number\n          * @return A list of signed blocks from block_num_from till block_num_to\n          */\n      vector<optional<signed_block>> get_blocks(uint32_t block_num_from, uint32_t block_num_to)const;\n\n   private:\n      const graphene::chain::database& _db;\n   };\n\n\n   /**\n    * @brief The network_broadcast_api class allows broadcasting of transactions.\n    */\n   class network_broadcast_api : public std::enable_shared_from_this<network_broadcast_api>\n   {\n      public:\n         explicit network_broadcast_api(application& a);\n\n         struct transaction_confirmation\n         {\n            transaction_id_type   id;\n            uint32_t              block_num;\n            uint32_t              trx_num;\n            processed_transaction trx;\n         };\n\n         using confirmation_callback = std::function<void(variant/*transaction_confirmation*/)>;\n\n         /**\n          * @brief Broadcast a transaction to the network\n          * @param trx The transaction to broadcast\n          *\n          * The transaction will be checked for validity in the local database prior to broadcasting. If it fails to\n          * apply locally, an error will be thrown and the transaction will not be broadcast.\n          */\n         void broadcast_transaction(const precomputable_transaction& trx);\n\n         /** This version of broadcast transaction registers a callback method that will be called when the\n          * transaction is included into a block.  The callback method includes the transaction id, block number,\n          * and transaction number in the block.\n          * @param cb the callback method\n          * @param trx the transaction\n          */\n         void broadcast_transaction_with_callback( confirmation_callback cb, const precomputable_transaction& trx);\n\n         /** This version of broadcast transaction waits until the transaction is included into a block,\n          *  then the transaction id, block number, and transaction number in the block will be returned.\n          * @param trx the transaction\n          * @return info about the block including the transaction\n          */\n         fc::variant broadcast_transaction_synchronous(const precomputable_transaction& trx);\n\n         /**\n          * @brief Broadcast a signed block to the network\n          * @param block The signed block to broadcast\n          */\n         void broadcast_block( const signed_block& block );\n\n         /**\n          * @brief Not reflected, thus not accessible to API clients.\n          *\n          * This function is registered to receive the applied_block\n          * signal from the chain database when a block is received.\n          * It then dispatches callbacks to clients who have requested\n          * to be notified when a particular txid is included in a block.\n          */\n         void on_applied_block( const signed_block& b );\n      private:\n         boost::signals2::scoped_connection             _applied_block_connection;\n         map<transaction_id_type,confirmation_callback> _callbacks;\n         application&                                   _app;\n   };\n\n   /**\n    * @brief The network_node_api class allows maintenance of p2p connections.\n    */\n   class network_node_api\n   {\n      public:\n         explicit network_node_api(application& a);\n\n         /**\n          * @brief Return general network information, such as p2p port\n          */\n         fc::variant_object get_info() const;\n\n         /**\n          * @brief add_node Connect to a new peer\n          * @param ep The IP/Port of the peer to connect to\n          */\n         void add_node(const fc::ip::endpoint& ep);\n\n         /**\n          * @brief Get status of all current connections to peers\n          */\n         std::vector<net::peer_status> get_connected_peers() const;\n\n         /**\n          * @brief Get advanced node parameters, such as desired and max\n          *        number of connections\n          */\n         fc::variant_object get_advanced_node_parameters() const;\n\n         /**\n          * @brief Set advanced node parameters, such as desired and max\n          *        number of connections\n          * @param params a JSON object containing the name/value pairs for the parameters to set\n          */\n         void set_advanced_node_parameters(const fc::variant_object& params);\n\n         /**\n          * @brief Return list of potential peers\n          */\n         std::vector<net::potential_peer_record> get_potential_peers() const;\n\n      private:\n         application& _app;\n   };\n\n   /**\n    * @brief The crypto_api class allows computations related to blinded transfers.\n    */\n   class crypto_api\n   {\n      public:\n\n         struct verify_range_result\n         {\n            bool        success;\n            uint64_t    min_val;\n            uint64_t    max_val;\n         };\n\n         struct verify_range_proof_rewind_result\n         {\n            bool                          success;\n            uint64_t                      min_val;\n            uint64_t                      max_val;\n            uint64_t                      value_out;\n            fc::ecc::blind_factor_type    blind_out;\n            string                        message_out;\n         };\n\n         /**\n          * @brief Generates a pedersen commitment: *commit = blind * G + value * G2.\n          * The commitment is 33 bytes, the blinding factor is 32 bytes.\n          * For more information about pederson commitment check url https://en.wikipedia.org/wiki/Commitment_scheme\n          * @param blind Sha-256 blind factor type\n          * @param value Positive 64-bit integer value\n          * @return A 33-byte pedersen commitment: *commit = blind * G + value * G2\n          */\n         fc::ecc::commitment_type blind( const fc::ecc::blind_factor_type& blind, uint64_t value ) const;\n\n         /**\n          * @brief Get sha-256 blind factor type\n          * @param blinds_in List of sha-256 blind factor types\n          * @param non_neg 32-bit integer value\n          * @return A blind factor type\n          */\n         fc::ecc::blind_factor_type blind_sum( const std::vector<blind_factor_type>& blinds_in,\n                                               uint32_t non_neg ) const;\n\n         /**\n          * @brief Verifies that commits + neg_commits + excess == 0\n          * @param commits_in List of 33-byte pedersen commitments\n          * @param neg_commits_in List of 33-byte pedersen commitments\n          * @param excess Sum of two list of 33-byte pedersen commitments\n          *               where sums the first set and subtracts the second\n          * @return Boolean - true in event of commits + neg_commits + excess == 0, otherwise false\n          */\n         bool verify_sum(\n            const std::vector<commitment_type>& commits_in,\n            const std::vector<commitment_type>& neg_commits_in,\n            int64_t excess\n         ) const;\n\n         /**\n          * @brief Verifies range proof for 33-byte pedersen commitment\n          * @param commit 33-byte pedersen commitment\n          * @param proof List of characters\n          * @return A structure with success, min and max values\n          */\n         verify_range_result verify_range( const fc::ecc::commitment_type& commit,\n                                           const std::vector<char>& proof ) const;\n\n         /**\n          * @brief Proves with respect to min_value the range for pedersen\n          * commitment which has the provided blinding factor and value\n          * @param min_value Positive 64-bit integer value\n          * @param commit 33-byte pedersen commitment\n          * @param commit_blind Sha-256 blind factor type for the correct digits\n          * @param nonce Sha-256 blind factor type for our non-forged signatures\n          * @param base10_exp Exponents base 10 in range [-1 ; 18] inclusively\n          * @param min_bits 8-bit positive integer, must be in range [0 ; 64] inclusively\n          * @param actual_value 64-bit positive integer, must be greater or equal min_value\n          * @return A list of characters as proof in proof\n          */\n         std::vector<char> range_proof_sign( uint64_t min_value,\n                                             const commitment_type& commit,\n                                             const blind_factor_type& commit_blind,\n                                             const blind_factor_type& nonce,\n                                             int8_t base10_exp,\n                                             uint8_t min_bits,\n                                             uint64_t actual_value ) const;\n\n         /**\n          * @brief Verifies range proof rewind for 33-byte pedersen commitment\n          * @param nonce Sha-256 blind refactor type\n          * @param commit 33-byte pedersen commitment\n          * @param proof List of characters\n          * @return A structure with success, min, max, value_out, blind_out and message_out values\n          */\n         verify_range_proof_rewind_result verify_range_proof_rewind( const blind_factor_type& nonce,\n                                                                     const fc::ecc::commitment_type& commit,\n                                                                     const std::vector<char>& proof ) const;\n\n         /**\n          * @brief Gets \"range proof\" info. The cli_wallet includes functionality for sending blind transfers\n          * in which the values of the input and outputs amounts are “blinded.”\n          * In the case where a transaction produces two or more outputs, (e.g. an amount to the intended\n          * recipient plus “change” back to the sender),\n          * a \"range proof\" must be supplied to prove that none of the outputs commit to a negative value.\n          * @param proof List of proof's characters\n          * @return A range proof info structure with exponent, mantissa, min and max values\n          */\n         fc::ecc::range_proof_info range_get_info( const std::vector<char>& proof ) const;\n   };\n\n   /**\n    * @brief The asset_api class allows query of info about asset holders.\n    */\n   class asset_api\n   {\n      public:\n         explicit asset_api(graphene::app::application& app);\n\n         struct account_asset_balance\n         {\n            string          name;\n            account_id_type account_id;\n            share_type      amount;\n         };\n         struct asset_holders\n         {\n            asset_id_type   asset_id;\n            int64_t         count;\n         };\n\n         /**\n          * @brief Get asset holders for a specific asset\n          * @param asset_symbol_or_id The specific asset symbol or ID\n          * @param start The start index\n          * @param limit Maximum number of accounts to retrieve, must not exceed the configured value of\n          *              @a api_limit_get_asset_holders\n          * @return A list of asset holders for the specified asset\n          */\n         vector<account_asset_balance> get_asset_holders( const std::string& asset_symbol_or_id,\n                                                          uint32_t start, uint32_t limit  )const;\n\n         /**\n          * @brief Get asset holders count for a specific asset\n          * @param asset_symbol_or_id The specific asset symbol or id\n          * @return Holders count for the specified asset\n          */\n         int64_t get_asset_holders_count( const std::string& asset_symbol_or_id )const;\n\n         /**\n          * @brief Get all asset holders\n          * @return A list of all asset holders\n          */\n         vector<asset_holders> get_all_asset_holders() const;\n\n      private:\n         graphene::app::application& _app;\n         graphene::chain::database& _db;\n   };\n\n   /**\n    * @brief the orders_api class exposes access to data processed with grouped orders plugin.\n    */\n   class orders_api\n   {\n      public:\n         explicit orders_api(application& app);\n\n         /**\n          * @brief summary data of a group of limit orders\n          */\n         struct limit_order_group\n         {\n            explicit limit_order_group( const std::pair<limit_order_group_key,limit_order_group_data>& p )\n               :  min_price( p.first.min_price ),\n                  max_price( p.second.max_price ),\n                  total_for_sale( p.second.total_for_sale )\n                  {}\n            limit_order_group() = default;\n\n            price         min_price; ///< possible lowest price in the group\n            price         max_price; ///< possible highest price in the group\n            share_type    total_for_sale; ///< total amount of asset for sale, asset id is min_price.base.asset_id\n         };\n\n         /**\n          * @brief Get tracked groups configured by the server.\n          * @return A list of numbers which indicate configured groups, of those, 1 means 0.01% diff on price.\n          */\n         flat_set<uint16_t> get_tracked_groups()const;\n\n         /**\n          * @brief Get grouped limit orders in given market.\n          *\n          * @param base_asset symbol or ID of asset being sold\n          * @param quote_asset symbol or ID of asset being purchased\n          * @param group Maximum price diff within each order group, have to be one of configured values\n          * @param start Optional price to indicate the first order group to retrieve\n          * @param limit Maximum number of order groups to retrieve, must not exceed the configured value of\n          *              @a api_limit_get_grouped_limit_orders\n          * @return The grouped limit orders, ordered from best offered price to worst\n          */\n         vector< limit_order_group > get_grouped_limit_orders( const std::string& base_asset,\n                                                               const std::string& quote_asset,\n                                                               uint16_t group,\n                                                               const optional<price>& start,\n                                                               uint32_t limit )const;\n\n      private:\n         application& _app;\n   };\n\n   /**\n    * @brief The custom_operations_api class exposes access to standard custom objects parsed by the\n    * custom_operations_plugin.\n    */\n   class custom_operations_api\n   {\n   public:\n      explicit custom_operations_api(application& app);\n\n      /**\n       * @brief Get stored objects\n       *\n       * @param account_name_or_id The account name or ID to get info from. Optional.\n       * @param catalog The catalog to get info from. Each account can store data in multiple catalogs. Optional.\n       * @param key The key to get info from. Each catalog can contain multiple keys. Optional.\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_storage_info. Optional.\n       * @param start_id Start ID of stored object, fetch objects whose IDs are greater than or equal to this ID\n       * @return The stored objects found, sorted by their ID\n       *\n       * @note\n       * 1. By passing @a null to various optional parameters, or omitting where applicable, this API can be used to\n       *    query stored objects by\n       *    a) account, catalog and key, or\n       *    b) account and catalog, or\n       *    c) account, or\n       *    d) catalog and key, or\n       *    e) catalog, or\n       *    f) unconditionally.\n       *    Queries with keys without a catalog are not allowed.\n       * 2. If @p account_name_or_id is specified but cannot be tied to an account, an error will be returned.\n       * 3. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_storage_info will be used.\n       * 4. @p start_id can be omitted or be @a null, if so the API will return the \"first page\" of objects.\n       * 5. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<account_storage_object> get_storage_info(\n            const optional<std::string>& account_name_or_id = optional<std::string>(),\n            const optional<std::string>& catalog = optional<std::string>(),\n            const optional<std::string>& key = optional<std::string>(),\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<account_storage_id_type>& start_id = optional<account_storage_id_type>() )const;\n\n   private:\n      application& _app;\n   };\n\n   /**\n    * @brief A dummy API class that does nothing, used when access to database_api is not allowed\n    */\n   struct dummy_api\n   {\n      bool dummy() const { return false; }\n   };\n} } // graphene::app\n\nextern template class fc::api<graphene::app::block_api>;\nextern template class fc::api<graphene::app::network_broadcast_api>;\nextern template class fc::api<graphene::app::network_node_api>;\nextern template class fc::api<graphene::app::history_api>;\nextern template class fc::api<graphene::app::crypto_api>;\nextern template class fc::api<graphene::app::asset_api>;\nextern template class fc::api<graphene::app::orders_api>;\nextern template class fc::api<graphene::debug_witness::debug_api>;\nextern template class fc::api<graphene::app::custom_operations_api>;\nextern template class fc::api<graphene::app::dummy_api>;\n\nnamespace graphene { namespace app {\n   /**\n    * @brief The login_api class implements the bottom layer of the RPC API\n    *\n    * All other APIs must be requested from this API.\n    */\n   class login_api\n   {\n      public:\n         explicit login_api(application& a);\n\n         /**\n          * @brief Authenticate to the RPC server, or retrieve the API set ID of the @a login API set\n          * @param user Username to login with, optional\n          * @param password Password to login with, optional\n          * @return @a true if to authenticate and logged in successfully,\n          *         @a false if to authenticate and failed to log in,\n          *         or the API set ID if to retrieve it\n          *\n          * @note Provide both @p user and @p password to authenticate,\n          *       or provide none of them (or @a null without quotes) to retrieve the API set ID\n          *          of the @a login API set.\n          * @note This is called automatically for authentication when a HTTP or WebSocket connection is established,\n          *       assuming credentials are provided with HTTP Basic authentication headers.\n          * @note When trying to authenticate again, even if failed to log in, already allocated API set IDs are\n          *       still accessible.\n          */\n         variant login(const optional<string>& user, const optional<string>& password);\n\n         /// @brief Log out\n         /// @return @a false\n         /// @note Already allocated API set IDs are still accessible after calling this.\n         bool logout();\n\n         /// @brief Retrive the node info string configured by the node operator\n         string get_info() const;\n\n         /// @brief Retrieve configured application options\n         /// @note It requires the user to be logged in and have access to at least one API set other than login_api.\n         application_options get_config() const;\n\n         /// @brief Retrieve a list of API sets that the user has access to\n         flat_set<string> get_available_api_sets() const;\n\n         /// @brief Retrieve the network block API set\n         fc::api<block_api> block();\n         /// @brief Retrieve the network broadcast API set\n         fc::api<network_broadcast_api> network_broadcast();\n         /// @brief Retrieve the database API set\n         fc::api<database_api> database();\n         /// @brief Retrieve the history API set\n         fc::api<history_api> history();\n         /// @brief Retrieve the network node API set\n         fc::api<network_node_api> network_node();\n         /// @brief Retrieve the cryptography API set\n         fc::api<crypto_api> crypto();\n         /// @brief Retrieve the asset API set\n         fc::api<asset_api> asset();\n         /// @brief Retrieve the orders API set\n         fc::api<orders_api> orders();\n         /// @brief Retrieve the debug API set\n         fc::api<graphene::debug_witness::debug_api> debug();\n         /// @brief Retrieve the custom operations API set\n         fc::api<custom_operations_api> custom_operations();\n\n         /// @brief Retrieve a dummy API set, not reflected\n         fc::api<dummy_api> dummy();\n\n         /// @brief Check whether database_api is allowed, not reflected\n         /// @return @a true if database_api is allowed, @a false otherwise\n         bool is_database_api_allowed() const;\n\n      private:\n         application& _app;\n\n         flat_set< string > _allowed_apis;\n\n         optional< fc::api<block_api> >                          _block_api;\n         optional< fc::api<database_api> >                       _database_api;\n         optional< fc::api<network_broadcast_api> >              _network_broadcast_api;\n         optional< fc::api<network_node_api> >                   _network_node_api;\n         optional< fc::api<history_api> >                        _history_api;\n         optional< fc::api<crypto_api> >                         _crypto_api;\n         optional< fc::api<asset_api> >                          _asset_api;\n         optional< fc::api<orders_api> >                         _orders_api;\n         optional< fc::api<graphene::debug_witness::debug_api> > _debug_api;\n         optional< fc::api<custom_operations_api> >              _custom_operations_api;\n         optional< fc::api<dummy_api> >                          _dummy_api;\n   };\n\n}}  // graphene::app\n\nextern template class fc::api<graphene::app::login_api>;\n\nFC_REFLECT( graphene::app::network_broadcast_api::transaction_confirmation,\n        (id)(block_num)(trx_num)(trx) )\n\nFC_REFLECT( graphene::app::crypto_api::verify_range_result,\n        (success)(min_val)(max_val) )\nFC_REFLECT( graphene::app::crypto_api::verify_range_proof_rewind_result,\n        (success)(min_val)(max_val)(value_out)(blind_out)(message_out) )\n\nFC_REFLECT( graphene::app::history_api::history_operation_detail,\n            (total_count)(operation_history_objs) )\n\nFC_REFLECT( graphene::app::orders_api::limit_order_group,\n            (min_price)(max_price)(total_for_sale) )\n\nFC_REFLECT( graphene::app::asset_api::account_asset_balance, (name)(account_id)(amount) )\nFC_REFLECT( graphene::app::asset_api::asset_holders, (asset_id)(count) )\n\nFC_API(graphene::app::history_api,\n       (get_account_history)\n       (get_account_history_by_time)\n       (get_account_history_by_operations)\n       (get_account_history_operations)\n       (get_relative_account_history)\n       (get_block_operation_history)\n       (get_block_operations_by_time)\n       (get_fill_order_history)\n       (get_market_history)\n       (get_market_history_buckets)\n       (get_liquidity_pool_history)\n       (get_liquidity_pool_history_by_sequence)\n     )\nFC_API(graphene::app::block_api,\n       (get_blocks)\n     )\nFC_API(graphene::app::network_broadcast_api,\n       (broadcast_transaction)\n       (broadcast_transaction_with_callback)\n       (broadcast_transaction_synchronous)\n       (broadcast_block)\n     )\nFC_API(graphene::app::network_node_api,\n       (get_info)\n       (add_node)\n       (get_connected_peers)\n       (get_potential_peers)\n       (get_advanced_node_parameters)\n       (set_advanced_node_parameters)\n     )\nFC_API(graphene::app::crypto_api,\n       (blind)\n       (blind_sum)\n       (verify_sum)\n       (verify_range)\n       (range_proof_sign)\n       (verify_range_proof_rewind)\n       (range_get_info)\n     )\nFC_API(graphene::app::asset_api,\n       (get_asset_holders)\n       (get_asset_holders_count)\n       (get_all_asset_holders)\n     )\nFC_API(graphene::app::orders_api,\n       (get_tracked_groups)\n       (get_grouped_limit_orders)\n     )\nFC_API(graphene::app::custom_operations_api,\n       (get_storage_info)\n     )\nFC_API(graphene::app::dummy_api,\n       (dummy)\n     )\nFC_API(graphene::app::login_api,\n       (login)\n       (logout)\n       (get_info)\n       (get_config)\n       (get_available_api_sets)\n       (block)\n       (network_broadcast)\n       (database)\n       (history)\n       (network_node)\n       (crypto)\n       (asset)\n       (orders)\n       (debug)\n       (custom_operations)\n     )\n"
  },
  {
    "path": "libraries/app/include/graphene/app/api_access.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/reflect/reflect.hpp>\n\n#include <boost/container/flat_set.hpp>\n\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace graphene { namespace app {\n\nstruct api_access_info\n{\n   api_access_info() = default;\n   api_access_info( const std::string& hash, const std::string& salt )\n   : password_hash_b64(hash), password_salt_b64(salt)\n   { /* Nothing else to do */ }\n\n   std::string password_hash_b64;\n   std::string password_salt_b64;\n   boost::container::flat_set< std::string > allowed_apis;\n};\n\nstruct api_access\n{\n   std::map< std::string, api_access_info > permission_map;\n};\n\n} } // graphene::app\n\nFC_REFLECT( graphene::app::api_access_info,\n    (password_hash_b64)\n    (password_salt_b64)\n    (allowed_apis)\n   )\n\nFC_REFLECT( graphene::app::api_access,\n    (permission_map)\n   )\n"
  },
  {
    "path": "libraries/app/include/graphene/app/api_objects.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n\n#include <graphene/api_helper_indexes/api_helper_indexes.hpp>\n#include <graphene/market_history/market_history_plugin.hpp>\n\n#include <fc/optional.hpp>\n\nnamespace graphene { namespace app {\n   using namespace graphene::chain;\n   using namespace graphene::market_history;\n\n   struct more_data\n   {\n      bool balances = false;\n      bool vesting_balances = false;\n      bool limit_orders = false;\n      bool call_orders = false;\n      bool settle_orders = false;\n      bool proposals = false;\n      bool assets = false;\n      bool withdraws_from = false;\n      bool withdraws_to = false;\n      bool htlcs_from = false;\n      bool htlcs_to = false;\n   };\n\n   struct full_account\n   {\n      account_object                   account;\n      account_statistics_object        statistics;\n      string                           registrar_name;\n      string                           referrer_name;\n      string                           lifetime_referrer_name;\n      vector<variant>                  votes;\n      optional<vesting_balance_object> cashback_balance;\n      vector<account_balance_object>   balances;\n      vector<vesting_balance_object>   vesting_balances;\n      vector<limit_order_object>       limit_orders;\n      vector<call_order_object>        call_orders;\n      vector<force_settlement_object>  settle_orders;\n      vector<proposal_object>          proposals;\n      vector<asset_id_type>            assets;\n      vector<withdraw_permission_object> withdraws_from;\n      vector<withdraw_permission_object> withdraws_to;\n      vector<htlc_object>              htlcs_from;\n      vector<htlc_object>              htlcs_to;\n      more_data                        more_data_available;\n   };\n\n   struct order\n   {\n      string                     price;\n      string                     quote;\n      string                     base;\n      limit_order_id_type        id;\n      account_id_type            owner_id;\n      string                     owner_name;\n      time_point_sec             expiration;\n\n      order() = default;\n      order( const string& _price,\n             const string& _quote,\n             const string& _base,\n             const limit_order_id_type& _id,\n             const account_id_type& _oid,\n             const string& _oname,\n             const time_point_sec& _exp );\n   };\n\n   struct order_book\n   {\n     string                      base;\n     string                      quote;\n     vector< order >             bids;\n     vector< order >             asks;\n     order_book() = default;\n     order_book( const string& _base, const string& _quote );\n   };\n\n   struct market_ticker\n   {\n      time_point_sec             time;\n      string                     base;\n      string                     quote;\n      string                     latest;\n      string                     lowest_ask;\n      string                     lowest_ask_base_size;\n      string                     lowest_ask_quote_size;\n      string                     highest_bid;\n      string                     highest_bid_base_size;\n      string                     highest_bid_quote_size;\n      string                     percent_change;\n      string                     base_volume;\n      string                     quote_volume;\n      optional<object_id_type>   mto_id;\n\n      market_ticker() {}\n      market_ticker(const market_ticker_object& mto,\n                    const fc::time_point_sec& now,\n                    const asset_object& asset_base,\n                    const asset_object& asset_quote,\n                    const order_book& orders);\n      market_ticker(const fc::time_point_sec& now,\n                    const asset_object& asset_base,\n                    const asset_object& asset_quote);\n   };\n\n   struct market_volume\n   {\n      time_point_sec             time;\n      string                     base;\n      string                     quote;\n      string                     base_volume;\n      string                     quote_volume;\n   };\n\n   struct market_trade\n   {\n      int64_t                    sequence = 0;\n      fc::time_point_sec         date;\n      string                     price;\n      string                     amount;\n      string                     value;\n      string                     type;\n      account_id_type            side1_account_id = GRAPHENE_NULL_ACCOUNT;\n      account_id_type            side2_account_id = GRAPHENE_NULL_ACCOUNT;\n   };\n\n   struct extended_asset_object : asset_object\n   {\n      extended_asset_object() {}\n      explicit extended_asset_object( const asset_object& a ) : asset_object( a ) {}\n      explicit extended_asset_object( asset_object&& a ) : asset_object( std::move(a) ) {}\n\n      optional<share_type> total_in_collateral;\n      optional<share_type> total_backing_collateral;\n   };\n\n   struct extended_liquidity_pool_object : liquidity_pool_object\n   {\n      extended_liquidity_pool_object() {}\n      explicit extended_liquidity_pool_object( const liquidity_pool_object& o ) : liquidity_pool_object( o ) {}\n      explicit extended_liquidity_pool_object( liquidity_pool_object&& o ) : liquidity_pool_object( std::move(o) ) {}\n\n      optional<liquidity_pool_ticker_object> statistics;\n   };\n\n   struct maybe_signed_block_header : block_header\n   {\n      maybe_signed_block_header() = default;\n      explicit maybe_signed_block_header( const signed_block_header& bh, bool with_witness_signature = true );\n\n      optional<signature_type> witness_signature;\n   };\n\n} }\n\nFC_REFLECT( graphene::app::more_data,\n            (balances) (vesting_balances) (limit_orders) (call_orders)\n            (settle_orders) (proposals) (assets) (withdraws_from) (withdraws_to) (htlcs_from) (htlcs_to)\n          )\n\nFC_REFLECT( graphene::app::full_account,\n            (account)\n            (statistics)\n            (registrar_name)\n            (referrer_name)\n            (lifetime_referrer_name)\n            (votes)\n            (cashback_balance)\n            (balances)\n            (vesting_balances)\n            (limit_orders)\n            (call_orders)\n            (settle_orders)\n            (proposals)\n            (assets)\n            (withdraws_from)\n            (withdraws_to)\n            (htlcs_from)\n            (htlcs_to)\n            (more_data_available)\n          )\n\nFC_REFLECT( graphene::app::order, (price)(quote)(base)(id)(owner_id)(owner_name)(expiration) )\nFC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) )\nFC_REFLECT( graphene::app::market_ticker,\n            (time)(base)(quote)(latest)(lowest_ask)(lowest_ask_base_size)(lowest_ask_quote_size)\n            (highest_bid)(highest_bid_base_size)(highest_bid_quote_size)(percent_change)(base_volume)(quote_volume)\n            (mto_id) )\nFC_REFLECT( graphene::app::market_volume, (time)(base)(quote)(base_volume)(quote_volume) )\nFC_REFLECT( graphene::app::market_trade, (sequence)(date)(price)(amount)(value)(type)\n            (side1_account_id)(side2_account_id) )\n\nFC_REFLECT_DERIVED( graphene::app::extended_asset_object, (graphene::chain::asset_object),\n                    (total_in_collateral)(total_backing_collateral) )\n\nFC_REFLECT_DERIVED( graphene::app::extended_liquidity_pool_object, (graphene::chain::liquidity_pool_object),\n                    (statistics) )\n\nFC_REFLECT_DERIVED( graphene::app::maybe_signed_block_header, (graphene::protocol::block_header),\n                    (witness_signature) )\n"
  },
  {
    "path": "libraries/app/include/graphene/app/application.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/api_access.hpp>\n#include <graphene/net/node.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <boost/program_options.hpp>\n\nnamespace graphene { namespace app {\n   namespace detail { class application_impl; }\n   using std::string;\n\n   class abstract_plugin;\n\n   class application_options\n   {\n      public:\n         bool enable_subscribe_to_all = false;\n\n         bool has_api_helper_indexes_plugin = false;\n         bool has_market_history_plugin = false;\n\n         uint32_t api_limit_get_account_history = 100;\n         uint32_t api_limit_get_account_history_operations = 100;\n         uint32_t api_limit_get_account_history_by_operations = 100;\n         uint32_t api_limit_get_relative_account_history = 100;\n         uint32_t api_limit_get_market_history = 200;\n         uint32_t api_limit_get_trade_history = 100;\n         uint32_t api_limit_get_trade_history_by_sequence = 100;\n         uint32_t api_limit_get_liquidity_pool_history = 101;\n         uint32_t api_limit_get_top_markets = 100;\n         uint32_t api_limit_get_assets = 101;\n         uint32_t api_limit_get_asset_holders = 100;\n         uint32_t api_limit_get_key_references = 100;\n         uint32_t api_limit_get_full_accounts = 50;\n         uint32_t api_limit_get_full_accounts_lists = 500;\n         uint32_t api_limit_get_full_accounts_subscribe = 100;\n         uint32_t api_limit_get_top_voters = 200;\n         uint32_t api_limit_get_limit_orders = 300;\n         uint32_t api_limit_get_limit_orders_by_account = 101;\n         uint32_t api_limit_get_account_limit_orders = 101;\n         uint32_t api_limit_get_grouped_limit_orders = 101;\n         uint32_t api_limit_get_order_book = 50;\n         uint32_t api_limit_get_call_orders = 300;\n         uint32_t api_limit_get_settle_orders = 300;\n         uint32_t api_limit_get_collateral_bids = 100;\n         uint32_t api_limit_lookup_accounts = 1000;\n         uint32_t api_limit_lookup_witness_accounts = 1000;\n         uint32_t api_limit_lookup_committee_member_accounts = 1000;\n         uint32_t api_limit_lookup_vote_ids = 1000;\n         uint32_t api_limit_list_htlcs = 100;\n         uint32_t api_limit_get_htlc_by = 100;\n         uint32_t api_limit_get_withdraw_permissions_by_giver = 101;\n         uint32_t api_limit_get_withdraw_permissions_by_recipient = 101;\n         uint32_t api_limit_get_tickets = 101;\n         uint32_t api_limit_get_liquidity_pools = 101;\n         uint32_t api_limit_get_samet_funds = 101;\n         uint32_t api_limit_get_credit_offers = 101;\n         uint32_t api_limit_get_storage_info = 101;\n\n         static constexpr application_options get_default()\n         {\n            constexpr application_options default_options;\n            return default_options;\n         }\n   };\n\n   class application\n   {\n      public:\n         application();\n         ~application();\n\n         void set_program_options(boost::program_options::options_description& command_line_options,\n                                  boost::program_options::options_description& configuration_file_options)const;\n         void initialize(const fc::path& data_dir,\n                         std::shared_ptr<boost::program_options::variables_map> options) const;\n         void startup();\n\n         template<typename PluginType>\n         std::shared_ptr<PluginType> register_plugin(bool auto_load = false) {\n            auto plug = std::make_shared<PluginType>(*this);\n\n            string cli_plugin_desc = plug->plugin_name() + \" plugin. \" + plug->plugin_description() + \"\\nOptions\";\n            boost::program_options::options_description plugin_cli_options( cli_plugin_desc );\n            boost::program_options::options_description plugin_cfg_options;\n            plug->plugin_set_program_options(plugin_cli_options, plugin_cfg_options);\n\n            if( !plugin_cli_options.options().empty() )\n               _cli_options.add(plugin_cli_options);\n\n            if( !plugin_cfg_options.options().empty() )\n            {\n               std::string header_name = \"plugin-cfg-header-\" + plug->plugin_name();\n               std::string header_desc = plug->plugin_name() + \" plugin options\";\n               _cfg_options.add_options()(header_name.c_str(), header_desc.c_str());\n               _cfg_options.add(plugin_cfg_options);\n            }\n\n            add_available_plugin( plug );\n\n            if (auto_load)\n                enable_plugin(plug->plugin_name());\n\n            return plug;\n         }\n         std::shared_ptr<abstract_plugin> get_plugin( const string& name )const;\n\n         template<typename PluginType>\n         std::shared_ptr<PluginType> get_plugin( const string& name ) const\n         {\n            std::shared_ptr<abstract_plugin> abs_plugin = get_plugin( name );\n            std::shared_ptr<PluginType> result = std::dynamic_pointer_cast<PluginType>( abs_plugin );\n            FC_ASSERT( result != std::shared_ptr<PluginType>(), \"Unable to load plugin '${p}'\", (\"p\",name) );\n            return result;\n         }\n\n         net::node_ptr                    p2p_node();\n         std::shared_ptr<chain::database> chain_database()const;\n         void set_api_limit();\n         void set_block_production(bool producing_blocks);\n         fc::optional< api_access_info > get_api_access_info( const string& username )const;\n         void set_api_access_info(const string& username, api_access_info&& permissions);\n\n         bool is_finished_syncing()const;\n         /// Emitted when syncing finishes (is_finished_syncing will return true)\n         boost::signals2::signal<void()> syncing_finished;\n\n         const application_options& get_options() const;\n\n         void enable_plugin( const string& name ) const;\n\n         bool is_plugin_enabled(const string& name) const;\n\n         std::shared_ptr<fc::thread> elasticsearch_thread;\n\n         const string& get_node_info() const;\n\n   private:\n         /// Add an available plugin\n         void add_available_plugin( std::shared_ptr<abstract_plugin> p ) const;\n\n         std::shared_ptr<detail::application_impl> my;\n\n         boost::program_options::options_description _cli_options;\n         boost::program_options::options_description _cfg_options;\n   };\n\n} }\n\nFC_REFLECT( graphene::app::application_options,\n            ( enable_subscribe_to_all )\n            ( has_api_helper_indexes_plugin )\n            ( has_market_history_plugin )\n            ( api_limit_get_account_history )\n            ( api_limit_get_account_history_operations )\n            ( api_limit_get_account_history_by_operations )\n            ( api_limit_get_relative_account_history )\n            ( api_limit_get_market_history )\n            ( api_limit_get_trade_history )\n            ( api_limit_get_trade_history_by_sequence )\n            ( api_limit_get_liquidity_pool_history )\n            ( api_limit_get_top_markets )\n            ( api_limit_get_assets )\n            ( api_limit_get_asset_holders )\n            ( api_limit_get_key_references )\n            ( api_limit_get_full_accounts )\n            ( api_limit_get_full_accounts_lists )\n            ( api_limit_get_full_accounts_subscribe )\n            ( api_limit_get_top_voters )\n            ( api_limit_get_limit_orders )\n            ( api_limit_get_limit_orders_by_account )\n            ( api_limit_get_account_limit_orders )\n            ( api_limit_get_grouped_limit_orders )\n            ( api_limit_get_order_book )\n            ( api_limit_get_call_orders )\n            ( api_limit_get_settle_orders )\n            ( api_limit_get_collateral_bids )\n            ( api_limit_lookup_accounts )\n            ( api_limit_lookup_witness_accounts )\n            ( api_limit_lookup_committee_member_accounts )\n            ( api_limit_lookup_vote_ids )\n            ( api_limit_list_htlcs )\n            ( api_limit_get_htlc_by )\n            ( api_limit_get_withdraw_permissions_by_giver )\n            ( api_limit_get_withdraw_permissions_by_recipient )\n            ( api_limit_get_tickets )\n            ( api_limit_get_liquidity_pools )\n            ( api_limit_get_samet_funds )\n            ( api_limit_get_credit_offers )\n            ( api_limit_get_storage_info )\n          )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::app::application_options )\n"
  },
  {
    "path": "libraries/app/include/graphene/app/config_util.hpp",
    "content": "/*\n * Copyright (c) 2018 Lubos Ilcik, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/filesystem.hpp>\n#include <boost/program_options.hpp>\n\nnamespace graphene { namespace app {\n\n   void load_configuration_options(const fc::path &data_dir, const boost::program_options::options_description &cfg_options,\n                           boost::program_options::variables_map &options);\n\n} } // graphene::app"
  },
  {
    "path": "libraries/app/include/graphene/app/database_api.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/api_objects.hpp>\n\n#include <graphene/protocol/types.hpp>\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/confidential_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <fc/api.hpp>\n#include <fc/variant_object.hpp>\n\n#include <boost/container/flat_set.hpp>\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <vector>\n\nnamespace graphene { namespace app {\n\nusing namespace graphene::chain;\nusing namespace graphene::market_history;\nusing std::string;\nusing std::vector;\nusing std::map;\n\nclass database_api_impl;\n\n/**\n * @brief The database_api class implements the RPC API for the chain database.\n *\n * This API exposes accessors on the database which query state tracked by a blockchain validating node. This API is\n * read-only; all modifications to the database must be performed via transactions. Transactions are broadcast via\n * the @ref network_broadcast_api.\n */\nclass database_api\n{\n   public:\n      database_api(graphene::chain::database& db, const application_options* app_options = nullptr );\n      ~database_api();\n\n      /////////////\n      // Objects //\n      /////////////\n\n      /**\n       * @brief Get the objects corresponding to the provided IDs\n       * @param ids IDs of the objects to retrieve\n       * @param subscribe @a true to subscribe to the queried objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @return The objects retrieved, in the order they are mentioned in ids\n       * @note operation_history_object (1.11.x) and account_history_object (2.9.x)\n       *       can not be subscribed.\n       *\n       * If any of the provided IDs does not map to an object, a null variant is returned in its position.\n       */\n      fc::variants get_objects( const vector<object_id_type>& ids,\n                                optional<bool> subscribe = optional<bool>() )const;\n\n      ///////////////////\n      // Subscriptions //\n      ///////////////////\n\n      /**\n       * @brief Register a callback handle which then can be used to subscribe to object database changes\n       * @param cb The callback handle to register\n       * @param notify_remove_create Whether subscribe to universal object creation and removal events.\n       *        If this is set to true, the API server will notify all newly created objects and ID of all\n       *        newly removed objects to the client, no matter whether client subscribed to the objects.\n       *        By default, API servers don't allow subscribing to universal events, which can be changed\n       *        on server startup.\n       *\n       * Note: auto-subscription is enabled by default and can be disabled with @ref set_auto_subscription API.\n       */\n      void set_subscribe_callback( std::function<void(const variant&)> cb, bool notify_remove_create );\n      /**\n       * @brief Set auto-subscription behavior of follow-up API queries\n       * @param enable whether follow-up API queries will automatically subscribe to queried objects\n       *\n       * Impacts behavior of these APIs:\n       * - get_accounts\n       * - get_assets\n       * - get_objects\n       * - lookup_accounts\n       * - get_full_accounts\n       * - get_htlc\n       * - get_liquidity_pools\n       * - get_liquidity_pools_by_share_asset\n       *\n       * Note: auto-subscription is enabled by default\n       *\n       * @see @ref set_subscribe_callback\n       */\n      void set_auto_subscription( bool enable );\n      /**\n       * @brief Register a callback handle which will get notified when a transaction is pushed to database\n       * @param cb The callback handle to register\n       *\n       * Note: a transaction can be pushed to database and be popped from database several times while\n       *   processing, before and after included in a block. Everytime when a push is done, the client will\n       *   be notified.\n       */\n      void set_pending_transaction_callback( std::function<void(const variant& signed_transaction_object)> cb );\n      /**\n       * @brief Register a callback handle which will get notified when a block is pushed to database\n       * @param cb The callback handle to register\n       */\n      void set_block_applied_callback( std::function<void(const variant& block_id)> cb );\n      /**\n       * @brief Stop receiving any notifications\n       *\n       * This unsubscribes from all subscribed markets and objects.\n       */\n      void cancel_all_subscriptions();\n\n      /////////////////////////////\n      // Blocks and transactions //\n      /////////////////////////////\n\n      /**\n       * @brief Retrieve a block header\n       * @param block_num Height of the block whose header should be returned\n       * @param with_witness_signature Whether to return witness signature. Optional.\n       *                               If omitted or is @a false, will not return witness signature.\n       * @return header of the referenced block, or null if no matching block was found\n       */\n      optional<maybe_signed_block_header> get_block_header(\n            uint32_t block_num,\n            const optional<bool>& with_witness_signature = optional<bool>() )const;\n\n      /**\n       * @brief Retrieve multiple block headers by block numbers\n       * @param block_nums vector containing heights of the blocks whose headers should be returned\n       * @param with_witness_signatures Whether to return witness signatures. Optional.\n       *                                If omitted or is @a false, will not return witness signatures.\n       * @return array of headers of the referenced blocks, or null if no matching block was found\n       */\n      map<uint32_t, optional<maybe_signed_block_header>> get_block_header_batch(\n            const vector<uint32_t>& block_nums,\n            const optional<bool>& with_witness_signatures = optional<bool>() )const;\n\n      /**\n       * @brief Retrieve a full, signed block\n       * @param block_num Height of the block to be returned\n       * @return the referenced block, or null if no matching block was found\n       */\n      optional<signed_block> get_block(uint32_t block_num)const;\n\n      /**\n       * @brief used to fetch an individual transaction.\n       * @param block_num height of the block to fetch\n       * @param trx_in_block the index (sequence number) of the transaction in the block, starts from 0\n       * @return the transaction at the given position\n       */\n      processed_transaction get_transaction( uint32_t block_num, uint32_t trx_in_block )const;\n\n      /**\n       * If the transaction has not expired, this method will return the transaction for the given ID or\n       * it will return NULL if it is not known.  Just because it is not known does not mean it wasn't\n       * included in the blockchain.\n       *\n       * @param txid hash of the transaction\n       * @return the corresponding transaction if found, or null if not found\n       */\n      optional<signed_transaction> get_recent_transaction_by_id( const transaction_id_type& txid )const;\n\n      /////////////\n      // Globals //\n      /////////////\n\n      /**\n       * @brief Retrieve the @ref graphene::chain::chain_property_object associated with the chain\n       */\n      chain_property_object get_chain_properties()const;\n\n      /**\n       * @brief Retrieve the current @ref graphene::chain::global_property_object\n       */\n      global_property_object get_global_properties()const;\n\n      /**\n       * @brief Retrieve compile-time constants\n       */\n      fc::variant_object get_config()const;\n\n      /**\n       * @brief Get the chain ID\n       */\n      chain_id_type get_chain_id()const;\n\n      /**\n       * @brief Retrieve the current @ref graphene::chain::dynamic_global_property_object\n       */\n      dynamic_global_property_object get_dynamic_global_properties()const;\n\n      /**\n       * @brief Get the next object ID in an object space\n       * @param space_id The space ID\n       * @param type_id The type ID\n       * @param with_pending_transactions Whether to include pending transactions\n       * @return The next object ID to be assigned\n       * @throw fc::exception If the object space does not exist, or @p with_pending_transactions is @a false but\n       *                      the api_helper_indexes plugin is not enabled\n       */\n      object_id_type get_next_object_id( uint8_t space_id, uint8_t type_id, bool with_pending_transactions )const;\n\n      //////////\n      // Keys //\n      //////////\n\n      /**\n       * @brief Get all accounts that refer to the specified public keys in their owner authority, active authorities\n       *        or memo key\n       * @param keys a list of public keys to query,\n       *              the quantity should not be greater than the configured value of\n       *              @a api_limit_get_key_references\n       * @return ID of all accounts that refer to the specified keys\n       */\n      vector<flat_set<account_id_type>> get_key_references( vector<public_key_type> keys )const;\n\n      /**\n       * Determine whether a textual representation of a public key\n       * (in Base-58 format) is *currently* linked\n       * to any *registered* (i.e. non-stealth) account on the blockchain\n       * @param public_key Public key\n       * @return Whether a public key is known\n       */\n      bool is_public_key_registered(string public_key) const;\n\n      //////////////\n      // Accounts //\n      //////////////\n\n      /**\n       * @brief Get account object from a name or ID\n       * @param name_or_id name or ID of the account\n       * @return Account ID\n       *\n       */\n      account_id_type get_account_id_from_string(const std::string& name_or_id) const;\n\n      /**\n       * @brief Get a list of accounts by names or IDs\n       * @param account_names_or_ids names or IDs of the accounts to retrieve\n       * @param subscribe @a true to subscribe to the queried account objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @return The accounts corresponding to the provided names or IDs\n       *\n       * This function has semantics identical to @ref get_objects\n       */\n      vector<optional<account_object>> get_accounts( const vector<std::string>& account_names_or_ids,\n                                                     optional<bool> subscribe = optional<bool>() )const;\n\n      /**\n       * @brief Fetch objects relevant to the specified accounts and optionally subscribe to updates\n       * @param names_or_ids Each item must be the name or ID of an account to retrieve,\n       *              the quantity should not be greater than the configured value of\n       *              @a api_limit_get_full_accounts\n       * @param subscribe @a true to subscribe to the queried full account objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @return Map of string from @p names_or_ids to the corresponding account\n       *\n       * This function fetches relevant objects for the given accounts, and subscribes to updates to the given\n       * accounts. If any of the strings in @p names_or_ids cannot be tied to an account, that input will be\n       * ignored. Other accounts will be retrieved and subscribed.\n       * @note The maximum number of accounts allowed to subscribe per connection is configured by the\n       *       @a api_limit_get_full_accounts_subscribe option. Exceeded subscriptions will be ignored.\n       * @note For each object type, the maximum number of objects to return is configured by the\n       *       @a api_limit_get_full_accounts_lists option. Exceeded objects need to be queried with other APIs.\n       *\n       */\n      map<string, full_account, std::less<>> get_full_accounts(\n            const vector<string>& names_or_ids,\n            const optional<bool>& subscribe = optional<bool>() )const;\n\n      /**\n       * @brief Returns vector of voting power sorted by reverse vp_active\n       * @param limit Maximum number of accounts to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_top_voters\n       * @return Desc Sorted voting power vector\n       */\n      vector<account_statistics_object> get_top_voters(uint32_t limit)const;\n\n      /**\n       * @brief Get info of an account by name\n       * @param name Name of the account to retrieve\n       * @return The account holding the provided name\n       */\n      optional<account_object> get_account_by_name( string name )const;\n\n      /**\n       * @brief Get all accounts that refer to the specified account in their owner or active authorities\n       * @param account_name_or_id Account name or ID to query\n       * @return all accounts that refer to the specified account in their owner or active authorities\n       */\n      vector<account_id_type> get_account_references( const std::string account_name_or_id )const;\n\n      /**\n       * @brief Get a list of accounts by name\n       * @param account_names Names of the accounts to retrieve\n       * @return The accounts holding the provided names\n       *\n       * This function has semantics identical to @ref get_objects, but doesn't subscribe.\n       */\n      vector<optional<account_object>> lookup_account_names(const vector<string>& account_names)const;\n\n      /**\n       * @brief Get names and IDs for registered accounts\n       * @param lower_bound_name Lower bound of the first name to return\n       * @param limit Maximum number of results to return, must not exceed the configured value of\n       *              @a api_limit_lookup_accounts\n       * @param subscribe @a true to subscribe to the queried account objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @return Map of account names to corresponding IDs\n       *\n       * @note In addition to the common auto-subscription rules,\n       *       this API will subscribe to the returned account only if @p limit is 1.\n       */\n      map<string, account_id_type, std::less<>> lookup_accounts( const string& lower_bound_name,\n                                                   uint32_t limit,\n                                                   const optional<bool>& subscribe = optional<bool>() )const;\n\n      //////////////\n      // Balances //\n      //////////////\n\n      /**\n       * @brief Get an account's balances in various assets\n       * @param account_name_or_id name or ID of the account to get balances for\n       * @param assets IDs of the assets to get balances of; if empty, get all assets account has a balance in\n       * @return Balances of the account\n       */\n      vector<asset> get_account_balances( const std::string& account_name_or_id,\n                                          const flat_set<asset_id_type>& assets )const;\n\n      /// Semantically equivalent to @ref get_account_balances.\n      vector<asset> get_named_account_balances(const std::string& name, const flat_set<asset_id_type>& assets)const;\n\n      /**\n       * @brief Return all unclaimed balance objects for a list of addresses\n       * @param addrs a list of addresses\n       * @return all unclaimed balance objects for the addresses\n       */\n      vector<balance_object> get_balance_objects( const vector<address>& addrs )const;\n\n      /**\n       * @brief Calculate how much assets in the given balance objects are able to be claimed at current head\n       *        block time\n       * @param objs a list of balance object IDs\n       * @return a list indicating how much asset in each balance object is available to be claimed\n       */\n      vector<asset> get_vested_balances( const vector<balance_id_type>& objs )const;\n\n      /**\n       * @brief Return all vesting balance objects owned by an account\n       * @param account_name_or_id name or ID of an account\n       * @return all vesting balance objects owned by the account\n       */\n      vector<vesting_balance_object> get_vesting_balances( const std::string account_name_or_id )const;\n\n      /**\n       * @brief Get the total number of accounts registered with the blockchain\n       */\n      uint64_t get_account_count()const;\n\n      ////////////\n      // Assets //\n      ////////////\n\n      /**\n       * @brief Get asset ID from an asset symbol or ID\n       * @param symbol_or_id symbol name or ID of the asset\n       * @return asset ID\n       */\n      asset_id_type get_asset_id_from_string(const std::string& symbol_or_id) const;\n\n      /**\n       * @brief Get a list of assets by symbol names or IDs\n       * @param asset_symbols_or_ids symbol names or IDs of the assets to retrieve\n       * @param subscribe @a true to subscribe to the queried asset objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @return The assets corresponding to the provided symbol names or IDs\n       *\n       * This function has semantics identical to @ref get_objects\n       */\n      vector<optional<extended_asset_object>> get_assets( const vector<std::string>& asset_symbols_or_ids,\n                                                          optional<bool> subscribe = optional<bool>() )const;\n\n      /**\n       * @brief Get assets alphabetically by symbol name\n       * @param lower_bound_symbol Lower bound of symbol names to retrieve\n       * @param limit Maximum number of assets to fetch, must not exceed the configured value of\n       *              @a api_limit_get_assets\n       * @return The assets found\n       */\n      vector<extended_asset_object> list_assets(const string& lower_bound_symbol, uint32_t limit)const;\n\n      /**\n       * @brief Get a list of assets by symbol names or IDs\n       * @param symbols_or_ids symbol names or IDs of the assets to retrieve\n       * @return The assets corresponding to the provided symbols or IDs\n       *\n       * This function has semantics identical to @ref get_objects, but doesn't subscribe\n       */\n      vector<optional<extended_asset_object>> lookup_asset_symbols(const vector<string>& symbols_or_ids)const;\n\n      /**\n       * @brief Get assets count\n       * @return The assets count\n       */\n      uint64_t get_asset_count()const;\n\n      /**\n       * @brief Get assets issued (owned) by a given account\n       * @param issuer_name_or_id Account name or ID to get objects from\n       * @param start Asset objects(1.3.X) before this ID will be skipped in results. Pagination purposes.\n       * @param limit Maximum number of assets to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_assets\n       * @return The assets issued (owned) by the account\n       */\n      vector<extended_asset_object> get_assets_by_issuer(const std::string& issuer_name_or_id,\n                                                         asset_id_type start, uint32_t limit)const;\n\n      /////////////////////\n      // Markets / feeds //\n      /////////////////////\n\n      /**\n       * @brief Get limit orders in a given market\n       * @param a symbol or ID of asset being sold\n       * @param b symbol or ID of asset being purchased\n       * @param limit Maximum number of orders to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_limit_orders\n       * @return The limit orders, ordered from least price to greatest\n       */\n      vector<limit_order_object> get_limit_orders(std::string a, std::string b, uint32_t limit)const;\n\n      /**\n       * @brief Fetch open limit orders in all markets relevant to the specified account, ordered by ID\n       *\n       * @param account_name_or_id  The name or ID of an account to retrieve\n       * @param limit  The limitation of items each query can fetch, not greater than the configured value of\n       *               @a api_limit_get_limit_orders_by_account\n       * @param start_id  Start order id, fetch orders whose IDs are greater than or equal to this order\n       *\n       * @return List of limit orders of the specified account\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *      @a api_limit_get_limit_orders_by_account will be used\n       * 3. @p start_id can be omitted or be null, if so the api will return the \"first page\" of orders\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<limit_order_object> get_limit_orders_by_account(\n            const string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<limit_order_id_type>& start_id = optional<limit_order_id_type>() );\n\n      /**\n       * @brief Fetch all orders relevant to the specified account and specified market, result orders\n       *        are sorted descendingly by price\n       *\n       * @param account_name_or_id  The name or ID of an account to retrieve\n       * @param base  Base asset\n       * @param quote  Quote asset\n       * @param limit  The limitation of items each query can fetch, not greater than the configured value of\n       *               @a api_limit_get_account_limit_orders\n       * @param ostart_id  Start order id, fetch orders which price lower than this order,\n       *                   or price equal to this order but order ID greater than this order\n       * @param ostart_price  Fetch orders with price lower than or equal to this price\n       *\n       * @return List of orders from @p account_name_or_id to the corresponding account\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p ostart_id and @p ostart_price can be empty, if so the api will return the \"first page\" of orders;\n       *    if @p ostart_id is specified, its price will be used to do page query preferentially,\n       *    otherwise the @p ostart_price will be used;\n       *    @p ostart_id and @p ostart_price may be used cooperatively in case of the order specified by @p ostart_id\n       *    was just canceled accidentally, in such case, the result orders' price may lower or equal to\n       *    @p ostart_price, but orders' id greater than @p ostart_id\n       */\n      vector<limit_order_object> get_account_limit_orders( const string& account_name_or_id,\n            const string &base,\n            const string &quote,\n            uint32_t limit = application_options::get_default().api_limit_get_account_limit_orders,\n            optional<limit_order_id_type> ostart_id = optional<limit_order_id_type>(),\n            optional<price> ostart_price = optional<price>());\n\n      /**\n       * @brief Get call orders (aka margin positions) for a given asset\n       * @param a symbol name or ID of the debt asset\n       * @param limit Maximum number of orders to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_call_orders\n       * @return The call orders, ordered from earliest to be called to latest\n       */\n      vector<call_order_object> get_call_orders(const std::string& a, uint32_t limit)const;\n\n      /**\n       * @brief Get call orders (aka margin positions) of a given account\n       * @param account_name_or_id Account name or ID to get objects from\n       * @param start Asset objects(1.3.X) before this ID will be skipped in results. Pagination purposes.\n       * @param limit Maximum number of orders to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_call_orders\n       * @return The call orders of the account\n       */\n      vector<call_order_object> get_call_orders_by_account(const std::string& account_name_or_id,\n                                                           asset_id_type start, uint32_t limit)const;\n\n      /**\n       * @brief Get forced settlement orders in a given asset\n       * @param a Symbol or ID of asset being settled\n       * @param limit Maximum number of orders to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_settle_orders\n       * @return The settle orders, ordered from earliest settlement date to latest\n       */\n      vector<force_settlement_object> get_settle_orders(const std::string& a, uint32_t limit)const;\n\n      /**\n       * @brief Get forced settlement orders of a given account\n       * @param account_name_or_id Account name or ID to get objects from\n       * @param start Force settlement objects(1.4.X) before this ID will be skipped in results. Pagination purposes.\n       * @param limit Maximum number of orders to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_settle_orders\n       * @return The settle orders, ordered from earliest settlement date to latest\n       * @return The settle orders of the account\n       */\n      vector<force_settlement_object> get_settle_orders_by_account( const std::string& account_name_or_id,\n                                                                    force_settlement_id_type start,\n                                                                    uint32_t limit )const;\n\n      /**\n       * @brief Get collateral_bid_objects for a given asset\n       * @param a Symbol or ID of asset\n       * @param limit Maximum number of objects to retrieve, must not exceed the configured value of\n       *              @a api_limit_get_collateral_bids\n       * @param start skip that many results\n       * @return The settle orders, ordered from earliest settlement date to latest\n       */\n      vector<collateral_bid_object> get_collateral_bids(const std::string& a, uint32_t limit, uint32_t start)const;\n\n      /**\n       * @brief Get open margin positions of a given account\n       * @param account_name_or_id name or ID of an account\n       * @return open margin positions of the account\n       *\n       * Similar to @ref get_call_orders_by_account, but only the first page will be returned, the page size is\n       * the configured value of @a api_limit_get_call_orders.\n       */\n      vector<call_order_object> get_margin_positions( const std::string& account_name_or_id )const;\n\n      /**\n       * @brief Request notification when the active orders in the market between two assets changes\n       * @param callback Callback method which is called when the market changes\n       * @param a symbol name or ID of the first asset\n       * @param b symbol name or ID of the second asset\n       *\n       * Callback will be passed a variant containing a vector<pair<operation, operation_result>>. The vector will\n       * contain, in order, the operations which changed the market, and their results.\n       */\n      void subscribe_to_market(std::function<void(const variant&)> callback,\n                               const std::string& a, const std::string& b);\n\n      /**\n       * @brief Unsubscribe from updates to a given market\n       * @param a symbol name or ID of the first asset\n       * @param b symbol name or ID of the second asset\n       */\n      void unsubscribe_from_market( const std::string& a, const std::string& b );\n\n      /**\n       * @brief Returns the ticker for the market assetA:assetB\n       * @param base symbol name or ID of the base asset\n       * @param quote symbol name or ID of the quote asset\n       * @return The market ticker for the past 24 hours.\n       */\n      market_ticker get_ticker( const string& base, const string& quote )const;\n\n      /**\n       * @brief Returns the 24 hour volume for the market assetA:assetB\n       * @param base symbol name or ID of the base asset\n       * @param quote symbol name or ID of the quote asset\n       * @return The market volume over the past 24 hours\n       */\n      market_volume get_24_volume( const string& base, const string& quote )const;\n\n      /**\n       * @brief Returns the order book for the market base:quote\n       * @param base symbol name or ID of the base asset\n       * @param quote symbol name or ID of the quote asset\n       * @param limit depth of the order book to retrieve, for bids and asks each, capped at the configured value of\n       *              @a api_limit_get_order_book and @a api_limit_get_limit_orders\n       * @return Order book of the market\n       */\n      order_book get_order_book( const string& base, const string& quote,\n            uint32_t limit = application_options::get_default().api_limit_get_order_book )const;\n\n      /**\n       * @brief Returns vector of tickers sorted by reverse base_volume\n       * @note this API is experimental and subject to change in next releases\n       * @param limit Max number of results, must not exceed the configured value of\n       *              @a api_limit_get_top_markets\n       * @return Desc Sorted ticker vector\n       */\n      vector<market_ticker> get_top_markets(uint32_t limit)const;\n\n      /**\n       * @brief Get market transactions occurred in the market base:quote, ordered by time, most recent first.\n       * @param base symbol or ID of the base asset\n       * @param quote symbol or ID of the quote asset\n       * @param start Start time as a UNIX timestamp, the latest transactions to retrieve\n       * @param stop Stop time as a UNIX timestamp, the earliest transactions to retrieve\n       * @param limit Maximum quantity of transactions to retrieve, capped at the configured value of\n       *              @a api_limit_get_trade_history\n       * @return Transactions in the market\n       * @note The time must be UTC, timezone offsets are not supported. The range is [stop, start].\n       *       In case when there are more transactions than @p limit occurred in the same second,\n       *       this API only returns the most recent records, the rest records can be retrieved\n       *       with the @ref get_trade_history_by_sequence API.\n       */\n      vector<market_trade> get_trade_history( const string& base, const string& quote,\n            fc::time_point_sec start, fc::time_point_sec stop,\n            uint32_t limit = application_options::get_default().api_limit_get_trade_history )const;\n\n      /**\n       * @brief Get market transactions occurred in the market base:quote, ordered by time, most recent first.\n       * @param base symbol or ID of the base asset\n       * @param quote symbol or ID of the quote asset\n       * @param start Start sequence as an Integer, the latest transaction to retrieve\n       * @param stop Stop time as a UNIX timestamp, the earliest transactions to retrieve\n       * @param limit Maximum quantity of transactions to retrieve, capped at the configured value of\n       *              @a api_limit_get_trade_history_by_sequence\n       * @return Transactions in the market\n       * @note The time must be UTC, timezone offsets are not supported. The range is [stop, start].\n       */\n      vector<market_trade> get_trade_history_by_sequence( const string& base, const string& quote,\n            int64_t start, fc::time_point_sec stop,\n            uint32_t limit = application_options::get_default().api_limit_get_trade_history_by_sequence )const;\n\n\n      /////////////////////\n      // Liquidity pools //\n      /////////////////////\n\n      /**\n       * @brief Get a list of liquidity pools\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param start_id Start liquidity pool id, fetch pools whose IDs are greater than or equal to this ID\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note\n       * 1. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_liquidity_pools will be used\n       * 2. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of pools\n       * 3. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<extended_liquidity_pool_object> list_liquidity_pools(\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<liquidity_pool_id_type>& start_id = optional<liquidity_pool_id_type>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by the symbol or ID of the first asset in the pool\n       * @param asset_symbol_or_id symbol name or ID of the asset\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param start_id Start liquidity pool id, fetch pools whose IDs are greater than or equal to this ID\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_liquidity_pools will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of pools\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_asset_a(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<liquidity_pool_id_type>& start_id = optional<liquidity_pool_id_type>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by the symbol or ID of the second asset in the pool\n       * @param asset_symbol_or_id symbol name or ID of the asset\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param start_id Start liquidity pool id, fetch pools whose IDs are greater than or equal to this ID\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_liquidity_pools will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of pools\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_asset_b(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<liquidity_pool_id_type>& start_id = optional<liquidity_pool_id_type>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by the symbol or ID of one asset in the pool\n       * @param asset_symbol_or_id symbol name or ID of the asset\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param start_id Start liquidity pool id, fetch pools whose IDs are greater than or equal to this ID\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_liquidity_pools will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of pools\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_one_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<liquidity_pool_id_type>& start_id = optional<liquidity_pool_id_type>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by the symbols or IDs of the two assets in the pool\n       * @param asset_symbol_or_id_a symbol name or ID of one asset\n       * @param asset_symbol_or_id_b symbol name or ID of the other asset\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param start_id Start liquidity pool id, fetch pools whose IDs are greater than or equal to this ID\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id_a or @p asset_symbol_or_id_b cannot be tied to an asset,\n       *    an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_liquidity_pools will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of pools\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_both_assets(\n            const std::string& asset_symbol_or_id_a,\n            const std::string& asset_symbol_or_id_b,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<liquidity_pool_id_type>& start_id = optional<liquidity_pool_id_type>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by their IDs\n       * @param ids IDs of the liquidity pools,\n       *              the quantity should not be greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param subscribe @a true to subscribe to the queried objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note if an ID in the list can not be found,\n       *       the corresponding data in the returned list is null.\n       */\n      vector<optional<extended_liquidity_pool_object>> get_liquidity_pools(\n            const vector<liquidity_pool_id_type>& ids,\n            const optional<bool>& subscribe = optional<bool>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by their share asset symbols or IDs\n       * @param asset_symbols_or_ids symbol names or IDs of the share assets,\n       *              the quantity should not be greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param subscribe @a true to subscribe to the queried objects, @a false to not subscribe,\n       *                  @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                  (see @ref set_auto_subscription)\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools that the assets are for\n       *\n       * @note if an asset in the list can not be found or is not a share asset of any liquidity pool,\n       *       the corresponding data in the returned list is null.\n       */\n      vector<optional<extended_liquidity_pool_object>> get_liquidity_pools_by_share_asset(\n            const vector<std::string>& asset_symbols_or_ids,\n            const optional<bool>& subscribe = optional<bool>(),\n            const optional<bool>& with_statistics = false )const;\n\n      /**\n       * @brief Get a list of liquidity pools by the name or ID of the owner account\n       * @param account_name_or_id name or ID of the owner account\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_liquidity_pools\n       * @param start_id Start share asset id, fetch pools whose share asset IDs are greater than or equal to this ID\n       * @param with_statistics Whether to return statistics\n       * @return The liquidity pools\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_liquidity_pools will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of pools\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<extended_liquidity_pool_object> get_liquidity_pools_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<asset_id_type>& start_id = optional<asset_id_type>(),\n            const optional<bool>& with_statistics = false )const;\n\n\n      /////////////////////\n      /// SameT Funds\n      /// @{\n\n      /**\n       * @brief Get a list of SameT Funds\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_samet_funds\n       * @param start_id Start SameT Fund id, fetch items whose IDs are greater than or equal to this ID\n       * @return The SameT Funds\n       *\n       * @note\n       * 1. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_samet_funds will be used\n       * 2. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 3. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<samet_fund_object> list_samet_funds(\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<samet_fund_id_type>& start_id = optional<samet_fund_id_type>() )const;\n\n      /**\n       * @brief Get a list of SameT Funds by the name or ID of the owner account\n       * @param account_name_or_id name or ID of the owner account\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_samet_funds\n       * @param start_id Start SameT Fund id, fetch items whose IDs are greater than or equal to this ID\n       * @return The SameT Funds\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_samet_funds will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<samet_fund_object> get_samet_funds_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<samet_fund_id_type>& start_id = optional<samet_fund_id_type>() )const;\n\n      /**\n       * @brief Get a list of SameT Funds by the symbol or ID of the asset type\n       * @param asset_symbol_or_id symbol or ID of the asset type\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_samet_funds\n       * @param start_id Start SameT Fund id, fetch items whose IDs are greater than or equal to this ID\n       * @return The SameT Funds\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_samet_funds will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<samet_fund_object> get_samet_funds_by_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<samet_fund_id_type>& start_id = optional<samet_fund_id_type>() )const;\n      /// @}\n\n\n      ////////////////////////////////////\n      /// Credit offers and credit deals\n      /// @{\n\n      /**\n       * @brief Get a list of credit offers\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit offer id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit offers\n       *\n       * @note\n       * 1. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 2. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 3. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_offer_object> list_credit_offers(\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_offer_id_type>& start_id = optional<credit_offer_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit offers by the name or ID of the owner account\n       * @param account_name_or_id name or ID of the owner account\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit offer id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit offers\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_offer_object> get_credit_offers_by_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_offer_id_type>& start_id = optional<credit_offer_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit offers by the symbol or ID of the asset type\n       * @param asset_symbol_or_id symbol or ID of the asset type\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit offer id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit offers\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_offer_object> get_credit_offers_by_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_offer_id_type>& start_id = optional<credit_offer_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit deals\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit deals\n       *\n       * @note\n       * 1. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 2. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 3. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_deal_object> list_credit_deals(\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_deal_id_type>& start_id = optional<credit_deal_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit deals by the ID of a credit offer\n       * @param offer_id ID of the credit offer\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit deals\n       *\n       * @note\n       * 1. If @p offer_id cannot be tied to a credit offer, an empty list will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_deal_object> get_credit_deals_by_offer_id(\n            const credit_offer_id_type& offer_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_deal_id_type>& start_id = optional<credit_deal_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit deals by the name or ID of a credit offer owner account\n       * @param account_name_or_id name or ID of the credit offer owner account\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit deals\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_deal_object> get_credit_deals_by_offer_owner(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_deal_id_type>& start_id = optional<credit_deal_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit deals by the name or ID of a borrower account\n       * @param account_name_or_id name or ID of the borrower account\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit deals\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_deal_object> get_credit_deals_by_borrower(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_deal_id_type>& start_id = optional<credit_deal_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit deals by the symbol or ID of the debt asset type\n       * @param asset_symbol_or_id symbol or ID of the debt asset type\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit deals\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_deal_object> get_credit_deals_by_debt_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_deal_id_type>& start_id = optional<credit_deal_id_type>() )const;\n\n      /**\n       * @brief Get a list of credit deals by the symbol or ID of the collateral asset type\n       * @param asset_symbol_or_id symbol or ID of the collateral asset type\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_credit_offers\n       * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID\n       * @return The credit deals\n       *\n       * @note\n       * 1. If @p asset_symbol_or_id cannot be tied to an asset, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_credit_offers will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of data\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<credit_deal_object> get_credit_deals_by_collateral_asset(\n            const std::string& asset_symbol_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<credit_deal_id_type>& start_id = optional<credit_deal_id_type>() )const;\n      /// @}\n\n\n      ///////////////\n      // Witnesses //\n      ///////////////\n\n      /**\n       * @brief Get a list of witnesses by ID\n       * @param witness_ids IDs of the witnesses to retrieve\n       * @return The witnesses corresponding to the provided IDs\n       *\n       * This function has semantics identical to @ref get_objects, but doesn't subscribe\n       */\n      vector<optional<witness_object>> get_witnesses(const vector<witness_id_type>& witness_ids)const;\n\n      /**\n       * @brief Get the witness owned by a given account\n       * @param account_name_or_id The name or ID of the account whose witness should be retrieved\n       * @return The witness object, or null if the account does not have a witness\n       */\n      fc::optional<witness_object> get_witness_by_account(const std::string& account_name_or_id)const;\n\n      /**\n       * @brief Get names and IDs for registered witnesses\n       * @param lower_bound_name Lower bound of the first name to return\n       * @param limit Maximum number of results to return, must not exceed the configured value of\n       *              @a api_limit_lookup_witness_accounts\n       * @return Map of witness names to corresponding IDs\n       */\n      map<string, witness_id_type, std::less<>> lookup_witness_accounts( const string& lower_bound_name,\n                                                                         uint32_t limit )const;\n\n      /**\n       * @brief Get the total number of witnesses registered with the blockchain\n       */\n      uint64_t get_witness_count()const;\n\n      ///////////////////////\n      // Committee members //\n      ///////////////////////\n\n      /**\n       * @brief Get a list of committee_members by ID\n       * @param committee_member_ids IDs of the committee_members to retrieve\n       * @return The committee_members corresponding to the provided IDs\n       *\n       * This function has semantics identical to @ref get_objects, but doesn't subscribe\n       */\n      vector<optional<committee_member_object>> get_committee_members(\n            const vector<committee_member_id_type>& committee_member_ids)const;\n\n      /**\n       * @brief Get the committee_member owned by a given account\n       * @param account_name_or_id The name or ID of the account whose committee_member should be retrieved\n       * @return The committee_member object, or null if the account does not have a committee_member\n       */\n      fc::optional<committee_member_object> get_committee_member_by_account( const string& account_name_or_id )const;\n\n      /**\n       * @brief Get names and IDs for registered committee_members\n       * @param lower_bound_name Lower bound of the first name to return\n       * @param limit Maximum number of results to return, must not exceed the configured value of\n       *              @a api_limit_lookup_committee_member_accounts\n       * @return Map of committee_member names to corresponding IDs\n       */\n      map<string, committee_member_id_type, std::less<>> lookup_committee_member_accounts(\n            const string& lower_bound_name,\n            uint32_t limit )const;\n\n      /**\n       * @brief Get the total number of committee registered with the blockchain\n      */\n      uint64_t get_committee_count()const;\n\n\n      ///////////////////////\n      // Worker proposals  //\n      ///////////////////////\n\n      /**\n       * @brief Get workers\n       * @param is_expired null for all workers, true for expired workers only, false for non-expired workers only\n       * @return A list of worker objects\n       *\n      */\n      vector<worker_object> get_all_workers( const optional<bool>& is_expired = optional<bool>() )const;\n\n      /**\n       * @brief Get the workers owned by a given account\n       * @param account_name_or_id The name or ID of the account whose worker should be retrieved\n       * @return A list of worker objects owned by the account\n       */\n      vector<worker_object> get_workers_by_account(const std::string& account_name_or_id)const;\n\n      /**\n       * @brief Get the total number of workers registered with the blockchain\n      */\n      uint64_t get_worker_count()const;\n\n\n\n      ///////////\n      // Votes //\n      ///////////\n\n      /**\n       * @brief Given a set of votes, return the objects they are voting for\n       * @param votes a list of vote IDs,\n       *              the quantity should not be greater than the configured value of\n       *              @a api_limit_lookup_vote_ids\n       * @return the referenced objects\n       *\n       * This will be a mixture of committee_member_objects, witness_objects, and worker_objects\n       *\n       * The results will be in the same order as the votes.  Null will be returned for\n       * any vote IDs that are not found.\n       */\n      vector<variant> lookup_vote_ids( const vector<vote_id_type>& votes )const;\n\n      ////////////////////////////\n      // Authority / validation //\n      ////////////////////////////\n\n      /**\n       * @brief Get a hexdump of the serialized binary form of a transaction\n       * @param trx a transaction to get hexdump from\n       * @return the hexdump of the transaction\n       */\n      std::string get_transaction_hex(const signed_transaction& trx)const;\n\n      /**\n       * @brief Get a hexdump of the serialized binary form of a signatures-stripped transaction\n       * @param trx a transaction to get hexdump from\n       * @return the hexdump of the transaction without the signatures\n       */\n      std::string get_transaction_hex_without_sig( const transaction &trx ) const;\n\n      /**\n       *  This API will take a partially signed transaction and a set of public keys that the owner\n       *  has the ability to sign for and return the minimal subset of public keys that should add\n       *  signatures to the transaction.\n       *\n       *  @param trx the transaction to be signed\n       *  @param available_keys a set of public keys\n       *  @return a subset of @p available_keys that could sign for the given transaction\n       */\n      set<public_key_type> get_required_signatures( const signed_transaction& trx,\n                                                    const flat_set<public_key_type>& available_keys )const;\n\n      /**\n       *  This method will return the set of all public keys that could possibly sign for a given transaction.\n       *  This call can be used by wallets to filter their set of public keys to just the relevant subset prior\n       *  to calling @ref get_required_signatures to get the minimum subset.\n       *\n       *  @param trx the transaction to be signed\n       *  @return a set of public keys that could possibly sign for the given transaction\n       */\n      set<public_key_type> get_potential_signatures( const signed_transaction& trx )const;\n\n      /**\n       *  This method will return the set of all addresses that could possibly sign for a given transaction.\n       *\n       *  @param trx the transaction to be signed\n       *  @return a set of addresses that could possibly sign for the given transaction\n       */\n      set<address> get_potential_address_signatures( const signed_transaction& trx )const;\n\n      /**\n       * Check whether a transaction has all of the required signatures\n       * @param trx a transaction to be verified\n       * @return true if the @p trx has all of the required signatures, otherwise throws an exception\n       */\n      bool           verify_authority( const signed_transaction& trx )const;\n\n      /**\n       * @brief Verify that the public keys have enough authority to approve an operation for this account\n       * @param account_name_or_id name or ID of an account to check\n       * @param signers the public keys\n       * @return true if the passed in keys have enough authority to approve an operation for this account\n       */\n      bool verify_account_authority( const string& account_name_or_id,\n                                     const flat_set<public_key_type>& signers )const;\n\n      /**\n       * @brief Validates a transaction against the current state without broadcasting it on the network\n       * @param trx a transaction to be validated\n       * @return a processed_transaction object if the transaction passes the validation,\n                 otherwise an exception will be thrown\n       */\n      processed_transaction validate_transaction( const signed_transaction& trx )const;\n\n      /**\n       * @brief For each operation calculate the required fee in the specified asset type\n       * @param ops a list of operations to be query for required fees\n       * @param asset_symbol_or_id symbol name or ID of an asset that to be used to pay the fees\n       * @return a list of objects which indicates required fees of each operation\n       */\n      vector< fc::variant > get_required_fees( const vector<operation>& ops,\n                                               const std::string& asset_symbol_or_id )const;\n\n      ///////////////////////////\n      // Proposed transactions //\n      ///////////////////////////\n\n      /**\n       * @brief return a set of proposed transactions (aka proposals) that the specified account\n       *        can add approval to or remove approval from\n       * @param account_name_or_id The name or ID of an account\n       * @return a set of proposed transactions that the specified account can act on\n       */\n      vector<proposal_object> get_proposed_transactions( const std::string account_name_or_id )const;\n\n      //////////////////////\n      // Blinded balances //\n      //////////////////////\n\n      /**\n       * @brief return the set of blinded balance objects by commitment ID\n       * @param commitments a set of commitments to query for\n       * @return the set of blinded balance objects by commitment ID\n       */\n      vector<blinded_balance_object> get_blinded_balances( const flat_set<commitment_type>& commitments )const;\n\n      /////////////////\n      // Withdrawals //\n      /////////////////\n\n      /**\n       *  @brief Get non expired withdraw permission objects for a giver(ex:recurring customer)\n       *  @param account_name_or_id Account name or ID to get objects from\n       *  @param start Withdraw permission objects(1.12.X) before this ID will be skipped in results.\n       *               Pagination purposes.\n       *  @param limit Maximum number of objects to retrieve, must not exceed the configured value of\n       *               @a api_limit_get_withdraw_permissions_by_giver\n       *  @return Withdraw permission objects for the account\n       */\n      vector<withdraw_permission_object> get_withdraw_permissions_by_giver( const std::string account_name_or_id,\n                                                                            withdraw_permission_id_type start,\n                                                                            uint32_t limit )const;\n\n      /**\n       *  @brief Get non expired withdraw permission objects for a recipient(ex:service provider)\n       *  @param account_name_or_id Account name or ID to get objects from\n       *  @param start Withdraw permission objects(1.12.X) before this ID will be skipped in results.\n       *               Pagination purposes.\n       *  @param limit Maximum number of objects to retrieve, must not exceed the configured value of\n       *               @a api_limit_get_withdraw_permissions_by_recipient\n       *  @return Withdraw permission objects for the account\n       */\n      vector<withdraw_permission_object> get_withdraw_permissions_by_recipient( const std::string account_name_or_id,\n                                                                                withdraw_permission_id_type start,\n                                                                                uint32_t limit )const;\n\n      //////////\n      // HTLC //\n      //////////\n\n      /**\n       *  @brief Get HTLC object\n       *  @param id HTLC contract id\n       *  @param subscribe @a true to subscribe to the queried HTLC objects, @a false to not subscribe,\n       *                   @a null to subscribe or not subscribe according to current auto-subscription setting\n       *                   (see @ref set_auto_subscription)\n       *  @return HTLC object for the id\n       */\n      optional<htlc_object> get_htlc( htlc_id_type id, optional<bool> subscribe = optional<bool>() ) const;\n\n      /**\n       *  @brief Get non expired HTLC objects using the sender account\n       *  @param account_name_or_id Account name or ID to get objects from\n       *  @param start htlc objects before this ID will be skipped in results. Pagination purposes.\n       *  @param limit Maximum number of objects to retrieve, must not exceed the configured value of\n       *               @a api_limit_get_htlc_by\n       *  @return HTLC objects for the account\n       */\n      vector<htlc_object> get_htlc_by_from( const std::string account_name_or_id,\n                                            htlc_id_type start,\n                                            uint32_t limit ) const;\n\n      /**\n       *  @brief Get non expired HTLC objects using the receiver account\n       *  @param account_name_or_id Account name or ID to get objects from\n       *  @param start htlc objects before this ID will be skipped in results. Pagination purposes.\n       *  @param limit Maximum number of objects to retrieve, must not exceed the configured value of\n       *               @a api_limit_get_htlc_by\n       *  @return HTLC objects for the account\n      */\n      vector<htlc_object> get_htlc_by_to( const std::string account_name_or_id,\n                                          htlc_id_type start,\n                                          uint32_t limit ) const;\n\n      /**\n       * @brief Get all HTLCs\n       * @param start Lower bound of htlc id to start getting results\n       * @param limit Maximum number of htlc objects to fetch, must not exceed the configured value of\n       *              @a api_limit_list_htlcs\n       * @return The htlc object list\n      */\n      vector<htlc_object> list_htlcs(const htlc_id_type start, uint32_t limit) const;\n\n\n      /////////////\n      // Tickets //\n      /////////////\n\n      /**\n       * @brief Get a list of tickets\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_tickets\n       * @param start_id Start ticket id, fetch tickets whose IDs are greater than or equal to this ID\n       * @return The tickets\n       *\n       * @note\n       * 1. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_tickets will be used\n       * 2. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of tickets\n       * 3. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<ticket_object> list_tickets(\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<ticket_id_type>& start_id = optional<ticket_id_type>() )const;\n\n      /**\n       * @brief Get a list of tickets by the name or ID of the owner account\n       * @param account_name_or_id name or ID of the owner account\n       * @param limit The limitation of items each query can fetch, not greater than the configured value of\n       *              @a api_limit_get_tickets\n       * @param start_id Start ticket id, fetch tickets whose IDs are greater than or equal to this ID\n       * @return The tickets\n       *\n       * @note\n       * 1. If @p account_name_or_id cannot be tied to an account, an error will be returned\n       * 2. @p limit can be omitted or be @a null, if so the configured value of\n       *       @a api_limit_get_tickets will be used\n       * 3. @p start_id can be omitted or be @a null, if so the api will return the \"first page\" of tickets\n       * 4. One or more optional parameters can be omitted from the end of the parameter list, and the optional\n       *    parameters in the middle cannot be omitted (but can be @a null).\n       */\n      vector<ticket_object> get_tickets_by_account(\n            const std::string& account_name_or_id,\n            const optional<uint32_t>& limit = optional<uint32_t>(),\n            const optional<ticket_id_type>& start_id = optional<ticket_id_type>() )const;\n\nprivate:\n      std::shared_ptr< database_api_impl > my;\n};\n\n} }\n\nextern template class fc::api<graphene::app::database_api>;\n\nFC_API(graphene::app::database_api,\n   // Objects\n   (get_objects)\n\n   // Subscriptions\n   (set_subscribe_callback)\n   (set_auto_subscription)\n   (set_pending_transaction_callback)\n   (set_block_applied_callback)\n   (cancel_all_subscriptions)\n\n   // Blocks and transactions\n   (get_block_header)\n   (get_block_header_batch)\n   (get_block)\n   (get_transaction)\n   (get_recent_transaction_by_id)\n\n   // Globals\n   (get_chain_properties)\n   (get_global_properties)\n   (get_config)\n   (get_chain_id)\n   (get_dynamic_global_properties)\n   (get_next_object_id)\n\n   // Keys\n   (get_key_references)\n   (is_public_key_registered)\n\n   // Accounts\n   (get_account_id_from_string)\n   (get_accounts)\n   (get_full_accounts)\n   (get_top_voters)\n   (get_account_by_name)\n   (get_account_references)\n   (lookup_account_names)\n   (lookup_accounts)\n   (get_account_count)\n\n   // Balances\n   (get_account_balances)\n   (get_named_account_balances)\n   (get_balance_objects)\n   (get_vested_balances)\n   (get_vesting_balances)\n\n   // Assets\n   (get_assets)\n   (list_assets)\n   (lookup_asset_symbols)\n   (get_asset_count)\n   (get_assets_by_issuer)\n   (get_asset_id_from_string)\n\n   // Markets / feeds\n   (get_order_book)\n   (get_limit_orders)\n   (get_limit_orders_by_account)\n   (get_account_limit_orders)\n   (get_call_orders)\n   (get_call_orders_by_account)\n   (get_settle_orders)\n   (get_settle_orders_by_account)\n   (get_margin_positions)\n   (get_collateral_bids)\n   (subscribe_to_market)\n   (unsubscribe_from_market)\n   (get_ticker)\n   (get_24_volume)\n   (get_top_markets)\n   (get_trade_history)\n   (get_trade_history_by_sequence)\n\n   // Liquidity pools\n   (list_liquidity_pools)\n   (get_liquidity_pools_by_asset_a)\n   (get_liquidity_pools_by_asset_b)\n   (get_liquidity_pools_by_one_asset)\n   (get_liquidity_pools_by_both_assets)\n   (get_liquidity_pools)\n   (get_liquidity_pools_by_share_asset)\n   (get_liquidity_pools_by_owner)\n\n   // SameT Funds\n   (list_samet_funds)\n   (get_samet_funds_by_owner)\n   (get_samet_funds_by_asset)\n\n   // Credit offers and credit deals\n   (list_credit_offers)\n   (get_credit_offers_by_owner)\n   (get_credit_offers_by_asset)\n   (list_credit_deals)\n   (get_credit_deals_by_offer_id)\n   (get_credit_deals_by_offer_owner)\n   (get_credit_deals_by_borrower)\n   (get_credit_deals_by_debt_asset)\n   (get_credit_deals_by_collateral_asset)\n\n   // Witnesses\n   (get_witnesses)\n   (get_witness_by_account)\n   (lookup_witness_accounts)\n   (get_witness_count)\n\n   // Committee members\n   (get_committee_members)\n   (get_committee_member_by_account)\n   (lookup_committee_member_accounts)\n   (get_committee_count)\n\n   // workers\n   (get_all_workers)\n   (get_workers_by_account)\n   (get_worker_count)\n\n   // Votes\n   (lookup_vote_ids)\n\n   // Authority / validation\n   (get_transaction_hex)\n   (get_transaction_hex_without_sig)\n   (get_required_signatures)\n   (get_potential_signatures)\n   (get_potential_address_signatures)\n   (verify_authority)\n   (verify_account_authority)\n   (validate_transaction)\n   (get_required_fees)\n\n   // Proposed transactions\n   (get_proposed_transactions)\n\n   // Blinded balances\n   (get_blinded_balances)\n\n   // Withdrawals\n   (get_withdraw_permissions_by_giver)\n   (get_withdraw_permissions_by_recipient)\n\n   // HTLC\n   (get_htlc)\n   (get_htlc_by_from)\n   (get_htlc_by_to)\n   (list_htlcs)\n\n   // Tickets\n   (list_tickets)\n   (get_tickets_by_account)\n)\n"
  },
  {
    "path": "libraries/app/include/graphene/app/plugin.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/application.hpp>\n\n#include <boost/program_options.hpp>\n#include <fc/io/json.hpp>\n\nnamespace graphene { namespace app {\n\nclass abstract_plugin\n{\n   public:\n      explicit abstract_plugin(application& a) : _app(a) {}\n      virtual ~abstract_plugin() = default;\n\n      /// Get the name of the plugin\n      virtual std::string plugin_name()const = 0;\n\n      /// Get the description of the plugin\n      virtual std::string plugin_description()const = 0;\n\n      /// Get a reference of the application bound to the plugin\n      application& app()const { return _app; }\n\n      /**\n       * @brief Perform early startup routines and register plugin indexes, callbacks, etc.\n       *\n       * Plugins MUST supply a method initialize() which will be called early in the application startup. This method\n       * should contain early setup code such as initializing variables, adding indexes to the database, registering\n       * callback methods from the database, adding APIs, etc., as well as applying any options in the @p options map\n       *\n       * This method is called BEFORE the database is open, therefore any routines which require any chain state MUST\n       * NOT be called by this method. These routines should be performed in startup() instead.\n       *\n       * @param options The options passed to the application, via configuration files or command line\n       */\n      virtual void plugin_initialize( const boost::program_options::variables_map& options ) = 0;\n\n      /**\n       * @brief Begin normal runtime operations\n       *\n       * Plugins MUST supply a method startup() which will be called at the end of application startup. This method\n       * should contain code which schedules any tasks, or requires chain state.\n       */\n      virtual void plugin_startup() = 0;\n\n      /**\n       * @brief Cleanly shut down the plugin.\n       *\n       * This is called to request a clean shutdown (e.g. due to SIGINT or SIGTERM).\n       */\n      virtual void plugin_shutdown() = 0;\n\n      /**\n       * @brief Fill in command line parameters used by the plugin.\n       *\n       * @param command_line_options All options this plugin supports taking on the command-line\n       * @param config_file_options All options this plugin supports storing in a configuration file\n       *\n       * This method populates its arguments with any\n       * command-line and configuration file options the plugin supports.\n       * If a plugin does not need these options, it\n       * may simply provide an empty implementation of this method.\n       */\n      virtual void plugin_set_program_options(\n         boost::program_options::options_description& command_line_options,\n         boost::program_options::options_description& config_file_options\n         ) = 0;\n   protected:\n      application& _app;\n};\n\n/**\n * Provides basic default implementations of abstract_plugin functions.\n */\n\nclass plugin : public abstract_plugin\n{\n   public:\n      using abstract_plugin::abstract_plugin;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n      void plugin_initialize( const boost::program_options::variables_map& options ) override;\n      void plugin_startup() override;\n      void plugin_shutdown() override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& command_line_options,\n         boost::program_options::options_description& config_file_options\n         ) override;\n\n      chain::database& database() { return *app().chain_database(); }\n   protected:\n      net::node_ptr p2p_node() const { return app().p2p_node(); }\n};\n\n/// @ingroup Some useful tools for boost::program_options arguments using vectors of JSON strings\n/// @{\ntemplate<typename T>\nT dejsonify(const string& s, uint32_t max_depth)\n{\n   return fc::json::from_string(s).as<T>(max_depth);\n}\n\nnamespace impl {\n   template<typename T>\n   T dejsonify( const string& s )\n   {\n      return graphene::app::dejsonify<T>( s, GRAPHENE_MAX_NESTED_OBJECTS );\n   }\n}\n\n#define DEFAULT_VALUE_VECTOR(value) default_value({fc::json::to_string(value)}, fc::json::to_string(value))\n#define LOAD_VALUE_SET(options, name, container, type) \\\ndo { \\\n   if( options.count(name) > 0 ) { \\\n      const std::vector<std::string>& ops = options[name].as<std::vector<std::string>>(); \\\n      std::transform(ops.begin(), ops.end(), std::inserter(container, container.end()), \\\n                     &graphene::app::impl::dejsonify<type>); \\\n   } \\\n} while (false)\n/// @}\n\n} } //graphene::app\n"
  },
  {
    "path": "libraries/app/include/graphene/app/util.hpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/uint128.hpp>\n\nnamespace graphene {\nnamespace protocol {\n   struct price;\n}\nnamespace chain {\n   class asset_object;\n}\n\nnamespace app {\n   std::string uint128_amount_to_string( const fc::uint128_t& amount, const uint8_t precision );\n   std::string price_to_string( const graphene::protocol::price& _price,\n                                const uint8_t base_precision,\n                                const uint8_t quote_precision );\n   std::string price_to_string( const graphene::protocol::price& _price,\n                                const graphene::chain::asset_object& _base,\n                                const graphene::chain::asset_object& _quote );\n   std::string price_diff_percent_string( const graphene::protocol::price& old_price,\n                                          const graphene::protocol::price& new_price );\n} }\n"
  },
  {
    "path": "libraries/app/plugin.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\nnamespace graphene { namespace app {\n\nstd::string plugin::plugin_name()const\n{\n   return \"<unknown plugin>\";\n}\n\nstd::string plugin::plugin_description()const\n{\n   return \"<no description>\";\n}\n\nvoid plugin::plugin_initialize( const boost::program_options::variables_map& options )\n{\n   // nothing to do\n}\n\nvoid plugin::plugin_startup()\n{\n   // nothing to do\n}\n\nvoid plugin::plugin_shutdown()\n{\n   // nothing to do\n}\n\nvoid plugin::plugin_set_program_options(\n   boost::program_options::options_description& command_line_options,\n   boost::program_options::options_description& config_file_options\n)\n{\n   // nothing to do\n}\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/app/util.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/multiprecision/cpp_int.hpp>\n\n#include <graphene/app/util.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/chain/asset_object.hpp>\n\nnamespace graphene { namespace app {\n\nusing boost::multiprecision::uint256_t;\n\nstatic fc::uint128_t to_capped_128( const uint256_t& t )\n{\n   if( t >= std::numeric_limits<fc::uint128_t>::max() )\n      return std::numeric_limits<fc::uint128_t>::max();\n   return static_cast<fc::uint128_t>(t);\n}\n\nstd::string uint128_amount_to_string( const fc::uint128_t& amount, const uint8_t precision )\n{ try {\n   std::string s = fc::variant( amount, 2 ).as_string();\n   if( precision == 0 || amount == fc::uint128_t() )\n      return s;\n\n   std::stringstream ss;\n   uint8_t pos = s.find_last_not_of( '0' ); // should be >= 0\n   uint8_t len = s.size();\n   if( len > precision )\n   {\n      uint8_t left_len = len - precision;\n      ss << s.substr( 0, left_len );\n      if( pos >= left_len )\n         ss << '.' << s.substr( left_len, pos - left_len + 1 );\n   }\n   else\n   {\n      ss << \"0.\";\n      for( uint8_t i = precision - len; i > 0; --i )\n         ss << '0';\n      ss << s.substr( 0, pos + 1 );\n   }\n   return ss.str();\n} FC_CAPTURE_AND_RETHROW( (amount)(precision) ) }\n\nstd::string price_to_string( const graphene::protocol::price& _price,\n                             const uint8_t base_precision,\n                             const uint8_t quote_precision )\n{ try {\n   if( _price.base.amount == 0 )\n      return \"0\";\n   FC_ASSERT( _price.base.amount >= 0 );\n   FC_ASSERT( _price.quote.amount >= 0 );\n   FC_ASSERT( base_precision <= 19 );\n   FC_ASSERT( quote_precision <= 19 );\n   graphene::protocol::price new_price = _price;\n   if( new_price.quote.amount == 0 )\n   {\n      new_price.base.amount = std::numeric_limits<int64_t>::max();\n      new_price.quote.amount = 1;\n   }\n\n   // times (10**19) so won't overflow but have good accuracy\n   fc::uint128_t price128 = fc::uint128_t( new_price.base.amount.value ) * uint64_t(10000000000000000000ULL)\n                                                                     / new_price.quote.amount.value;\n\n   return uint128_amount_to_string( price128, 19 + base_precision - quote_precision );\n} FC_CAPTURE_AND_RETHROW( (_price)(base_precision)(quote_precision) ) }\n\nstd::string price_to_string( const graphene::protocol::price& _price,\n                             const graphene::chain::asset_object& _base,\n                             const graphene::chain::asset_object& _quote )\n{ try {\n   if( _price.base.asset_id == _base.id && _price.quote.asset_id == _quote.id )\n      return price_to_string( _price, _base.precision, _quote.precision );\n   else if( _price.base.asset_id == _quote.id && _price.quote.asset_id == _base.id )\n      return price_to_string( ~_price, _base.precision, _quote.precision );\n   else\n      FC_ASSERT( !\"bad parameters\" );\n} FC_CAPTURE_AND_RETHROW( (_price)(_base)(_quote) ) }\n\nstd::string price_diff_percent_string( const graphene::protocol::price& old_price,\n                                       const graphene::protocol::price& new_price )\n{ try {\n   FC_ASSERT( old_price.base.asset_id == new_price.base.asset_id );\n   FC_ASSERT( old_price.quote.asset_id == new_price.quote.asset_id );\n   FC_ASSERT( old_price.base.amount >= 0 );\n   FC_ASSERT( old_price.quote.amount >= 0 );\n   FC_ASSERT( new_price.base.amount >= 0 );\n   FC_ASSERT( new_price.quote.amount >= 0 );\n   graphene::protocol::price old_price1 = old_price;\n   if( old_price.base.amount == 0 )\n   {\n      old_price1.base.amount = 1;\n      old_price1.quote.amount = std::numeric_limits<int64_t>::max();\n   }\n   else if( old_price.quote.amount == 0 )\n   {\n      old_price1.base.amount = std::numeric_limits<int64_t>::max();\n      old_price1.quote.amount = 1;\n   }\n   graphene::protocol::price new_price1 = new_price;\n   if( new_price.base.amount == 0 )\n   {\n      new_price1.base.amount = 1;\n      new_price1.quote.amount = std::numeric_limits<int64_t>::max();\n   }\n   else if( new_price.quote.amount == 0 )\n   {\n      new_price1.base.amount = std::numeric_limits<int64_t>::max();\n      new_price1.quote.amount = 1;\n   }\n\n   // change = new/old - 1 = (new_base/new_quote)/(old_base/old_quote) - 1\n   //        = (new_base * old_quote) / (new_quote * old_base) - 1\n   //        = (new_base * old_quote - new_quote * old_base) / (new_quote * old_base)\n   uint256_t new256 = uint256_t( new_price1.base.amount.value ) * old_price1.quote.amount.value;\n   uint256_t old256 = uint256_t( old_price1.base.amount.value ) * new_price1.quote.amount.value;\n   bool non_negative = (new256 >= old256);\n   uint256_t diff256;\n   if( non_negative )\n      diff256 = new256 - old256;\n   else\n      diff256 = old256 - new256;\n   diff256 = diff256 * 10000 / old256;\n   std::string diff_str = uint128_amount_to_string( to_capped_128(diff256), 2 ); // at most 2 decimal digits\n   if( non_negative || diff_str == \"0\" )\n      return diff_str;\n   else\n      return \"-\" + diff_str;\n} FC_CAPTURE_AND_RETHROW( (old_price)(new_price) ) }\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/chain/CMakeLists.txt",
    "content": "\nset_source_files_properties( \"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp\" PROPERTIES GENERATED TRUE )\n\nfile(GLOB HEADERS \"include/graphene/chain/*.hpp\")\n\nif( GRAPHENE_DISABLE_UNITY_BUILD )\n   set( GRAPHENE_DB_FILES\n        db_balance.cpp\n        db_block.cpp\n        db_debug.cpp\n        db_genesis.cpp\n        db_getter.cpp\n        db_init.cpp\n        db_maint.cpp\n        db_management.cpp\n        db_market.cpp\n        db_notify.cpp\n        db_update.cpp\n        db_witness_schedule.cpp\n      )\n   message( STATUS \"Graphene database unity build disabled\" )\nelse( GRAPHENE_DISABLE_UNITY_BUILD )\n   set( GRAPHENE_DB_FILES\n        database.cpp )\n   message( STATUS \"Graphene database unity build enabled\" )\nendif( GRAPHENE_DISABLE_UNITY_BUILD )\n\n## SORT .cpp by most likely to change / break compile\nadd_library( graphene_chain\n\n             # As database takes the longest to compile, start it first\n             ${GRAPHENE_DB_FILES}\n             fork_database.cpp\n\n             genesis_state.cpp\n             get_config.cpp\n             exceptions.cpp\n\n             evaluator.cpp\n             liquidity_pool_evaluator.cpp\n             samet_fund_evaluator.cpp\n             credit_offer_evaluator.cpp\n             balance_evaluator.cpp\n             account_evaluator.cpp\n             assert_evaluator.cpp\n             witness_evaluator.cpp\n             committee_member_evaluator.cpp\n             asset_evaluator.cpp\n             transfer_evaluator.cpp\n             proposal_evaluator.cpp\n             market_evaluator.cpp\n             ticket_evaluator.cpp\n             vesting_balance_evaluator.cpp\n             withdraw_permission_evaluator.cpp\n             worker_evaluator.cpp\n             htlc_evaluator.cpp\n             confidential_evaluator.cpp\n             special_authority_evaluation.cpp\n             custom_authority_evaluator.cpp\n             buyback.cpp\n\n             account_object.cpp\n             asset_object.cpp\n             fba_object.cpp\n             market_object.cpp\n             proposal_object.cpp\n             vesting_balance_object.cpp\n             ticket_object.cpp\n             small_objects.cpp\n\n             block_database.cpp\n\n             is_authorized_asset.cpp\n\n             ${HEADERS}\n             \"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp\"\n           )\n\nadd_dependencies( graphene_chain build_hardfork_hpp )\ntarget_link_libraries( graphene_chain graphene_db graphene_protocol fc )\ntarget_include_directories( graphene_chain\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" \"${CMAKE_CURRENT_BINARY_DIR}/include\" )\n\nset( GRAPHENE_CHAIN_BIG_FILES\n     db_init.cpp\n     db_genesis.cpp\n     db_block.cpp\n     db_maint.cpp\n     db_market.cpp\n     database.cpp\n     block_database.cpp\n   )\n\nif(MSVC)\n  set_source_files_properties( ${GRAPHENE_CHAIN_BIG_FILES} PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nelse( MSVC )\n   if( MINGW )\n      # Note: Even with the big-obj property and the -Os property set,\n      #       \"string table overflow\" and \"File too big\" errors may still occur on database.cpp.\n      #       Can set GRAPHENE_DISABLE_UNITY_BUILD to ON to get around the issue.\n      set_source_files_properties( ${GRAPHENE_CHAIN_BIG_FILES} PROPERTIES COMPILE_FLAGS -Wa,-mbig-obj )\n   endif( MINGW )\nendif(MSVC)\n\n# Support for CMake < 3.4\nif (${CMAKE_VERSION} VERSION_LESS \"3.4.0\")\n    MESSAGE(STATUS \"Configuring for legacy CMake (CMake version ${CMAKE_VERSION} older than 3.4.0)\")\n    MESSAGE(STATUS \"  Target propertes not supported (SOURCE_DIR, BINARY_DIR)\")\n\n    set(GRAPHENE_CHAIN_BIN_LEGACY \"${PROJECT_BINARY_DIR}/libraries/chain\" CACHE STRING \"Path to fc chain\")\n    set(GRAPHENE_CHAIN_SOURCE_LEGACY \"${PROJECT_SOURCE_DIR}/libraries/chain\" CACHE STRING \"Path to fc chain\")\nendif ()\n\nINSTALL( TARGETS\n   graphene_chain\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/chain\" )\n"
  },
  {
    "path": "libraries/chain/account_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/account_evaluator.hpp>\n#include <graphene/chain/buyback.hpp>\n#include <graphene/chain/buyback_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/internal_exceptions.hpp>\n#include <graphene/chain/special_authority_evaluation.hpp>\n#include <graphene/chain/special_authority_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n\n#include <algorithm>\n\nnamespace graphene { namespace chain {\n\nvoid verify_authority_accounts( const database& db, const authority& a )\n{\n   const auto& chain_params = db.get_global_properties().parameters;\n   GRAPHENE_ASSERT(\n      a.num_auths() <= chain_params.maximum_authority_membership,\n      internal_verify_auth_max_auth_exceeded,\n      \"Maximum authority membership exceeded\" );\n   for( const auto& acnt : a.account_auths )\n   {\n      GRAPHENE_ASSERT( db.find( acnt.first ) != nullptr,\n         internal_verify_auth_account_not_found,\n         \"Account ${a} specified in authority does not exist\",\n         (\"a\", acnt.first) );\n   }\n}\n\nvoid verify_account_votes( const database& db, const account_options& options )\n{\n   // ensure account's votes satisfy requirements\n   // NB only the part of vote checking that requires chain state is here,\n   // the rest occurs in account_options::validate()\n\n   const auto& gpo = db.get_global_properties();\n   const auto& chain_params = gpo.parameters;\n\n   FC_ASSERT( options.num_witness <= chain_params.maximum_witness_count,\n              \"Voted for more witnesses than currently allowed (${c})\", (\"c\", chain_params.maximum_witness_count) );\n   FC_ASSERT( options.num_committee <= chain_params.maximum_committee_count,\n              \"Voted for more committee members than currently allowed (${c})\", (\"c\", chain_params.maximum_committee_count) );\n\n   FC_ASSERT( db.find(options.voting_account), \"Invalid proxy account specified.\" );\n\n   uint32_t max_vote_id = gpo.next_available_vote_id;\n   bool has_worker_votes = false;\n   for( auto id : options.votes )\n   {\n      FC_ASSERT( id < max_vote_id, \"Can not vote for ${id} which does not exist.\", (\"id\",id) );\n      if( id.type() == vote_id_type::worker )\n         has_worker_votes = true;\n   }\n\n   if( has_worker_votes && (db.head_block_time() >= HARDFORK_607_TIME) )\n   {\n      const auto& against_worker_idx = db.get_index_type<worker_index>().indices().get<by_vote_against>();\n      for( auto id : options.votes )\n      {\n         if( id.type() == vote_id_type::worker )\n         {\n            FC_ASSERT( against_worker_idx.find( id ) == against_worker_idx.end(),\n                       \"Can no longer vote against a worker.\" );\n         }\n      }\n   }\n   if ( db.head_block_time() >= HARDFORK_CORE_143_TIME ) {\n      const auto& approve_worker_idx = db.get_index_type<worker_index>().indices().get<by_vote_for>();\n      const auto& committee_idx = db.get_index_type<committee_member_index>().indices().get<by_vote_id>();\n      const auto& witness_idx = db.get_index_type<witness_index>().indices().get<by_vote_id>();\n      for ( auto id : options.votes ) {\n         switch ( id.type() ) {\n            case vote_id_type::committee:\n               FC_ASSERT( committee_idx.find(id) != committee_idx.end(),\n                          \"Can not vote for ${id} which does not exist.\", (\"id\",id) );\n               break;\n            case vote_id_type::witness:\n               FC_ASSERT( witness_idx.find(id) != witness_idx.end(),\n                          \"Can not vote for ${id} which does not exist.\", (\"id\",id) );\n               break;\n            case vote_id_type::worker:\n               FC_ASSERT( approve_worker_idx.find( id ) != approve_worker_idx.end(),\n                          \"Can not vote for ${id} which does not exist.\", (\"id\",id) );\n               break;\n            default:\n               FC_THROW( \"Invalid Vote Type: ${id}\", (\"id\", id) );\n               break;\n         }\n      }\n   }\n}\n\nvoid_result account_create_evaluator::do_evaluate( const account_create_operation& op )\n{ try {\n   database& d = db();\n\n   FC_ASSERT( fee_paying_account->is_lifetime_member(), \"Only Lifetime members may register an account.\" );\n   FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), \"The referrer must be either a lifetime or annual subscriber.\" );\n\n   try\n   {\n      verify_authority_accounts( d, op.owner );\n      verify_authority_accounts( d, op.active );\n   }\n   GRAPHENE_RECODE_EXC( internal_verify_auth_max_auth_exceeded, account_create_max_auth_exceeded )\n   GRAPHENE_RECODE_EXC( internal_verify_auth_account_not_found, account_create_auth_account_not_found )\n\n   if( op.extensions.value.owner_special_authority.valid() )\n      evaluate_special_authority( d, *op.extensions.value.owner_special_authority );\n   if( op.extensions.value.active_special_authority.valid() )\n      evaluate_special_authority( d, *op.extensions.value.active_special_authority );\n   if( op.extensions.value.buyback_options.valid() )\n      evaluate_buyback_account_options( d, *op.extensions.value.buyback_options );\n   verify_account_votes( d, op.options );\n\n   auto& acnt_indx = d.get_index_type<account_index>();\n   if( op.name.size() )\n   {\n      auto current_account_itr = acnt_indx.indices().get<by_name>().find( op.name );\n      FC_ASSERT( current_account_itr == acnt_indx.indices().get<by_name>().end(),\n                 \"Account '${a}' already exists.\", (\"a\",op.name) );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nobject_id_type account_create_evaluator::do_apply( const account_create_operation& o )\n{ try {\n\n   database& d = db();\n   uint16_t referrer_percent = o.referrer_percent;\n   bool has_small_percent = (\n         (db().head_block_time() <= HARDFORK_453_TIME)\n      && (o.referrer != o.registrar  )\n      && (o.referrer_percent != 0    )\n      && (o.referrer_percent <= 0x100)\n      );\n\n   if( has_small_percent )\n   {\n      if( referrer_percent >= 100 )\n      {\n         wlog( \"between 100% and 0x100%:  ${o}\", (\"o\", o) );\n      }\n      referrer_percent = referrer_percent*100;\n      if( referrer_percent > GRAPHENE_100_PERCENT )\n         referrer_percent = GRAPHENE_100_PERCENT;\n   }\n\n   const auto& global_properties = d.get_global_properties();\n\n   const auto& new_acnt_object = d.create<account_object>( [&o,&d,&global_properties,referrer_percent]\n                                                           ( account_object& obj )\n   {\n         obj.registrar = o.registrar;\n         obj.referrer = o.referrer;\n         obj.lifetime_referrer = o.referrer(d).lifetime_referrer;\n\n         const auto& params = global_properties.parameters;\n         obj.network_fee_percentage = params.network_percent_of_fee;\n         obj.lifetime_referrer_fee_percentage = params.lifetime_referrer_percent_of_fee;\n         obj.referrer_rewards_percentage = referrer_percent;\n\n         obj.name             = o.name;\n         obj.owner            = o.owner;\n         obj.active           = o.active;\n         obj.options          = o.options;\n         obj.num_committee_voted = o.options.num_committee_voted();\n         obj.statistics = d.create<account_statistics_object>([&obj](account_statistics_object& s){\n                             s.owner = obj.id;\n                             s.name = obj.name;\n                             s.is_voting = obj.options.is_voting();\n                          }).id;\n\n         if( o.extensions.value.owner_special_authority.valid() )\n            obj.owner_special_authority = *(o.extensions.value.owner_special_authority);\n         if( o.extensions.value.active_special_authority.valid() )\n            obj.active_special_authority = *(o.extensions.value.active_special_authority);\n         if( o.extensions.value.buyback_options.valid() )\n         {\n            obj.allowed_assets = o.extensions.value.buyback_options->markets;\n            obj.allowed_assets->emplace( o.extensions.value.buyback_options->asset_to_buy );\n         }\n\n         obj.creation_block_num = d._current_block_num;\n         obj.creation_time      = d._current_block_time;\n   });\n\n   const auto& dynamic_properties = d.get_dynamic_global_properties();\n   d.modify(dynamic_properties, [](dynamic_global_property_object& p) {\n      ++p.accounts_registered_this_interval;\n   });\n\n   if( dynamic_properties.accounts_registered_this_interval % global_properties.parameters.accounts_per_fee_scale == 0\n         && global_properties.parameters.account_fee_scale_bitshifts != 0 )\n   {\n      d.modify(global_properties, [](global_property_object& p) {\n         p.parameters.get_mutable_fees().get<account_create_operation>().basic_fee <<= p.parameters.account_fee_scale_bitshifts;\n      });\n   }\n\n   if(    o.extensions.value.owner_special_authority.valid()\n       || o.extensions.value.active_special_authority.valid() )\n   {\n      db().create< special_authority_object >( [&]( special_authority_object& sa )\n      {\n         sa.account = new_acnt_object.id;\n      } );\n   }\n\n   if( o.extensions.value.buyback_options.valid() )\n   {\n      asset_id_type asset_to_buy = o.extensions.value.buyback_options->asset_to_buy;\n\n      d.create< buyback_object >( [&]( buyback_object& bo )\n      {\n         bo.asset_to_buy = asset_to_buy;\n      } );\n\n      d.modify( asset_to_buy(d), [&]( asset_object& a )\n      {\n         a.buyback_account = new_acnt_object.id;\n      } );\n   }\n\n   return new_acnt_object.id;\n} FC_CAPTURE_AND_RETHROW((o)) }\n\n\nvoid_result account_update_evaluator::do_evaluate( const account_update_operation& o )\n{ try {\n   database& d = db();\n\n   try\n   {\n      if( o.owner )  verify_authority_accounts( d, *o.owner );\n      if( o.active ) verify_authority_accounts( d, *o.active );\n   }\n   GRAPHENE_RECODE_EXC( internal_verify_auth_max_auth_exceeded, account_update_max_auth_exceeded )\n   GRAPHENE_RECODE_EXC( internal_verify_auth_account_not_found, account_update_auth_account_not_found )\n\n   if( o.extensions.value.owner_special_authority.valid() )\n      evaluate_special_authority( d, *o.extensions.value.owner_special_authority );\n   if( o.extensions.value.active_special_authority.valid() )\n      evaluate_special_authority( d, *o.extensions.value.active_special_authority );\n\n   acnt = &o.account(d);\n\n   if( o.new_options.valid() )\n      verify_account_votes( d, *o.new_options );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result account_update_evaluator::do_apply( const account_update_operation& o )\n{ try {\n   database& d = db();\n\n   bool sa_before = acnt->has_special_authority();\n\n   // update account statistics\n   if( o.new_options.valid() )\n   {\n      if ( o.new_options->voting_account != acnt->options.voting_account\n           || o.new_options->votes != acnt->options.votes )\n      {\n         d.modify( acnt->statistics( d ), [&d,&o]( account_statistics_object& aso )\n         {\n            aso.is_voting = o.new_options->is_voting();\n            aso.last_vote_time = d.head_block_time();\n         } );\n      }\n   }\n\n   // update account object\n   d.modify( *acnt, [&o](account_object& a){\n      if( o.owner )\n      {\n         a.owner = *o.owner;\n         a.top_n_control_flags = 0;\n      }\n      if( o.active )\n      {\n         a.active = *o.active;\n         a.top_n_control_flags = 0;\n      }\n      if( o.new_options )\n      {\n         a.options = *o.new_options;\n         a.num_committee_voted = a.options.num_committee_voted();\n      }\n      if( o.extensions.value.owner_special_authority.valid() )\n      {\n         a.owner_special_authority = *(o.extensions.value.owner_special_authority);\n         a.top_n_control_flags = 0;\n      }\n      if( o.extensions.value.active_special_authority.valid() )\n      {\n         a.active_special_authority = *(o.extensions.value.active_special_authority);\n         a.top_n_control_flags = 0;\n      }\n   });\n\n   bool sa_after = acnt->has_special_authority();\n\n   if( sa_before && (!sa_after) )\n   {\n      const auto& sa_idx = d.get_index_type< special_authority_index >().indices().get<by_account>();\n      auto sa_it = sa_idx.find( o.account );\n      assert( sa_it != sa_idx.end() );\n      d.remove( *sa_it );\n   }\n   else if( (!sa_before) && sa_after )\n   {\n      d.create< special_authority_object >( [&]( special_authority_object& sa )\n      {\n         sa.account = o.account;\n      } );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result account_whitelist_evaluator::do_evaluate(const account_whitelist_operation& o)\n{ try {\n   database& d = db();\n\n   listed_account = &o.account_to_list(d);\n   if( !d.get_global_properties().parameters.allow_non_member_whitelists )\n      FC_ASSERT( o.authorizing_account(d).is_lifetime_member(), \"The authorizing account must be a lifetime member.\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result account_whitelist_evaluator::do_apply(const account_whitelist_operation& o)\n{ try {\n   database& d = db();\n\n   d.modify(*listed_account, [&o](account_object& a) {\n      if( o.new_listing & o.white_listed )\n         a.whitelisting_accounts.insert(o.authorizing_account);\n      else\n         a.whitelisting_accounts.erase(o.authorizing_account);\n\n      if( o.new_listing & o.black_listed )\n         a.blacklisting_accounts.insert(o.authorizing_account);\n      else\n         a.blacklisting_accounts.erase(o.authorizing_account);\n   });\n\n   /** for tracking purposes only, this state is not needed to evaluate */\n   d.modify( o.authorizing_account(d), [&]( account_object& a ) {\n     if( o.new_listing & o.white_listed )\n        a.whitelisted_accounts.insert( o.account_to_list );\n     else\n        a.whitelisted_accounts.erase( o.account_to_list );\n\n     if( o.new_listing & o.black_listed )\n        a.blacklisted_accounts.insert( o.account_to_list );\n     else\n        a.blacklisted_accounts.erase( o.account_to_list );\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result account_upgrade_evaluator::do_evaluate(const account_upgrade_evaluator::operation_type& o)\n{ try {\n   database& d = db();\n\n   account = &d.get(o.account_to_upgrade);\n   FC_ASSERT(!account->is_lifetime_member());\n\n   return {};\n//} FC_CAPTURE_AND_RETHROW( (o) ) }\n} FC_RETHROW_EXCEPTIONS( error, \"Unable to upgrade account '${a}'\", (\"a\",o.account_to_upgrade(db()).name) ) }\n\nvoid_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator::operation_type& o)\n{ try {\n   database& d = db();\n\n   d.modify(*account, [&](account_object& a) {\n      if( o.upgrade_to_lifetime_member )\n      {\n         // Upgrade to lifetime member. I don't care what the account was before.\n         a.statistics(d).process_fees(a, d);\n         a.membership_expiration_date = time_point_sec::maximum();\n         a.referrer = a.registrar = a.lifetime_referrer = a.get_id();\n         a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage;\n      } else if( a.is_annual_member(d.head_block_time()) ) {\n         // Renew an annual subscription that's still in effect.\n         FC_ASSERT( d.head_block_time() <= HARDFORK_613_TIME );\n         FC_ASSERT(a.membership_expiration_date - d.head_block_time() < fc::days(3650),\n                   \"May not extend annual membership more than a decade into the future.\");\n         a.membership_expiration_date += fc::days(365);\n      } else {\n         // Upgrade from basic account.\n         FC_ASSERT( d.head_block_time() <= HARDFORK_613_TIME );\n         a.statistics(d).process_fees(a, d);\n         assert(a.is_basic_account(d.head_block_time()));\n         a.referrer = a.get_id();\n         a.membership_expiration_date = d.head_block_time() + fc::days(365);\n      }\n   });\n\n   return {};\n} FC_RETHROW_EXCEPTIONS( error, \"Unable to upgrade account '${a}'\", (\"a\",o.account_to_upgrade(db()).name) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/account_object.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace chain {\n\nshare_type cut_fee(share_type a, uint16_t p)\n{\n   if( a == 0 || p == 0 )\n      return 0;\n   if( p == GRAPHENE_100_PERCENT )\n      return a;\n\n   fc::uint128_t r = a.value;\n   r *= p;\n   r /= GRAPHENE_100_PERCENT;\n   return static_cast<uint64_t>(r);\n}\n\nvoid account_balance_object::adjust_balance(const asset& delta)\n{\n   assert(delta.asset_id == asset_type);\n   balance += delta.amount;\n   if( asset_type == asset_id_type() ) // CORE asset\n      maintenance_flag = true;\n}\n\nvoid account_statistics_object::process_fees(const account_object& a, database& d) const\n{\n   if( pending_fees > 0 || pending_vested_fees > 0 )\n   {\n      auto pay_out_fees = [&](const account_object& account, share_type core_fee_total, bool require_vesting)\n      {\n         // Check the referrer -- if he's no longer a member, pay to the lifetime referrer instead.\n         // No need to check the registrar; registrars are required to be lifetime members.\n         if( account.referrer(d).is_basic_account(d.head_block_time()) )\n            d.modify( account, [](account_object& acc) {\n               acc.referrer = acc.lifetime_referrer;\n            });\n\n         share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage);\n         assert( network_cut <= core_fee_total );\n\n         share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage);\n         share_type referral = core_fee_total - network_cut - lifetime_cut;\n\n         d.modify( d.get_core_dynamic_data(), [network_cut](asset_dynamic_data_object& addo) {\n            addo.accumulated_fees += network_cut;\n         });\n\n         // Potential optimization: Skip some of this math and object lookups by special casing on the account type.\n         // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to\n         // it directly.\n         share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage);\n         share_type registrar_cut = referral - referrer_cut;\n\n         d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting);\n         d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting);\n         d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting);\n\n         assert( referrer_cut + registrar_cut + network_cut + lifetime_cut == core_fee_total );\n      };\n\n      pay_out_fees(a, pending_fees, true);\n      pay_out_fees(a, pending_vested_fees, false);\n\n      d.modify(*this, [&](account_statistics_object& s) {\n         s.lifetime_fees_paid += pending_fees + pending_vested_fees;\n         s.pending_fees = 0;\n         s.pending_vested_fees = 0;\n      });\n   }\n}\n\nvoid account_statistics_object::pay_fee( share_type core_fee, share_type cashback_vesting_threshold )\n{\n   if( core_fee > cashback_vesting_threshold )\n      pending_fees += core_fee;\n   else\n      pending_vested_fees += core_fee;\n}\n\nset<account_id_type> account_member_index::get_account_members(const account_object& a)const\n{\n   set<account_id_type> result;\n   for( auto auth : a.owner.account_auths )\n      result.insert(auth.first);\n   for( auto auth : a.active.account_auths )\n      result.insert(auth.first);\n   return result;\n}\nset<public_key_type, pubkey_comparator> account_member_index::get_key_members(const account_object& a)const\n{\n   set<public_key_type, pubkey_comparator> result;\n   for( auto auth : a.owner.key_auths )\n      result.insert(auth.first);\n   for( auto auth : a.active.key_auths )\n      result.insert(auth.first);\n   result.insert( a.options.memo_key );\n   return result;\n}\nset<address> account_member_index::get_address_members(const account_object& a)const\n{\n   set<address> result;\n   for( auto auth : a.owner.address_auths )\n      result.insert(auth.first);\n   for( auto auth : a.active.address_auths )\n      result.insert(auth.first);\n   return result;\n}\n\nvoid account_member_index::object_inserted(const object& obj)\n{\n    assert( dynamic_cast<const account_object*>(&obj) ); // for debug only\n    const account_object& a = static_cast<const account_object&>(obj);\n    const account_id_type account_id = a.get_id();\n\n    auto account_members = get_account_members(a);\n    for( auto item : account_members )\n       account_to_account_memberships[item].insert(account_id);\n\n    auto key_members = get_key_members(a);\n    for( auto item : key_members )\n       account_to_key_memberships[item].insert(account_id);\n\n    auto address_members = get_address_members(a);\n    for( auto item : address_members )\n       account_to_address_memberships[item].insert(account_id);\n}\n\nvoid account_member_index::object_removed(const object& obj)\n{\n    assert( dynamic_cast<const account_object*>(&obj) ); // for debug only\n    const account_object& a = static_cast<const account_object&>(obj);\n    const account_id_type account_id = a.get_id();\n\n    auto key_members = get_key_members(a);\n    for( auto item : key_members )\n       account_to_key_memberships[item].erase( account_id );\n\n    auto address_members = get_address_members(a);\n    for( auto item : address_members )\n       account_to_address_memberships[item].erase( account_id );\n\n    auto account_members = get_account_members(a);\n    for( auto item : account_members )\n       account_to_account_memberships[item].erase( account_id );\n}\n\nvoid account_member_index::about_to_modify(const object& before)\n{\n   before_key_members.clear();\n   before_account_members.clear();\n   assert( dynamic_cast<const account_object*>(&before) ); // for debug only\n   const account_object& a = static_cast<const account_object&>(before);\n   before_key_members     = get_key_members(a);\n   before_address_members = get_address_members(a);\n   before_account_members = get_account_members(a);\n}\n\nvoid account_member_index::object_modified(const object& after)\n{\n    assert( dynamic_cast<const account_object*>(&after) ); // for debug only\n    const account_object& a = static_cast<const account_object&>(after);\n    const account_id_type account_id = a.get_id();\n\n    {\n       set<account_id_type> after_account_members = get_account_members(a);\n       vector<account_id_type> removed;\n       removed.reserve(before_account_members.size());\n       std::set_difference(before_account_members.begin(), before_account_members.end(),\n                           after_account_members.begin(), after_account_members.end(),\n                           std::inserter(removed, removed.end()));\n\n       for( auto itr = removed.begin(); itr != removed.end(); ++itr )\n          account_to_account_memberships[*itr].erase(account_id);\n\n       vector<account_id_type> added;\n       added.reserve(after_account_members.size());\n       std::set_difference(after_account_members.begin(), after_account_members.end(),\n                           before_account_members.begin(), before_account_members.end(),\n                           std::inserter(added, added.end()));\n\n       for( auto itr = added.begin(); itr != added.end(); ++itr )\n          account_to_account_memberships[*itr].insert(account_id);\n    }\n\n\n    {\n       set<public_key_type, pubkey_comparator> after_key_members = get_key_members(a);\n\n       vector<public_key_type> removed;\n       removed.reserve(before_key_members.size());\n       std::set_difference(before_key_members.begin(), before_key_members.end(),\n                           after_key_members.begin(), after_key_members.end(),\n                           std::inserter(removed, removed.end()));\n\n       for( auto itr = removed.begin(); itr != removed.end(); ++itr )\n          account_to_key_memberships[*itr].erase(account_id);\n\n       vector<public_key_type> added;\n       added.reserve(after_key_members.size());\n       std::set_difference(after_key_members.begin(), after_key_members.end(),\n                           before_key_members.begin(), before_key_members.end(),\n                           std::inserter(added, added.end()));\n\n       for( auto itr = added.begin(); itr != added.end(); ++itr )\n          account_to_key_memberships[*itr].insert(account_id);\n    }\n\n    {\n       set<address> after_address_members = get_address_members(a);\n\n       vector<address> removed;\n       removed.reserve(before_address_members.size());\n       std::set_difference(before_address_members.begin(), before_address_members.end(),\n                           after_address_members.begin(), after_address_members.end(),\n                           std::inserter(removed, removed.end()));\n\n       for( auto itr = removed.begin(); itr != removed.end(); ++itr )\n          account_to_address_memberships[*itr].erase(account_id);\n\n       vector<address> added;\n       added.reserve(after_address_members.size());\n       std::set_difference(after_address_members.begin(), after_address_members.end(),\n                           before_address_members.begin(), before_address_members.end(),\n                           std::inserter(added, added.end()));\n\n       for( auto itr = added.begin(); itr != added.end(); ++itr )\n          account_to_address_memberships[*itr].insert(account_id);\n    }\n\n}\n\nconst uint8_t  balances_by_account_index::bits = 20;\nconst uint64_t balances_by_account_index::mask = (1ULL << balances_by_account_index::bits) - 1;\n\nvoid balances_by_account_index::object_inserted( const object& obj )\n{\n   const auto& abo = dynamic_cast< const account_balance_object& >( obj );\n   while( balances.size() < (abo.owner.instance.value >> bits) + 1 )\n   {\n      balances.reserve( (abo.owner.instance.value >> bits) + 1 );\n      balances.resize( balances.size() + 1 );\n      balances.back().resize( 1ULL << bits );\n   }\n   balances[abo.owner.instance.value >> bits][abo.owner.instance.value & mask][abo.asset_type] = &abo;\n}\n\nvoid balances_by_account_index::object_removed( const object& obj )\n{\n   const auto& abo = dynamic_cast< const account_balance_object& >( obj );\n   if( balances.size() < (abo.owner.instance.value >> bits) + 1 ) return;\n   balances[abo.owner.instance.value >> bits][abo.owner.instance.value & mask].erase( abo.asset_type );\n}\n\nvoid balances_by_account_index::about_to_modify( const object& before )\n{\n   ids_being_modified.emplace( before.id );\n}\n\nvoid balances_by_account_index::object_modified( const object& after  )\n{\n   FC_ASSERT( ids_being_modified.top() == after.id, \"Modification of ID is not supported!\");\n   ids_being_modified.pop();\n}\n\nconst map< asset_id_type, const account_balance_object* >& balances_by_account_index::get_account_balances( const account_id_type& acct )const\n{\n   static const map< asset_id_type, const account_balance_object* > _empty;\n\n   if( balances.size() < (acct.instance.value >> bits) + 1 ) return _empty;\n   return balances[acct.instance.value >> bits][acct.instance.value & mask];\n}\n\nconst account_balance_object* balances_by_account_index::get_account_balance( const account_id_type& acct, const asset_id_type& asset )const\n{\n   if( balances.size() < (acct.instance.value >> bits) + 1 ) return nullptr;\n   const auto& mine = balances[acct.instance.value >> bits][acct.instance.value & mask];\n   const auto itr = mine.find( asset );\n   if( mine.end() == itr ) return nullptr;\n   return itr->second;\n}\n\n} } // graphene::chain\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::account_object,\n                    (graphene::db::object),\n                    (membership_expiration_date)(registrar)(referrer)(lifetime_referrer)\n                    (network_fee_percentage)(lifetime_referrer_fee_percentage)(referrer_rewards_percentage)\n                    (name)(owner)(active)(options)(num_committee_voted)(statistics)\n                    (whitelisting_accounts)(blacklisting_accounts)\n                    (whitelisted_accounts)(blacklisted_accounts)\n                    (cashback_vb)\n                    (owner_special_authority)(active_special_authority)\n                    (top_n_control_flags)\n                    (allowed_assets)\n                    (creation_block_num)(creation_time)\n                    )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::account_balance_object,\n                    (graphene::db::object),\n                    (owner)(asset_type)(balance)(maintenance_flag) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::account_statistics_object,\n                    (graphene::chain::object),\n                    (owner)(name)\n                    (most_recent_op)\n                    (total_ops)(removed_ops)\n                    (total_core_in_orders)\n                    (total_core_inactive)(total_core_pob)(total_core_pol)\n                    (total_pob_value)(total_pol_value)\n                    (core_in_balance)\n                    (has_cashback_vb)\n                    (is_voting)\n                    (last_vote_time)\n                    (vp_all)(vp_active)(vp_committee)(vp_witness)(vp_worker)\n                    (vote_tally_time)\n                    (lifetime_fees_paid)\n                    (pending_fees)(pending_vested_fees)\n                  )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::account_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::account_balance_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::account_statistics_object )\n"
  },
  {
    "path": "libraries/chain/assert_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/assert_evaluator.hpp>\n#include <graphene/chain/block_summary_object.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <sstream>\n\nnamespace graphene { namespace chain {\n\nstruct predicate_evaluator\n{\n   typedef void result_type;\n   const database& db;\n\n   predicate_evaluator( const database& d ):db(d){}\n\n   void operator()( const  account_name_eq_lit_predicate& p )const\n   {\n      FC_ASSERT( p.account_id(db).name == p.name );\n   }\n   void operator()( const  asset_symbol_eq_lit_predicate& p )const\n   {\n      FC_ASSERT( p.asset_id(db).symbol == p.symbol );\n   }\n   void operator()( const block_id_predicate& p )const\n   {\n      FC_ASSERT( block_summary_id_type( block_header::num_from_id( p.id ) & 0xffff )(db).block_id == p.id );\n   }\n};\n\nvoid_result assert_evaluator::do_evaluate( const assert_operation& o )\n{ try {\n   const database& _db = db();\n   uint32_t skip = _db.get_node_properties().skip_flags;\n   auto max_predicate_opcode = _db.get_global_properties().parameters.max_predicate_opcode;\n\n   if( skip & database::skip_assert_evaluation )\n      return void_result();\n\n   for( const auto& p : o.predicates )\n   {\n      FC_ASSERT( p.which() >= 0 );\n      FC_ASSERT( unsigned(p.which()) < max_predicate_opcode );\n      p.visit( predicate_evaluator( _db ) );\n   }\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result assert_evaluator::do_apply( const assert_operation& o )\n{ try {\n   // assert_operation is always a no-op\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/asset_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015-2018 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/asset_evaluator.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <functional>\n\nnamespace graphene { namespace chain {\nnamespace detail {\n\n   // TODO review and remove code below and links to it after hf_1774\n   void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options)\n   {\n      if( block_time < HARDFORK_1774_TIME )\n      {\n         FC_ASSERT( !options.extensions.value.reward_percent.valid() ||\n                    *options.extensions.value.reward_percent < GRAPHENE_100_PERCENT,\n            \"Asset extension reward percent must be less than 100% till HARDFORK_1774_TIME!\");\n      }\n   }\n\n   void check_bitasset_options_hf_bsip74( const fc::time_point_sec& block_time, const bitasset_options& options)\n   {\n      // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:\n      FC_ASSERT( block_time >= HARDFORK_CORE_BSIP74_TIME\n            || !options.extensions.value.margin_call_fee_ratio.valid(),\n            \"A BitAsset's MCFR cannot be set before Hardfork BSIP74\" );\n   }\n\n   // TODO review and remove code below and links to it after HARDFORK_BSIP_81_TIME\n   void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options)\n   {\n      if (block_time < HARDFORK_BSIP_81_TIME) {\n         // Taker fees should not be set until activation of BSIP81\n         FC_ASSERT(!options.extensions.value.taker_fee_percent.valid(),\n                   \"Taker fee percent should not be defined before HARDFORK_BSIP_81_TIME\");\n      }\n   }\n\n   // TODO review and remove code below and links to it after HARDFORK_BSIP_48_75_TIME\n   void check_asset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const asset_options& options)\n   {\n      if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) )\n      {\n         // new issuer permissions should not be set until activation of BSIP_48_75\n         FC_ASSERT( 0 == (options.issuer_permissions & (uint16_t)(~ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK)),\n                    \"New asset issuer permission bits should not be set before HARDFORK_BSIP_48_75_TIME\" );\n         // Note: no check for flags here because we didn't check in the past\n      }\n   }\n\n   // TODO review and remove code below and links to it after HARDFORK_BSIP_48_75_TIME\n   void check_bitasset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const bitasset_options& options)\n   {\n      if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) )\n      {\n         // new params should not be set until activation of BSIP_48_75\n         FC_ASSERT( !options.extensions.value.maintenance_collateral_ratio.valid(),\n                    \"Maintenance collateral ratio should not be defined by asset owner \"\n                    \"before HARDFORK_BSIP_48_75_TIME\" );\n         FC_ASSERT( !options.extensions.value.maximum_short_squeeze_ratio.valid(),\n                    \"Maximum short squeeze ratio should not be defined by asset owner \"\n                    \"before HARDFORK_BSIP_48_75_TIME\" );\n      }\n   }\n\n   // TODO review and remove code below and links to it after HARDFORK_BSIP_48_75_TIME\n   void check_asset_update_extensions_hf_bsip_48_75( const fc::time_point_sec& block_time,\n                                                     const asset_update_operation::ext& extensions )\n   {\n      if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) )\n      {\n         // new extensions should not be set until activation of BSIP_48_75\n         FC_ASSERT( !extensions.new_precision.valid(),\n                    \"new_precision should not be set before HARDFORK_BSIP_48_75_TIME\" );\n         FC_ASSERT( !extensions.skip_core_exchange_rate.valid(),\n                    \"skip_core_exchange_rate should not be set before HARDFORK_BSIP_48_75_TIME\" );\n      }\n   }\n\n   // TODO review and remove code below and links to it after HARDFORK_BSIP_77_TIME\n   void check_asset_publish_feed_extensions_hf_bsip77( const fc::time_point_sec& block_time,\n                                                       const asset_publish_feed_operation::ext& extensions )\n   {\n      if ( !HARDFORK_BSIP_77_PASSED( block_time ) )\n      {\n         // new extensions should not be set until activation of BSIP_77\n         FC_ASSERT( !extensions.initial_collateral_ratio.valid(),\n                   \"Initial collateral ratio should not be defined before HARDFORK_BSIP_77_TIME\" );\n      }\n   }\n\n   // TODO review and remove code below and links to it after HARDFORK_BSIP_77_TIME\n   void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options)\n   {\n      if ( !HARDFORK_BSIP_77_PASSED( block_time ) ) {\n         // ICR should not be set until activation of BSIP77\n         FC_ASSERT(!options.extensions.value.initial_collateral_ratio.valid(),\n                   \"Initial collateral ratio should not be defined before HARDFORK_BSIP_77_TIME\");\n      }\n   }\n\n   void check_bitasset_options_hf_bsip87(const fc::time_point_sec& block_time, const bitasset_options& options)\n   {\n      // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:\n      FC_ASSERT( !options.extensions.value.force_settle_fee_percent.valid()\n                 || block_time >= HARDFORK_CORE_BSIP87_TIME,\n                 \"A BitAsset's FSFP cannot be set before Hardfork BSIP87\" );\n   }\n\n   void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time,\n                                                        const asset_claim_fees_operation& op)\n   {\n      // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:\n      FC_ASSERT( !op.extensions.value.claim_from_asset_id.valid() ||\n                 block_time >= HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME,\n                 \"Collateral-denominated fees are not yet active and therefore cannot be claimed.\" );\n   }\n\n   void check_asset_options_hf_core2281( const fc::time_point_sec& next_maint_time, const asset_options& options)\n   {\n      // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:\n      if ( !HARDFORK_CORE_2281_PASSED(next_maint_time) )\n      {\n         // new issuer permissions should not be set until activation of the hardfork\n         FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_collateral_bidding),\n                    \"New asset issuer permission bit 'disable_collateral_bidding' should not be set \"\n                    \"before Hardfork core-2281\" );\n         // Note: checks about flags are more complicated due to old bugs,\n         //       and likely can not be removed after hardfork, so do not put them here\n      }\n   }\n\n   void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options)\n   {\n      // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:\n      if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) )\n      {\n         // new issuer permissions should not be set until activation of the hardfork\n         FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bsrm_update),\n                    \"New asset issuer permission bit 'disable_bsrm_update' should not be set \"\n                    \"before Hardfork core-2467\" );\n      }\n   }\n\n   void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options)\n   {\n      // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:\n      if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) )\n      {\n         FC_ASSERT( !options.extensions.value.black_swan_response_method.valid(),\n                    \"A BitAsset's black swan response method cannot be set before Hardfork core-2467\" );\n      }\n   }\n\n} // graphene::chain::detail\n\nvoid_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) const\n{ try {\n\n   const database& d = db();\n   const time_point_sec now = d.head_block_time();\n   const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   // Hardfork Checks:\n   detail::check_asset_options_hf_1774(now, op.common_options);\n   detail::check_asset_options_hf_bsip_48_75(now, op.common_options);\n   detail::check_asset_options_hf_bsip81(now, op.common_options);\n   detail::check_asset_options_hf_core2281( next_maint_time, op.common_options ); // HF_REMOVABLE\n   detail::check_asset_options_hf_core2467( next_maint_time, op.common_options ); // HF_REMOVABLE\n   if( op.bitasset_opts ) {\n      detail::check_bitasset_options_hf_bsip_48_75( now, *op.bitasset_opts );\n      detail::check_bitasset_options_hf_bsip74( now, *op.bitasset_opts ); // HF_REMOVABLE\n      detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts ); // HF_REMOVABLE\n      detail::check_bitasset_options_hf_bsip87( now, *op.bitasset_opts ); // HF_REMOVABLE\n      detail::check_bitasset_opts_hf_core2467( next_maint_time, *op.bitasset_opts ); // HF_REMOVABLE\n   }\n\n   // TODO move as many validations as possible to validate() if not triggered before hardfork\n   if( HARDFORK_CORE_2281_PASSED( next_maint_time ) )\n   {\n      op.common_options.validate_flags( op.bitasset_opts.valid() );\n   }\n   else if( HARDFORK_BSIP_48_75_PASSED( now ) )\n   {\n      // do not allow the 'disable_collateral_bidding' bit\n      op.common_options.validate_flags( op.bitasset_opts.valid(), false );\n   }\n\n   const auto& chain_parameters = d.get_global_properties().parameters;\n   FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );\n   FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );\n\n   // Check that all authorities do exist\n   for( auto id : op.common_options.whitelist_authorities )\n      d.get(id);\n   for( auto id : op.common_options.blacklist_authorities )\n      d.get(id);\n\n   auto& asset_indx = d.get_index_type<asset_index>().indices().get<by_symbol>();\n   auto asset_symbol_itr = asset_indx.find( op.symbol );\n   FC_ASSERT( asset_symbol_itr == asset_indx.end() );\n\n   // This must remain due to \"BOND.CNY\" being allowed before this HF\n   if( now > HARDFORK_385_TIME )\n   {\n      auto dotpos = op.symbol.rfind( '.' );\n      if( dotpos != std::string::npos )\n      {\n         auto prefix = op.symbol.substr( 0, dotpos );\n         auto asset_prefix_itr = asset_indx.find( prefix );\n         FC_ASSERT( asset_prefix_itr != asset_indx.end(),\n                    \"Asset ${s} may only be created by issuer of asset ${p}, but asset ${p} has not been created\",\n                    (\"s\",op.symbol)(\"p\",prefix) );\n         FC_ASSERT( asset_prefix_itr->issuer == op.issuer, \"Asset ${s} may only be created by issuer of ${p}, ${i}\",\n                    (\"s\",op.symbol)(\"p\",prefix)(\"i\", op.issuer(d).name) );\n      }\n   }\n\n   if( op.bitasset_opts )\n   {\n      const asset_object& backing = op.bitasset_opts->short_backing_asset(d);\n      if( backing.is_market_issued() )\n      {\n         const asset_bitasset_data_object& backing_bitasset_data = backing.bitasset_data(d);\n         const asset_object& backing_backing = backing_bitasset_data.options.short_backing_asset(d);\n         FC_ASSERT( !backing_backing.is_market_issued(),\n                    \"May not create a bitasset backed by a bitasset backed by a bitasset.\" );\n         FC_ASSERT( op.issuer != GRAPHENE_COMMITTEE_ACCOUNT || backing_backing.get_id() == asset_id_type(),\n                    \"May not create a blockchain-controlled market asset which is not backed by CORE.\");\n      } else\n         FC_ASSERT( op.issuer != GRAPHENE_COMMITTEE_ACCOUNT || backing.get_id() == asset_id_type(),\n                    \"May not create a blockchain-controlled market asset which is not backed by CORE.\");\n      FC_ASSERT( op.bitasset_opts->feed_lifetime_sec > chain_parameters.block_interval &&\n                 op.bitasset_opts->force_settlement_delay_sec > chain_parameters.block_interval );\n   }\n\n   if( op.is_prediction_market )\n   {\n      FC_ASSERT( op.bitasset_opts );\n      FC_ASSERT( op.precision == op.bitasset_opts->short_backing_asset(d).precision );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid asset_create_evaluator::pay_fee()\n{\n   constexpr int64_t two = 2;\n   fee_is_odd = ( ( core_fee_paid.value % two ) != 0 );\n   core_fee_paid -= core_fee_paid.value / two;\n   generic_evaluator::pay_fee();\n}\n\nobject_id_type asset_create_evaluator::do_apply( const asset_create_operation& op ) const\n{ try {\n   database& d = db();\n\n   bool hf_429 = fee_is_odd && d.head_block_time() > HARDFORK_CORE_429_TIME;\n\n   const asset_dynamic_data_object& dyn_asset =\n      d.create<asset_dynamic_data_object>( [hf_429,this]( asset_dynamic_data_object& a ) {\n         a.current_supply = 0;\n         a.fee_pool = core_fee_paid - (hf_429 ? 1 : 0);\n      });\n\n   if( fee_is_odd && !hf_429 )\n   {\n      d.modify( d.get_core_dynamic_data(), []( asset_dynamic_data_object& dd ) {\n         ++dd.current_supply;\n      });\n   }\n\n   asset_bitasset_data_id_type bit_asset_id;\n\n   auto next_asset_id = d.get_index_type<asset_index>().get_next_id();\n\n   if( op.bitasset_opts.valid() )\n      bit_asset_id = d.create<asset_bitasset_data_object>( [&op,next_asset_id]( asset_bitasset_data_object& a ) {\n            a.options = *op.bitasset_opts;\n            a.is_prediction_market = op.is_prediction_market;\n            a.asset_id = next_asset_id;\n         }).id;\n\n   const asset_object& new_asset =\n     d.create<asset_object>( [&op,next_asset_id,&dyn_asset,bit_asset_id,&d]( asset_object& a ) {\n         a.issuer = op.issuer;\n         a.symbol = op.symbol;\n         a.precision = op.precision;\n         a.options = op.common_options;\n         if( 0 == a.options.core_exchange_rate.base.asset_id.instance.value )\n            a.options.core_exchange_rate.quote.asset_id = next_asset_id;\n         else\n            a.options.core_exchange_rate.base.asset_id = next_asset_id;\n         a.dynamic_asset_data_id = dyn_asset.id;\n         if( op.bitasset_opts.valid() )\n            a.bitasset_data_id = bit_asset_id;\n         a.creation_block_num = d._current_block_num;\n         a.creation_time      = d._current_block_time;\n      });\n   FC_ASSERT( new_asset.id == next_asset_id, \"Unexpected object database error, object id mismatch\" );\n\n   return new_asset.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o )\n{ try {\n   const database& d = db();\n\n   const asset_object& a = o.asset_to_issue.asset_id(d);\n   FC_ASSERT( o.issuer == a.issuer );\n   FC_ASSERT( !a.is_market_issued(), \"Cannot manually issue a market-issued asset.\" );\n\n   FC_ASSERT( !a.is_liquidity_pool_share_asset(), \"Cannot manually issue a liquidity pool share asset.\" );\n\n   FC_ASSERT( a.can_create_new_supply(), \"Can not create new supply\" );\n\n   to_account = &o.issue_to_account(d);\n   FC_ASSERT( is_authorized_asset( d, *to_account, a ) );\n\n   asset_dyn_data = &a.dynamic_asset_data_id(d);\n   FC_ASSERT( (asset_dyn_data->current_supply + o.asset_to_issue.amount) <= a.options.max_supply );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_issue_evaluator::do_apply( const asset_issue_operation& o ) const\n{ try {\n   db().adjust_balance( o.issue_to_account, o.asset_to_issue );\n\n   db().modify( *asset_dyn_data, [&o]( asset_dynamic_data_object& data ){\n        data.current_supply += o.asset_to_issue.amount;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation& o )\n{ try {\n   const database& d = db();\n\n   const asset_object& a = o.amount_to_reserve.asset_id(d);\n   GRAPHENE_ASSERT(\n      !a.is_market_issued(),\n      asset_reserve_invalid_on_mia,\n      \"Cannot reserve ${sym} because it is a market-issued asset\",\n      (\"sym\", a.symbol)\n   );\n\n   from_account = fee_paying_account;\n   FC_ASSERT( is_authorized_asset( d, *from_account, a ) );\n\n   asset_dyn_data = &a.dynamic_asset_data_id(d);\n   if( !a.is_liquidity_pool_share_asset() )\n   {\n      FC_ASSERT( asset_dyn_data->current_supply >= o.amount_to_reserve.amount,\n                 \"Can not reserve an amount that is more than the current supply\" );\n   }\n   else\n   {\n      FC_ASSERT( asset_dyn_data->current_supply > o.amount_to_reserve.amount,\n                 \"The asset is a liquidity pool share asset thus can only reserve an amount \"\n                 \"that is less than the current supply\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_reserve_evaluator::do_apply( const asset_reserve_operation& o ) const\n{ try {\n   db().adjust_balance( o.payer, -o.amount_to_reserve );\n\n   db().modify( *asset_dyn_data, [&o]( asset_dynamic_data_object& data ){\n        data.current_supply -= o.amount_to_reserve.amount;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_fund_fee_pool_evaluator::do_evaluate(const asset_fund_fee_pool_operation& o)\n{ try {\n   const database& d = db();\n\n   const asset_object& a = o.asset_id(d);\n\n   asset_dyn_data = &a.dynamic_asset_data_id(d);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_fund_fee_pool_evaluator::do_apply(const asset_fund_fee_pool_operation& o) const\n{ try {\n   db().adjust_balance(o.from_account, -o.amount);\n\n   db().modify( *asset_dyn_data, [&o]( asset_dynamic_data_object& data ) {\n      data.fee_pool += o.amount;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nstatic void validate_new_issuer( const database& d, const asset_object& a, account_id_type new_issuer )\n{ try {\n   FC_ASSERT(d.find(new_issuer), \"New issuer account does not exist\");\n   if( a.is_market_issued() && new_issuer == GRAPHENE_COMMITTEE_ACCOUNT )\n   {\n      const asset_object& backing = a.bitasset_data(d).options.short_backing_asset(d);\n      if( backing.is_market_issued() )\n      {\n         const asset_object& backing_backing = backing.bitasset_data(d).options.short_backing_asset(d);\n         FC_ASSERT( backing_backing.get_id() == asset_id_type(),\n                    \"May not create a blockchain-controlled market asset which is not backed by CORE.\");\n      } else\n         FC_ASSERT( backing.get_id() == asset_id_type(),\n                    \"May not create a blockchain-controlled market asset which is not backed by CORE.\");\n   }\n} FC_CAPTURE_AND_RETHROW( (a)(new_issuer) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_update_evaluator::do_evaluate(const asset_update_operation& o)\n{ try {\n   const database& d = db();\n   const time_point_sec now = d.head_block_time();\n   const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   // Hardfork Checks:\n   detail::check_asset_options_hf_1774(now, o.new_options);\n   detail::check_asset_options_hf_bsip_48_75(now, o.new_options);\n   detail::check_asset_options_hf_bsip81(now, o.new_options);\n   detail::check_asset_options_hf_core2281( next_maint_time, o.new_options ); // HF_REMOVABLE\n   detail::check_asset_options_hf_core2467( next_maint_time, o.new_options ); // HF_REMOVABLE\n   detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value );\n\n   bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) );\n   bool hf_core_2281_passed = ( HARDFORK_CORE_2281_PASSED( next_maint_time ) );\n   bool hf_core_2467_passed = ( HARDFORK_CORE_2467_PASSED( next_maint_time ) );\n\n   const asset_object& a = o.asset_to_update(d);\n   auto a_copy = a;\n   a_copy.options = o.new_options;\n   a_copy.validate();\n\n   if( o.new_issuer )\n   {\n      FC_ASSERT( now < HARDFORK_CORE_199_TIME,\n                 \"Since Hardfork #199, updating issuer requires the use of asset_update_issuer_operation.\");\n      validate_new_issuer( d, a, *o.new_issuer );\n   }\n\n   if( a.is_market_issued() )\n      bitasset_data = &a.bitasset_data(d);\n\n   if( hf_core_2467_passed )\n   {\n      // Unable to set non-UIA issuer permission bits on UIA\n      if( !a.is_market_issued() )\n         FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ),\n                    \"Unable to set non-UIA issuer permission bits on UIA\" );\n      // Unable to set disable_bsrm_update issuer permission bit on PM\n      else if( bitasset_data->is_prediction_market )\n         FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bsrm_update ),\n                    \"Unable to set disable_bsrm_update issuer permission bit on PM\" );\n      // else do nothing\n   }\n\n   uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask();\n   if( hf_bsip_48_75_passed && a.is_market_issued() && bitasset_data->is_prediction_market )\n   {\n      // Note: if the global_settle permission was unset, it should be corrected\n      FC_ASSERT( a_copy.can_global_settle(),\n                 \"The global_settle permission should be enabled for prediction markets\" );\n      enabled_issuer_permissions_mask |= global_settle;\n   }\n\n   const auto& dyn_data = a.dynamic_asset_data_id(d);\n   if( dyn_data.current_supply != 0 )\n   {\n      // new issuer_permissions must be subset of old issuer permissions\n      if( hf_core_2467_passed && !a.is_market_issued() ) // for UIA, ignore non-UIA bits\n         FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask()\n                        & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ),\n                 \"Cannot reinstate previously revoked issuer permissions on a UIA if current supply is non-zero, \"\n                 \"unless to unset non-UIA issuer permission bits.\");\n      else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bsrm_update\n         FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask()\n                        & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bsrm_update) ),\n                 \"Cannot reinstate previously revoked issuer permissions on a PM if current supply is non-zero, \"\n                 \"unless to unset the disable_bsrm_update issuer permission bit.\");\n      else\n         FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask()\n                        & (uint16_t)(~enabled_issuer_permissions_mask) ),\n                 \"Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero.\");\n      // precision can not be changed\n      FC_ASSERT( !o.extensions.value.new_precision.valid(),\n                 \"Cannot update precision if current supply is non-zero\" );\n\n      if( hf_bsip_48_75_passed ) // TODO review after hard fork, probably can assert unconditionally\n      {\n         FC_ASSERT( dyn_data.current_supply <= o.new_options.max_supply,\n                    \"Max supply should not be smaller than current supply\" );\n      }\n   }\n\n   // If an invalid bit was set in flags, it should be unset\n   // TODO move as many validations as possible to validate() if not triggered before hardfork\n   if( hf_core_2281_passed )\n   {\n      o.new_options.validate_flags( a.is_market_issued() );\n   }\n   else if( hf_bsip_48_75_passed )\n   {\n      // do not allow the 'disable_collateral_bidding' bit\n      o.new_options.validate_flags( a.is_market_issued(), false );\n   }\n\n   // changed flags must be subset of old issuer permissions\n   if( hf_bsip_48_75_passed )\n   {\n      // Note: if an invalid bit was set, it can be unset regardless of the permissions\n      uint16_t valid_flags_mask = hf_core_2281_passed ? VALID_FLAGS_MASK\n                                                      : (VALID_FLAGS_MASK & (uint16_t)(~disable_collateral_bidding));\n      uint16_t check_bits = a.is_market_issued() ? valid_flags_mask : UIA_VALID_FLAGS_MASK;\n\n      FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & check_bits\n                        & (uint16_t)(~enabled_issuer_permissions_mask) ),\n                 \"Flag change is forbidden by issuer permissions\" );\n   }\n   else\n   {\n      FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & (uint16_t)(~a.options.issuer_permissions) ),\n                 \"Flag change is forbidden by issuer permissions\" );\n   }\n\n   asset_to_update = &a;\n   FC_ASSERT( o.issuer == a.issuer,\n              \"Incorrect issuer for asset! (${o.issuer} != ${a.issuer})\",\n              (\"o.issuer\", o.issuer)(\"a.issuer\", a.issuer) );\n\n   FC_ASSERT( a.can_update_max_supply() || a.options.max_supply == o.new_options.max_supply,\n              \"Can not update max supply\" );\n\n   if( o.extensions.value.new_precision.valid() )\n   {\n      FC_ASSERT( *o.extensions.value.new_precision != a.precision,\n                 \"Specified a new precision but it does not change\" );\n\n      if( a.is_market_issued() )\n         FC_ASSERT( !bitasset_data->is_prediction_market, \"Can not update precision of a prediction market\" );\n\n      // If any other asset is backed by this asset, this asset's precision can't be updated\n      const auto& idx = d.get_index_type<graphene::chain::asset_bitasset_data_index>()\n                         .indices().get<by_short_backing_asset>();\n      auto itr = idx.lower_bound( o.asset_to_update );\n      bool backing_another_asset = ( itr != idx.end() && itr->options.short_backing_asset == o.asset_to_update );\n      FC_ASSERT( !backing_another_asset,\n                 \"Asset ${a} is backed by this asset, can not update precision\",\n                 (\"a\",itr->asset_id) );\n   }\n\n   const auto& chain_parameters = d.get_global_properties().parameters;\n\n   FC_ASSERT( o.new_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );\n   for( auto id : o.new_options.whitelist_authorities )\n      d.get(id);\n   FC_ASSERT( o.new_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );\n   for( auto id : o.new_options.blacklist_authorities )\n      d.get(id);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE\n\nvoid_result asset_update_evaluator::do_apply(const asset_update_operation& o)\n{ try {\n   database& d = db();\n\n   // If we are now disabling force settlements, cancel all open force settlement orders\n   if( 0 != (o.new_options.flags & disable_force_settle) && asset_to_update->can_force_settle() )\n   {\n      const auto& idx = d.get_index_type<force_settlement_index>().indices().get<by_expiration>();\n      // Funky iteration code because we're removing objects as we go. We have to re-initialize itr every loop instead\n      // of simply incrementing it.\n      for( auto itr = idx.lower_bound(o.asset_to_update);\n           itr != idx.end() && itr->settlement_asset_id() == o.asset_to_update;\n           itr = idx.lower_bound(o.asset_to_update) )\n         d.cancel_settle_order(*itr);\n   }\n\n   // If we are now disabling collateral bidding, cancel all open collateral bids\n   if( 0 != (o.new_options.flags & disable_collateral_bidding) && asset_to_update->can_bid_collateral() )\n   {\n      const auto& bid_idx = d.get_index_type< collateral_bid_index >().indices().get<by_price>();\n      auto itr = bid_idx.lower_bound( o.asset_to_update );\n      const auto end = bid_idx.upper_bound( o.asset_to_update );\n      while( itr != end )\n      {\n         const collateral_bid_object& bid = *itr;\n         ++itr;\n         d.cancel_bid( bid );\n      }\n   }\n\n   // For market-issued assets, if core exchange rate changed, update flag in bitasset data\n   if( !o.extensions.value.skip_core_exchange_rate.valid() && asset_to_update->is_market_issued()\n          && asset_to_update->options.core_exchange_rate != o.new_options.core_exchange_rate )\n   {\n      const auto& bitasset = ( bitasset_data ? *bitasset_data : asset_to_update->bitasset_data(d) );\n      if( !bitasset.asset_cer_updated )\n      {\n         d.modify( bitasset, [](asset_bitasset_data_object& b)\n         {\n            b.asset_cer_updated = true;\n         });\n      }\n   }\n\n   d.modify(*asset_to_update, [&o](asset_object& a) {\n      if( o.new_issuer )\n         a.issuer = *o.new_issuer;\n      if( o.extensions.value.new_precision.valid() )\n         a.precision = *o.extensions.value.new_precision;\n      if( o.extensions.value.skip_core_exchange_rate.valid() )\n      {\n         const auto old_cer = a.options.core_exchange_rate;\n         a.options = o.new_options;\n         a.options.core_exchange_rate = old_cer;\n      }\n      else\n         a.options = o.new_options;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_update_issuer_evaluator::do_evaluate(const asset_update_issuer_operation& o)\n{ try {\n   database& d = db();\n\n   const asset_object& a = o.asset_to_update(d);\n\n   validate_new_issuer( d, a, o.new_issuer );\n\n   asset_to_update = &a;\n   FC_ASSERT( o.issuer == a.issuer,\n              \"Incorrect issuer for asset! (${o.issuer} != ${a.issuer})\",\n              (\"o.issuer\", o.issuer)(\"a.issuer\", a.issuer) );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE\n\nvoid_result asset_update_issuer_evaluator::do_apply(const asset_update_issuer_operation& o)\n{ try {\n   database& d = db();\n   d.modify(*asset_to_update, [&](asset_object& a) {\n      a.issuer = o.new_issuer;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n/****************\n * Loop through assets, looking for ones that are backed by the asset being changed. When found,\n * perform checks to verify validity\n *\n * @param d the database\n * @param op the bitasset update operation being performed\n * @param new_backing_asset\n * @param true if after hf 922/931 (if nothing triggers, this and the logic that depends on it\n *    should be removed).\n */\nvoid check_children_of_bitasset(const database& d, const asset_update_bitasset_operation& op,\n      const asset_object& new_backing_asset)\n{\n   // no need to do these checks if the new backing asset is CORE\n   if ( new_backing_asset.get_id() == asset_id_type() )\n      return;\n\n   // loop through all assets that have this asset as a backing asset\n   const auto& idx = d.get_index_type<graphene::chain::asset_bitasset_data_index>()\n         .indices()\n         .get<by_short_backing_asset>();\n   auto backed_range = idx.equal_range(op.asset_to_update);\n   std::for_each( backed_range.first, backed_range.second,\n         [&new_backing_asset, &d, &op](const asset_bitasset_data_object& bitasset_data)\n         {\n            const auto& child = bitasset_data.asset_id(d);\n            FC_ASSERT( child.get_id() != op.new_options.short_backing_asset,\n                  \"A BitAsset would be invalidated by changing this backing asset \"\n                  \"('A' backed by 'B' backed by 'A').\" );\n\n            FC_ASSERT( child.issuer != GRAPHENE_COMMITTEE_ACCOUNT,\n                  \"A blockchain-controlled market asset would be invalidated by changing this backing asset.\" );\n\n            FC_ASSERT( !new_backing_asset.is_market_issued(),\n                  \"A non-blockchain controlled BitAsset would be invalidated by changing this backing asset.\");\n         } ); // end of lambda and std::for_each()\n} // check_children_of_bitasset\n\nvoid_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bitasset_operation& op)\n{ try {\n   const database& d = db();\n   const time_point_sec now = d.head_block_time();\n   const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   // Hardfork Checks:\n   detail::check_bitasset_options_hf_bsip_48_75( now, op.new_options );\n   detail::check_bitasset_options_hf_bsip74( now, op.new_options ); // HF_REMOVABLE\n   detail::check_bitasset_options_hf_bsip77( now, op.new_options ); // HF_REMOVABLE\n   detail::check_bitasset_options_hf_bsip87( now, op.new_options ); // HF_REMOVABLE\n   detail::check_bitasset_opts_hf_core2467( next_maint_time, op.new_options ); // HF_REMOVABLE\n\n   const asset_object& asset_obj = op.asset_to_update(d);\n\n   FC_ASSERT( asset_obj.is_market_issued(), \"Cannot update BitAsset-specific settings on a non-BitAsset.\" );\n\n   FC_ASSERT( op.issuer == asset_obj.issuer, \"Only asset issuer can update bitasset_data of the asset.\" );\n\n   const asset_bitasset_data_object& current_bitasset_data = asset_obj.bitasset_data(d);\n\n   if( !HARDFORK_CORE_2282_PASSED( next_maint_time ) )\n      FC_ASSERT( !current_bitasset_data.is_globally_settled(),\n                 \"Cannot update a bitasset after a global settlement has executed\" );\n\n   if( current_bitasset_data.is_prediction_market )\n      FC_ASSERT( !op.new_options.extensions.value.black_swan_response_method.valid(),\n                 \"Can not set black_swan_response_method for Prediction Markets\" );\n\n   // TODO simplify code below when made sure operator==(optional,optional) works\n   if( !asset_obj.can_owner_update_mcr() )\n   {\n      // check if MCR will change\n      const auto& old_mcr = current_bitasset_data.options.extensions.value.maintenance_collateral_ratio;\n      const auto& new_mcr = op.new_options.extensions.value.maintenance_collateral_ratio;\n      bool mcr_changed = ( ( old_mcr.valid() != new_mcr.valid() )\n                           || ( old_mcr.valid() && *old_mcr != *new_mcr ) );\n      FC_ASSERT( !mcr_changed, \"No permission to update MCR\" );\n   }\n   if( !asset_obj.can_owner_update_icr() )\n   {\n      // check if ICR will change\n      const auto& old_icr = current_bitasset_data.options.extensions.value.initial_collateral_ratio;\n      const auto& new_icr = op.new_options.extensions.value.initial_collateral_ratio;\n      bool icr_changed = ( ( old_icr.valid() != new_icr.valid() )\n                           || ( old_icr.valid() && *old_icr != *new_icr ) );\n      FC_ASSERT( !icr_changed, \"No permission to update ICR\" );\n   }\n   if( !asset_obj.can_owner_update_mssr() )\n   {\n      // check if MSSR will change\n      const auto& old_mssr = current_bitasset_data.options.extensions.value.maximum_short_squeeze_ratio;\n      const auto& new_mssr = op.new_options.extensions.value.maximum_short_squeeze_ratio;\n      bool mssr_changed = ( ( old_mssr.valid() != new_mssr.valid() )\n                           || ( old_mssr.valid() && *old_mssr != *new_mssr ) );\n      FC_ASSERT( !mssr_changed, \"No permission to update MSSR\" );\n   }\n   // check if BSRM will change\n   const auto old_bsrm = current_bitasset_data.get_black_swan_response_method();\n   const auto new_bsrm = op.new_options.get_black_swan_response_method();\n   if( old_bsrm != new_bsrm )\n   {\n      FC_ASSERT( asset_obj.can_owner_update_bsrm(), \"No permission to update BSRM\" );\n      FC_ASSERT( !current_bitasset_data.is_globally_settled(),\n                 \"Unable to update BSRM when the asset has been globally settled\" );\n\n      // Note: it is probably OK to allow BSRM update, be conservative here so far\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      if( bsrm_type::individual_settlement_to_fund == old_bsrm )\n         FC_ASSERT( !current_bitasset_data.is_individually_settled_to_fund(),\n                 \"Unable to update BSRM when the individual settlement pool (for force-settlements) is not empty\" );\n      else if( bsrm_type::individual_settlement_to_order == old_bsrm )\n         FC_ASSERT( !d.find_settled_debt_order( op.asset_to_update ),\n                 \"Unable to update BSRM when there exists an individual settlement order\" );\n\n      // Since we do not allow updating in some cases (above), only check no_settlement here\n      if( bsrm_type::no_settlement == old_bsrm || bsrm_type::no_settlement == new_bsrm )\n         update_feeds_due_to_bsrm_change = true;\n   }\n\n\n   // hf 922_931 is a consensus/logic change. This hf cannot be removed.\n   bool after_hf_core_922_931 = ( next_maint_time > HARDFORK_CORE_922_931_TIME );\n\n   // Are we changing the backing asset?\n   if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset )\n   {\n      FC_ASSERT( !current_bitasset_data.is_globally_settled(),\n                 \"Cannot change backing asset after a global settlement has executed\" );\n\n      const asset_dynamic_data_object& dyn = asset_obj.dynamic_asset_data_id(d);\n      FC_ASSERT( dyn.current_supply == 0,\n                 \"Cannot change backing asset if there is already a current supply.\" );\n\n      FC_ASSERT( dyn.accumulated_collateral_fees == 0,\n                 \"Must claim collateral-denominated fees before changing backing asset.\" );\n\n      const asset_object& new_backing_asset = op.new_options.short_backing_asset(d); // check if the asset exists\n\n      if( after_hf_core_922_931 )\n      {\n         FC_ASSERT( op.new_options.short_backing_asset != asset_obj.get_id(),\n                    \"Cannot update an asset to be backed by itself.\" );\n\n         if( current_bitasset_data.is_prediction_market )\n         {\n            FC_ASSERT( asset_obj.precision == new_backing_asset.precision,\n                       \"The precision of the asset and backing asset must be equal.\" );\n         }\n\n         if( asset_obj.issuer == GRAPHENE_COMMITTEE_ACCOUNT )\n         {\n            if( new_backing_asset.is_market_issued() )\n            {\n               FC_ASSERT( new_backing_asset.bitasset_data(d).options.short_backing_asset == asset_id_type(),\n                          \"May not modify a blockchain-controlled market asset to be backed by an asset which is not \"\n                          \"backed by CORE.\" );\n\n               check_children_of_bitasset( d, op, new_backing_asset );\n            }\n            else\n            {\n               FC_ASSERT( new_backing_asset.get_id() == asset_id_type(),\n                          \"May not modify a blockchain-controlled market asset to be backed by an asset which is not \"\n                          \"market issued asset nor CORE.\" );\n            }\n         }\n         else\n         {\n            // not a committee issued asset\n\n            // If we're changing to a backing_asset that is not CORE, we need to look at any\n            // asset ( \"CHILD\" ) that has this one as a backing asset. If CHILD is committee-owned,\n            // the change is not allowed. If CHILD is user-owned, then this asset's backing\n            // asset must be either CORE or a UIA.\n            if ( new_backing_asset.get_id() != asset_id_type() ) // not backed by CORE\n            {\n               check_children_of_bitasset( d, op, new_backing_asset );\n            }\n\n         }\n\n         // Check if the new backing asset is itself backed by something. It must be CORE or a UIA\n         if ( new_backing_asset.is_market_issued() )\n         {\n            asset_id_type backing_backing_asset_id = new_backing_asset.bitasset_data(d).options.short_backing_asset;\n            FC_ASSERT( (backing_backing_asset_id == asset_id_type()\n                        || !backing_backing_asset_id(d).is_market_issued()),\n                  \"A BitAsset cannot be backed by a BitAsset that itself is backed by a BitAsset.\");\n         }\n      }\n   }\n\n   const auto& chain_parameters = d.get_global_properties().parameters;\n   if( after_hf_core_922_931 )\n   {\n      FC_ASSERT( op.new_options.feed_lifetime_sec > chain_parameters.block_interval,\n            \"Feed lifetime must exceed block interval.\" );\n      FC_ASSERT( op.new_options.force_settlement_delay_sec > chain_parameters.block_interval,\n            \"Force settlement delay must exceed block interval.\" );\n   }\n\n   bitasset_to_update = &current_bitasset_data;\n   asset_to_update = &asset_obj;\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\n/*******\n * @brief Apply requested changes to bitasset options\n *\n * This applies the requested changes to the bitasset object. It also cleans up the\n * releated feeds, and checks conditions that might necessitate a call to check_call_orders.\n * Called from asset_update_bitasset_evaluator::do_apply().\n *\n * @param op the requested operation\n * @param db the database\n * @param bdo the actual database object\n * @param asset_to_update the asset_object related to this bitasset_data_object\n *\n * @returns true if we should check call orders, such as if if the feed price is changed, or some\n *    cases after hf core-868-890, or if the margin_call_fee_ratio has changed, which affects the\n *    matching price of margin call orders.\n */\nstatic bool update_bitasset_object_options(\n      const asset_update_bitasset_operation& op, database& db,\n      asset_bitasset_data_object& bdo, const asset_object& asset_to_update,\n      bool update_feeds_due_to_bsrm_change )\n{\n   const fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;\n   bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME );\n\n   const auto& head_time = db.head_block_time();\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n   // If the minimum number of feeds to calculate a median has changed, we need to recalculate the median\n   bool should_update_feeds = false;\n   if( op.new_options.minimum_feeds != bdo.options.minimum_feeds )\n      should_update_feeds = true;\n\n   // after hardfork core-868-890, we also should call update_bitasset_current_feed if the feed_lifetime_sec changed\n   if( after_hf_core_868_890\n         && op.new_options.feed_lifetime_sec != bdo.options.feed_lifetime_sec )\n   {\n      should_update_feeds = true;\n   }\n\n   // feeds must be reset if the backing asset is changed after hardfork core-868-890\n   bool backing_asset_changed = false;\n   bool is_witness_or_committee_fed = false;\n   if( after_hf_core_868_890\n         && op.new_options.short_backing_asset != bdo.options.short_backing_asset )\n   {\n      backing_asset_changed = true;\n      should_update_feeds = true;\n      if( 0 != ( asset_to_update.options.flags & ( witness_fed_asset | committee_fed_asset ) ) )\n         is_witness_or_committee_fed = true;\n   }\n\n   // TODO simplify code below when made sure operator==(optional,optional) works\n   // check if ICR will change\n   if( !should_update_feeds )\n   {\n      const auto& old_icr = bdo.options.extensions.value.initial_collateral_ratio;\n      const auto& new_icr = op.new_options.extensions.value.initial_collateral_ratio;\n      bool icr_changed = ( ( old_icr.valid() != new_icr.valid() )\n                           || ( old_icr.valid() && *old_icr != *new_icr ) );\n      should_update_feeds = icr_changed;\n   }\n   // check if MCR will change\n   if( !should_update_feeds )\n   {\n      const auto& old_mcr = bdo.options.extensions.value.maintenance_collateral_ratio;\n      const auto& new_mcr = op.new_options.extensions.value.maintenance_collateral_ratio;\n      bool mcr_changed = ( ( old_mcr.valid() != new_mcr.valid() )\n                           || ( old_mcr.valid() && *old_mcr != *new_mcr ) );\n      should_update_feeds = mcr_changed;\n   }\n   // check if MSSR will change\n   if( !should_update_feeds )\n   {\n      const auto& old_mssr = bdo.options.extensions.value.maximum_short_squeeze_ratio;\n      const auto& new_mssr = op.new_options.extensions.value.maximum_short_squeeze_ratio;\n      bool mssr_changed = ( ( old_mssr.valid() != new_mssr.valid() )\n                           || ( old_mssr.valid() && *old_mssr != *new_mssr ) );\n      should_update_feeds = mssr_changed;\n   }\n\n   // check if MCFR will change\n   const auto& old_mcfr = bdo.options.extensions.value.margin_call_fee_ratio;\n   const auto& new_mcfr = op.new_options.extensions.value.margin_call_fee_ratio;\n   const bool mcfr_changed = ( ( old_mcfr.valid() != new_mcfr.valid() )\n                               || ( old_mcfr.valid() && *old_mcfr != *new_mcfr ) );\n\n   // Apply changes to bitasset options\n   bdo.options = op.new_options;\n\n   // are we modifying the underlying? If so, reset the feeds\n   if( backing_asset_changed )\n   {\n      if( is_witness_or_committee_fed )\n      {\n         bdo.feeds.clear();\n      }\n      else\n      {\n         // for non-witness-feeding and non-committee-feeding assets, modify all feeds\n         // published by producers to nothing, since we can't simply remove them. For more information:\n         // https://github.com/bitshares/bitshares-core/pull/832#issuecomment-384112633\n         for( auto& current_feed : bdo.feeds )\n         {\n            current_feed.second.second.settlement_price = price();\n         }\n      }\n   }\n\n   bool feed_actually_changed = false;\n   if( should_update_feeds || update_feeds_due_to_bsrm_change )\n   {\n      const auto old_feed = bdo.current_feed;\n      const auto old_median_feed = bdo.median_feed;\n      // skip recalculating median feed if it is not needed\n      db.update_bitasset_current_feed( bdo, !should_update_feeds );\n      // Note: we don't try to revive the bitasset here if it was GSed // TODO probably we should do it\n\n      // TODO potential optimization: check only when should_update_feeds == true\n\n      // We need to call check_call_orders if the settlement price changes after hardfork core-868-890\n      feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) );\n\n      if( !feed_actually_changed && after_core_hardfork_2582\n            && !old_median_feed.margin_call_params_equal( bdo.median_feed ) )\n         feed_actually_changed = true;\n   }\n\n   // Conditions under which a call to check_call_orders is needed in response to the updates applied here:\n   const bool retval = feed_actually_changed || mcfr_changed;\n\n   return retval;\n}\n\nvoid_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasset_operation& op)\n{\n   try\n   {\n      auto& db_conn = db();\n      bool to_check_call_orders = false;\n\n      db_conn.modify( *bitasset_to_update,\n                      [&op, &to_check_call_orders, &db_conn, this]( asset_bitasset_data_object& bdo )\n      {\n         to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, *asset_to_update,\n                                                                update_feeds_due_to_bsrm_change );\n      });\n\n      if( to_check_call_orders )\n         // Process margin calls, allow black swan, not for a new limit order\n         db_conn.check_call_orders( *asset_to_update, true, false, bitasset_to_update );\n\n      return void_result();\n\n   } FC_CAPTURE_AND_RETHROW( (op) ) // GCOVR_EXCL_LINE\n}\n\nvoid_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_operation& o)\n{ try {\n   database& d = db();\n\n   FC_ASSERT( o.new_feed_producers.size() <= d.get_global_properties().parameters.maximum_asset_feed_publishers,\n              \"Cannot specify more feed producers than maximum allowed\" );\n\n   const asset_object& a = o.asset_to_update(d);\n\n   FC_ASSERT(a.is_market_issued(), \"Cannot update feed producers on a non-BitAsset.\");\n   FC_ASSERT(0 == (a.options.flags & committee_fed_asset), \"Cannot set feed producers on a committee-fed asset.\");\n   FC_ASSERT(0 == (a.options.flags & witness_fed_asset), \"Cannot set feed producers on a witness-fed asset.\");\n\n   FC_ASSERT( a.issuer == o.issuer, \"Only asset issuer can update feed producers of an asset\" );\n\n   asset_to_update = &a;\n\n   // Make sure all producers exist. Check these after asset because account lookup is more expensive\n   for( auto id : o.new_feed_producers )\n      d.get(id);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) const\n{ try {\n   database& d = db();\n   const asset_bitasset_data_object& bitasset_to_update = asset_to_update->bitasset_data(d);\n   d.modify( bitasset_to_update, [&o](asset_bitasset_data_object& a) {\n      //This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored.\n      //I need to update the map such that the keys match the new publishers, but not munge the old price feeds from\n      //publishers who are being kept.\n\n      // TODO possible performance optimization:\n      //      Since both the map and the set are ordered by account already, we can iterate through them only once\n      //      and avoid lookups while iterating by maintaining two iterators at same time.\n      //      However, this operation is not used much, and both the set and the map are small,\n      //      so likely we won't gain much with the optimization.\n\n      //First, remove any old publishers who are no longer publishers\n      for( auto itr = a.feeds.begin(); itr != a.feeds.end(); )\n      {\n         if( o.new_feed_producers.count(itr->first) == 0 )\n            itr = a.feeds.erase(itr);\n         else\n            ++itr;\n      }\n      //Now, add any new publishers\n      for( const account_id_type& acc : o.new_feed_producers )\n      {\n         a.feeds[acc];\n      }\n   });\n   d.update_bitasset_current_feed( bitasset_to_update );\n   // Note: we don't try to revive the bitasset here if it was GSed // TODO probably we should do it\n\n   // Process margin calls, allow black swan, not for a new limit order\n   d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle_evaluator::operation_type& op)\n{ try {\n   const database& d = db();\n   asset_to_settle = &op.asset_to_settle(d);\n   FC_ASSERT( asset_to_settle->is_market_issued(), \"Can only globally settle market-issued assets\" );\n   FC_ASSERT( asset_to_settle->can_global_settle(), \"The global_settle permission of this asset is disabled\" );\n   FC_ASSERT( asset_to_settle->issuer == op.issuer, \"Only asset issuer can globally settle an asset\" );\n   FC_ASSERT( asset_to_settle->dynamic_data(d).current_supply > 0,\n              \"Can not globally settle an asset with zero supply\" );\n\n   const asset_bitasset_data_object& _bitasset_data  = asset_to_settle->bitasset_data(d);\n   // if there is a settlement for this asset, then no further global settle may be taken\n   FC_ASSERT( !_bitasset_data.is_globally_settled(),\n              \"This asset has been globally settled, cannot globally settle again\" );\n\n   // Note: after core-2467 hard fork, there can be no debt position due to individual settlements, so we check here\n   const call_order_object* least_collateralized_short = d.find_least_collateralized_short( _bitasset_data, true );\n   if( least_collateralized_short )\n   {\n      FC_ASSERT( ( least_collateralized_short->get_debt() * op.settle_price )\n                   <= least_collateralized_short->get_collateral(),\n                 \"Cannot globally settle at supplied price: least collateralized short lacks \"\n                 \"sufficient collateral to settle.\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_global_settle_evaluator::do_apply(const asset_global_settle_evaluator::operation_type& op)\n{ try {\n   database& d = db();\n   d.globally_settle_asset( *asset_to_settle, op.settle_price );\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::operation_type& op)\n{ try {\n   const database& d = db();\n   asset_to_settle = &op.amount.asset_id(d);\n   FC_ASSERT( asset_to_settle->is_market_issued(),\n              \"Can only force settle a predition market or a market issued asset\" );\n\n   const auto& bitasset = asset_to_settle->bitasset_data(d);\n   FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.is_globally_settled()\n                 || bitasset.is_individually_settled_to_fund(),\n              \"Either the asset need to have the force_settle flag enabled, or it need to be globally settled, \"\n              \"or the individual settlement pool (for force-settlements) is not empty\" );\n\n   if( bitasset.is_prediction_market )\n   {\n      FC_ASSERT( bitasset.is_globally_settled(),\n                 \"Global settlement must occur before force settling a prediction market\" );\n   }\n   else if( bitasset.current_feed.settlement_price.is_null() )\n   {\n      // TODO check whether the HF check can be removed\n      if( d.head_block_time() <= HARDFORK_CORE_216_TIME )\n      {\n         FC_THROW_EXCEPTION( insufficient_feeds,\n                             \"Before the core-216 hard fork, unable to force settle when there is no sufficient \"\n                             \" price feeds, no matter if the asset has been globally settled\" );\n      }\n      if( !bitasset.is_globally_settled() && !bitasset.is_individually_settled_to_fund() )\n      {\n         FC_THROW_EXCEPTION( insufficient_feeds,\n                             \"Cannot force settle with no price feed if the asset is not globally settled and the \"\n                             \"individual settlement pool (for force-settlements) is not empty\" );\n      }\n   }\n\n   FC_ASSERT( d.get_balance( op.account, op.amount.asset_id ) >= op.amount, \"Insufficient balance\" );\n\n   // Since hard fork core-973, check asset authorization limitations\n   if( HARDFORK_CORE_973_PASSED(d.head_block_time()) )\n   {\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *asset_to_settle ),\n                 \"The account is not allowed to settle the asset\" );\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, bitasset.options.short_backing_asset(d) ),\n                 \"The account is not allowed to receive the backing asset\" );\n   }\n\n   bitasset_ptr = &bitasset;\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nstatic optional<asset> pay_collateral_fees( database& d,\n                                            const asset& pays,\n                                            const asset& settled_amount,\n                                            const asset_object& asset_to_settle,\n                                            const asset_bitasset_data_object& bitasset )\n{\n   const auto& head_time = d.head_block_time();\n   bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill settlement at MCOP)\n   if( after_core_hardfork_2591 && !bitasset.is_prediction_market\n         && !bitasset.current_feed.settlement_price.is_null() )\n   {\n      price fill_price = bitasset.get_margin_call_order_price();\n      try\n      {\n         asset settled_amount_by_mcop = pays.multiply_and_round_up( fill_price ); // Throws fc::exception if overflow\n         if( settled_amount_by_mcop < settled_amount )\n         {\n            asset collateral_fees = settled_amount - settled_amount_by_mcop;\n            asset_to_settle.accumulate_fee( d, collateral_fees );\n            return collateral_fees;\n         }\n      } FC_CAPTURE_AND_LOG( (pays)(settled_amount)(fill_price) ) // Catch and log the exception // GCOVR_EXCL_LINE\n   }\n   return optional<asset>();\n}\n\nstatic extendable_operation_result pay_settle_from_gs_fund( database& d,\n                                                 const asset_settle_evaluator::operation_type& op,\n                                                 const account_object* fee_paying_account,\n                                                 const asset_object& asset_to_settle,\n                                                 const asset_bitasset_data_object& bitasset )\n{\n   const auto& head_time = d.head_block_time();\n   const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   const auto& mia_dyn = asset_to_settle.dynamic_asset_data_id(d);\n\n   asset settled_amount = ( op.amount.amount == mia_dyn.current_supply )\n                          ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset )\n                          : ( op.amount * bitasset.settlement_price ); // round down, favors global settlement fund\n   if( op.amount.amount != mia_dyn.current_supply )\n   {\n      // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero\n      FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund,\n                 \"Internal error: amount in the global settlement fund is not sufficient to pay the settlement\" );\n   }\n\n   if( 0 == settled_amount.amount && !bitasset.is_prediction_market && maint_time > HARDFORK_CORE_184_TIME )\n      FC_THROW( \"Settle amount is too small to receive anything due to rounding\" );\n      // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur\n\n   asset pays = op.amount;\n   if( op.amount.amount != mia_dyn.current_supply\n         && settled_amount.amount != 0\n         && maint_time > HARDFORK_CORE_342_TIME )\n   {\n      pays = settled_amount.multiply_and_round_up( bitasset.settlement_price );\n   }\n\n   d.adjust_balance( op.account, -pays );\n\n   asset issuer_fees( 0, bitasset.options.short_backing_asset );\n   optional<asset> collateral_fees;\n\n   if( settled_amount.amount > 0 )\n   {\n      d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){\n         obj.settlement_fund -= settled_amount.amount;\n      });\n\n      // Calculate and pay collateral fees after HF core-2591\n      collateral_fees = pay_collateral_fees( d, pays, settled_amount, asset_to_settle, bitasset );\n      if( collateral_fees.valid() )\n         settled_amount -= *collateral_fees;\n\n      // The account who settles pays market fees to the issuer of the collateral asset after HF core-1780\n      //\n      // TODO Check whether the HF check can be removed after the HF.\n      //      Note: even if logically it can be removed, perhaps the removal will lead to a small\n      //            performance loss. Needs testing.\n      if( head_time >= HARDFORK_CORE_1780_TIME )\n      {\n         issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false );\n         settled_amount -= issuer_fees;\n      }\n\n      if( settled_amount.amount > 0 )\n         d.adjust_balance( op.account, settled_amount );\n   }\n\n   d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){\n      obj.current_supply -= pays.amount;\n   });\n   // Note: we don't revive the asset here if current_supply become zero, but only do it on a new feed\n\n   extendable_operation_result result;\n\n   result.value.paid = vector<asset>({ pays });\n   result.value.received = vector<asset>({ settled_amount });\n   result.value.fees = collateral_fees.valid() ? vector<asset>({ *collateral_fees, issuer_fees })\n                                               : vector<asset>({ issuer_fees });\n\n   return result;\n}\n\nstatic extendable_operation_result pay_settle_from_individual_pool( database& d,\n                                                 const asset_settle_evaluator::operation_type& op,\n                                                 const account_object* fee_paying_account,\n                                                 const asset_object& asset_to_settle,\n                                                 const asset_bitasset_data_object& bitasset )\n{\n   asset pays( bitasset.individual_settlement_debt, bitasset.asset_id );\n   asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset );\n   if( op.amount.amount < bitasset.individual_settlement_debt )\n   {\n      auto settlement_price = bitasset.get_individual_settlement_price();\n      settled_amount = op.amount * settlement_price; // round down, in favor of settlement fund\n      FC_ASSERT( settled_amount.amount > 0, \"Settle amount is too small to receive anything due to rounding\" );\n      pays = settled_amount.multiply_and_round_up( settlement_price );\n   }\n\n   d.adjust_balance( op.account, -pays );\n   d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){\n      obj.individual_settlement_debt -= pays.amount;\n      obj.individual_settlement_fund -= settled_amount.amount;\n   });\n   d.modify( asset_to_settle.dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){\n      obj.current_supply -= pays.amount;\n   });\n\n   // Calculate and pay collateral fees after HF core-2591\n   optional<asset> collateral_fees = pay_collateral_fees( d, pays, settled_amount, asset_to_settle, bitasset );\n   if( collateral_fees.valid() )\n      settled_amount -= *collateral_fees;\n\n   auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false );\n   settled_amount -= issuer_fees;\n\n   if( settled_amount.amount > 0 )\n      d.adjust_balance( op.account, settled_amount );\n\n   // Update current_feed since fund price changed\n   auto old_feed_price = bitasset.current_feed.settlement_price;\n   d.update_bitasset_current_feed( bitasset, true );\n\n   // When current_feed is updated, it is possible that there are limit orders able to get filled,\n   // so we need to call check_call_orders()\n   // Note: theoretically, if the fund is still not empty, its new CR should be >= old CR,\n   //       in this case, calling check_call_orders() should not change anything.\n   // Note: there should be no existing force settlements\n   if( 0 == bitasset.individual_settlement_debt && old_feed_price != bitasset.current_feed.settlement_price )\n      d.check_call_orders( asset_to_settle, true, false, &bitasset );\n\n   extendable_operation_result result;\n\n   result.value.paid = vector<asset>({ pays });\n   result.value.received = vector<asset>({ settled_amount });\n   result.value.fees = collateral_fees.valid() ? vector<asset>({ *collateral_fees, issuer_fees })\n                                               : vector<asset>({ issuer_fees });\n\n   return result;\n}\n\noperation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op)\n{ try {\n   database& d = db();\n\n   const auto& bitasset = *bitasset_ptr;\n\n   // Process global settlement fund\n   if( bitasset.is_globally_settled() )\n      return pay_settle_from_gs_fund( d, op, fee_paying_account, *asset_to_settle, bitasset );\n\n   // Process individual settlement pool\n   extendable_operation_result result;\n   asset to_settle = op.amount;\n   if( bitasset.is_individually_settled_to_fund() )\n   {\n      result = pay_settle_from_individual_pool( d, op, fee_paying_account, *asset_to_settle, bitasset );\n\n      // If the amount to settle is too small, or force settlement is disabled, we return\n      if( bitasset.is_individually_settled_to_fund() || !asset_to_settle->can_force_settle() )\n         return result;\n\n      to_settle -= result.value.paid->front();\n   }\n\n   // Process the rest\n   const auto& head_time = d.head_block_time();\n\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n   if( after_core_hardfork_2582 && 0 == to_settle.amount )\n      return result;\n\n   bool after_core_hardfork_2587 = HARDFORK_CORE_2587_PASSED( head_time );\n   if( after_core_hardfork_2587 && bitasset.current_feed.settlement_price.is_null() )\n      return result;\n\n   d.adjust_balance( op.account, -to_settle );\n   const auto& settle = d.create<force_settlement_object>(\n         [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) {\n      s.owner = op.account;\n      s.balance = to_settle;\n      s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec;\n   });\n\n   result.value.new_objects = flat_set<object_id_type>({ settle.id });\n\n   const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n   if( HARDFORK_CORE_2481_PASSED( maint_time ) )\n   {\n      d.apply_force_settlement( settle, bitasset, *asset_to_settle );\n   }\n\n   return result;\n\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o)\n{ try {\n   const database& d = db();\n   const time_point_sec now = d.head_block_time();\n\n   // TODO remove check after hard fork\n   detail::check_asset_publish_feed_extensions_hf_bsip77( now, o.extensions.value );\n\n   const asset_object& base = o.asset_id(d);\n   //Verify that this feed is for a market-issued asset and that asset is backed by the base\n   FC_ASSERT( base.is_market_issued(), \"Can only publish price feeds for market-issued assets\" );\n\n   const asset_bitasset_data_object& bitasset = base.bitasset_data(d);\n   if( bitasset.is_prediction_market || now <= HARDFORK_CORE_216_TIME )\n   {\n      FC_ASSERT( !bitasset.is_globally_settled(), \"No further feeds may be published after a settlement event\" );\n   }\n\n   // the settlement price must be quoted in terms of the backing asset\n   FC_ASSERT( o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset,\n              \"Quote asset type in settlement price should be same as backing asset of this asset\" );\n\n   if( now > HARDFORK_480_TIME )\n   {\n      if( !o.feed.core_exchange_rate.is_null() )\n      {\n         FC_ASSERT( o.feed.core_exchange_rate.quote.asset_id == asset_id_type(),\n                    \"Quote asset in core exchange rate should be CORE asset\" );\n      }\n   }\n   else\n   {\n      if( (!o.feed.settlement_price.is_null()) && (!o.feed.core_exchange_rate.is_null()) )\n      {\n         // Old buggy code, but we have to live with it\n         FC_ASSERT( o.feed.settlement_price.quote.asset_id == o.feed.core_exchange_rate.quote.asset_id, \"Bad feed\" );\n      }\n   }\n\n   //Verify that the publisher is authoritative to publish a feed\n   if( 0 != ( base.options.flags & witness_fed_asset ) )\n   {\n      FC_ASSERT( d.get(GRAPHENE_WITNESS_ACCOUNT).active.account_auths.count(o.publisher) > 0,\n                 \"Only active witnesses are allowed to publish price feeds for this asset\" );\n   }\n   else if( 0 != ( base.options.flags & committee_fed_asset ) )\n   {\n      FC_ASSERT( d.get(GRAPHENE_COMMITTEE_ACCOUNT).active.account_auths.count(o.publisher) > 0,\n                 \"Only active committee members are allowed to publish price feeds for this asset\" );\n   }\n   else\n   {\n      FC_ASSERT( bitasset.feeds.count(o.publisher) > 0,\n                 \"The account is not in the set of allowed price feed producers of this asset\" );\n   }\n\n   asset_ptr = &base;\n   bitasset_ptr = &bitasset;\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE\n\nvoid_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_operation& o)\n{ try {\n\n   database& d = db();\n   const auto head_time = d.head_block_time();\n   const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   const asset_object& base = *asset_ptr;\n   const asset_bitasset_data_object& bad = *bitasset_ptr;\n\n   auto old_feed = bad.current_feed;\n   auto old_median_feed = bad.median_feed;\n   // Store medians for this asset\n   d.modify( bad , [&o,&head_time](asset_bitasset_data_object& a) {\n      a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed,\n                                                      o.extensions.value.initial_collateral_ratio ) );\n   });\n   d.update_bitasset_current_feed( bad );\n\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n   if( !after_core_hardfork_2582 && old_feed.margin_call_params_equal(bad.current_feed) )\n      return void_result();\n   if( after_core_hardfork_2582 && old_median_feed.margin_call_params_equal(bad.median_feed) )\n      return void_result();\n\n   // Feed changed, check whether need to revive the asset and proceed if need\n   if( bad.is_globally_settled() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME\n       && !bad.current_feed.settlement_price.is_null() ) // has a valid feed\n   {\n      bool should_revive = false;\n      const auto& mia_dyn = base.dynamic_asset_data_id(d);\n      if( mia_dyn.current_supply == 0 ) // if current supply is zero, revive the asset\n         should_revive = true;\n      // if current supply is not zero, revive the asset when collateral ratio of settlement fund\n      //    is greater than ( MCR if before HF core-2290, ICR if after)\n      else if( next_maint_time <= HARDFORK_CORE_1270_TIME )\n      {\n         // before core-1270 hard fork, calculate call_price and compare to median feed\n         auto fund_call_price = ~price::call_price( asset(mia_dyn.current_supply, o.asset_id),\n                                    asset(bad.settlement_fund, bad.options.short_backing_asset),\n                                    bad.current_feed.maintenance_collateral_ratio );\n         should_revive = ( fund_call_price < bad.current_feed.settlement_price );\n      }\n      else\n      {\n         // after core-1270 hard fork, calculate collateralization and compare to maintenance_collateralization\n         price fund_collateralization( asset( bad.settlement_fund, bad.options.short_backing_asset ),\n                                       asset( mia_dyn.current_supply, o.asset_id ) );\n         should_revive = HARDFORK_CORE_2290_PASSED( next_maint_time ) ?\n                               ( fund_collateralization > bad.current_initial_collateralization )\n                             : ( fund_collateralization > bad.current_maintenance_collateralization );\n      }\n      if( should_revive )\n         d.revive_bitasset( base, bad );\n   }\n\n   // Process margin calls, allow black swan, not for a new limit order\n   d.check_call_orders( base, true, false, bitasset_ptr );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE\n\n\n/***\n * @brief evaluator for asset_claim_fees operation\n *\n * Checks that we are able to claim fees denominated in asset Y (the amount_to_claim asset),\n * from some container asset X which is presumed to have accumulated the fees we wish to claim.\n * The container asset is either explicitly named in the extensions, or else assumed as the same\n * asset as the amount_to_claim asset. Evaluation fails if either (a) operation issuer is not\n * the same as the container_asset issuer, or (b) container_asset has no fee bucket for\n * amount_to_claim asset, or (c) accumulated fees are insufficient to cover amount claimed.\n */\nvoid_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_operation& o )\n{ try {\n   const database& d = db();\n\n   detail::check_asset_claim_fees_hardfork_87_74_collatfee(d.head_block_time(), o); // HF_REMOVABLE\n\n   container_asset = o.extensions.value.claim_from_asset_id.valid() ?\n      &(*o.extensions.value.claim_from_asset_id)(d) : &o.amount_to_claim.asset_id(d);\n\n   FC_ASSERT( container_asset->issuer == o.issuer, \"Asset fees may only be claimed by the issuer\" );\n   FC_ASSERT( container_asset->can_accumulate_fee(d,o.amount_to_claim),\n              \"Asset ${a} (${id}) is not backed by asset (${fid}) and does not hold it as fees.\",\n              (\"a\",container_asset->symbol)(\"id\",container_asset->id)(\"fid\",o.amount_to_claim.asset_id) );\n\n   container_ddo = &container_asset->dynamic_asset_data_id(d);\n\n   if (container_asset->get_id() == o.amount_to_claim.asset_id) {\n      FC_ASSERT( o.amount_to_claim.amount <= container_ddo->accumulated_fees,\n                 \"Attempt to claim more fees than have accumulated within asset ${a} (${id}). \"\n                 \"Asset DDO: ${ddo}. Fee claim: ${claim}.\", (\"a\",container_asset->symbol)\n                 (\"id\",container_asset->id)(\"ddo\",*container_ddo)(\"claim\",o.amount_to_claim) );\n   } else {\n      FC_ASSERT( o.amount_to_claim.amount <= container_ddo->accumulated_collateral_fees,\n                 \"Attempt to claim more backing-asset fees than have accumulated within asset ${a} (${id}) \"\n                 \"backed by (${fid}). Asset DDO: ${ddo}. Fee claim: ${claim}.\", (\"a\",container_asset->symbol)\n                 (\"id\",container_asset->id)(\"fid\",o.amount_to_claim.asset_id)(\"ddo\",*container_ddo)\n                 (\"claim\",o.amount_to_claim) );\n      // Note: asset authorization check on (account, collateral asset) is skipped here,\n      //       because it is fine to allow the funds to be moved to account balance\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n\n/***\n * @brief apply asset_claim_fees operation\n */\nvoid_result asset_claim_fees_evaluator::do_apply( const asset_claim_fees_operation& o )\n{ try {\n   database& d = db();\n\n   if ( container_asset->get_id() == o.amount_to_claim.asset_id ) {\n      d.modify( *container_ddo, [&o]( asset_dynamic_data_object& _addo  ) {\n         _addo.accumulated_fees -= o.amount_to_claim.amount;\n      });\n   } else {\n      d.modify( *container_ddo, [&o]( asset_dynamic_data_object& _addo  ) {\n         _addo.accumulated_collateral_fees -= o.amount_to_claim.amount;\n      });\n   }\n\n   d.adjust_balance( o.issuer, o.amount_to_claim );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n\nvoid_result asset_claim_pool_evaluator::do_evaluate( const asset_claim_pool_operation& o )\n{ try {\n    FC_ASSERT( o.asset_id(db()).issuer == o.issuer, \"Asset fee pool may only be claimed by the issuer\" );\n\n    return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result asset_claim_pool_evaluator::do_apply( const asset_claim_pool_operation& o )\n{ try {\n    database& d = db();\n\n    const asset_object& a = o.asset_id(d);\n    const asset_dynamic_data_object& addo = a.dynamic_asset_data_id(d);\n    FC_ASSERT( o.amount_to_claim.amount <= addo.fee_pool, \"Attempt to claim more fees than is available\", (\"addo\",addo) );\n\n    d.modify( addo, [&o]( asset_dynamic_data_object& _addo  ) {\n        _addo.fee_pool -= o.amount_to_claim.amount;\n    });\n\n    d.adjust_balance( o.issuer, o.amount_to_claim );\n\n    return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/asset_object.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace chain {\n\nshare_type asset_bitasset_data_object::max_force_settlement_volume(share_type current_supply) const\n{\n   if( 0 == options.maximum_force_settlement_volume )\n      return 0;\n   if( GRAPHENE_100_PERCENT == options.maximum_force_settlement_volume )\n      return current_supply + force_settled_volume;\n\n   fc::uint128_t volume = current_supply.value;\n   volume += force_settled_volume.value;\n   volume *= options.maximum_force_settlement_volume;\n   volume /= GRAPHENE_100_PERCENT;\n   return static_cast<uint64_t>(volume);\n}\n\nvoid asset_bitasset_data_object::update_median_feeds( time_point_sec current_time,\n                                                      time_point_sec next_maintenance_time )\n{\n   bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue\n   current_feed_publication_time = current_time;\n   vector<std::reference_wrapper<const price_feed_with_icr>> effective_feeds;\n   // find feeds that were alive at current_time\n   for( const pair<account_id_type, pair<time_point_sec,price_feed_with_icr>>& f : feeds )\n   {\n      if( (current_time - f.second.first).to_seconds() < options.feed_lifetime_sec &&\n          f.second.first != time_point_sec() )\n      {\n         effective_feeds.emplace_back(f.second.second);\n         current_feed_publication_time = std::min(current_feed_publication_time, f.second.first);\n      }\n   }\n\n   // If there are no valid feeds, or the number available is less than the minimum to calculate a median...\n   if( effective_feeds.size() < options.minimum_feeds )\n   {\n      //... don't calculate a median, and set a null feed\n      feed_cer_updated = false; // new median cer is null, won't update asset_object anyway,\n                                // set to false for better performance\n      current_feed_publication_time = current_time;\n      median_feed = price_feed_with_icr();\n      if( after_core_hardfork_1270 )\n      {\n         // update data derived from MCR, ICR and etc\n         refresh_cache();\n      }\n      return;\n   }\n\n   if( 1U == effective_feeds.size() )\n   {\n      if( median_feed.core_exchange_rate != effective_feeds.front().get().core_exchange_rate )\n         feed_cer_updated = true;\n      median_feed = effective_feeds.front();\n      // Note: perhaps can defer updating current_maintenance_collateralization for better performance\n      if( after_core_hardfork_1270 )\n      {\n         const auto& exts = options.extensions.value;\n         if( exts.maintenance_collateral_ratio.valid() )\n            median_feed.maintenance_collateral_ratio = *exts.maintenance_collateral_ratio;\n         if( exts.maximum_short_squeeze_ratio.valid() )\n            median_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio;\n         if( exts.initial_collateral_ratio.valid() )\n            median_feed.initial_collateral_ratio = *exts.initial_collateral_ratio;\n         // update data derived from MCR, ICR and etc\n         refresh_cache();\n      }\n      return;\n   }\n\n   // *** Begin Median Calculations ***\n   price_feed_with_icr tmp_median_feed;\n   const auto median_itr = effective_feeds.begin() + ( effective_feeds.size() / 2 );\n#define CALCULATE_MEDIAN_VALUE(r, data, field_name) \\\n   std::nth_element( effective_feeds.begin(), median_itr, effective_feeds.end(), \\\n                     [](const price_feed_with_icr& a, const price_feed_with_icr& b) { \\\n      return a.field_name < b.field_name; \\\n   }); \\\n   tmp_median_feed.field_name = median_itr->get().field_name;\n\n#define CHECK_AND_CALCULATE_MEDIAN_VALUE(r, data, field_name) \\\n   if( options.extensions.value.field_name.valid() ) { \\\n      tmp_median_feed.field_name = *options.extensions.value.field_name; \\\n   } else { \\\n      CALCULATE_MEDIAN_VALUE(r, data, field_name); \\\n   }\n\n   BOOST_PP_SEQ_FOR_EACH( CALCULATE_MEDIAN_VALUE, ~, (settlement_price)(core_exchange_rate) )\n   BOOST_PP_SEQ_FOR_EACH( CHECK_AND_CALCULATE_MEDIAN_VALUE, ~,\n                          (maintenance_collateral_ratio)(maximum_short_squeeze_ratio)(initial_collateral_ratio) )\n#undef CHECK_AND_CALCULATE_MEDIAN_VALUE\n#undef CALCULATE_MEDIAN_VALUE\n   // *** End Median Calculations ***\n\n   if( median_feed.core_exchange_rate != tmp_median_feed.core_exchange_rate )\n      feed_cer_updated = true;\n   median_feed = tmp_median_feed;\n   // Note: perhaps can defer updating current_maintenance_collateralization for better performance\n   if( after_core_hardfork_1270 )\n   {\n      // update data derived from MCR, ICR and etc\n      refresh_cache();\n   }\n}\n\nvoid asset_bitasset_data_object::refresh_cache()\n{\n   current_maintenance_collateralization = median_feed.maintenance_collateralization();\n   if( median_feed.initial_collateral_ratio > median_feed.maintenance_collateral_ratio ) // if ICR is above MCR\n      current_initial_collateralization = median_feed.get_initial_collateralization();\n   else // if ICR is not above MCR\n      current_initial_collateralization = current_maintenance_collateralization;\n}\n\nprice price_feed_with_icr::get_initial_collateralization()const\n{\n   if( settlement_price.is_null() )\n      return price();\n   return ~settlement_price * ratio_type( initial_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );\n}\n\nasset asset_object::amount_from_string(string amount_string) const\n{ try {\n   bool negative_found = false;\n   bool decimal_found = false;\n   for( const char c : amount_string )\n   {\n      if( isdigit( c ) )\n         continue;\n\n      if( c == '-' && !negative_found )\n      {\n         negative_found = true;\n         continue;\n      }\n\n      if( c == '.' && !decimal_found )\n      {\n         decimal_found = true;\n         continue;\n      }\n\n      FC_THROW( (amount_string) );\n   }\n\n   share_type satoshis = 0;\n\n   share_type scaled_precision = asset::scaled_precision( precision );\n\n   const auto decimal_pos = amount_string.find( '.' );\n   const string lhs = amount_string.substr( negative_found, decimal_pos );\n   if( !lhs.empty() )\n      satoshis += fc::safe<int64_t>(std::stoll(lhs)) *= scaled_precision;\n\n   if( decimal_found )\n   {\n      const size_t max_rhs_size = std::to_string( scaled_precision.value ).substr( 1 ).size();\n\n      string rhs = amount_string.substr( decimal_pos + 1 );\n      FC_ASSERT( rhs.size() <= max_rhs_size );\n\n      while( rhs.size() < max_rhs_size )\n         rhs += '0';\n\n      if( !rhs.empty() )\n         satoshis += std::stoll( rhs );\n   }\n\n   FC_ASSERT( satoshis <= GRAPHENE_MAX_SHARE_SUPPLY );\n\n   if( negative_found )\n      satoshis *= -1;\n\n   return amount(satoshis);\n} FC_CAPTURE_AND_RETHROW( (amount_string) ) }\n\nstring asset_object::amount_to_string(share_type amount) const\n{\n   share_type scaled_precision = asset::scaled_precision( precision );\n\n   string result = fc::to_string(amount.value / scaled_precision.value);\n   auto decimals = abs( amount.value % scaled_precision.value );\n   if( decimals )\n   {\n      if( amount < 0 && result == \"0\" )\n         result = \"-0\";\n      result += \".\" + fc::to_string(scaled_precision.value + decimals).erase(0,1);\n   }\n   return result;\n}\n\n} } // namespace graphene::chain\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (graphene::db::object),\n                    (current_supply)(confidential_supply)(accumulated_fees)(accumulated_collateral_fees)(fee_pool) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (graphene::db::object),\n                    (asset_id)\n                    (feeds)\n                    (median_feed)\n                    (current_feed)\n                    (current_feed_publication_time)\n                    (current_maintenance_collateralization)\n                    (current_initial_collateralization)\n                    (options)\n                    (force_settled_volume)\n                    (is_prediction_market)\n                    (settlement_price)\n                    (settlement_fund)\n                    (individual_settlement_debt)\n                    (individual_settlement_fund)\n                    (asset_cer_updated)\n                    (feed_cer_updated)\n                  )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::price_feed_with_icr )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::asset_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::asset_bitasset_data_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::asset_dynamic_data_object )\n"
  },
  {
    "path": "libraries/chain/balance_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/balance_evaluator.hpp>\n#include <graphene/protocol/pts_address.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result balance_claim_evaluator::do_evaluate(const balance_claim_operation& op)\n{\n   database& d = db();\n   balance = &op.balance_to_claim(d);\n\n   bool is_balance_owner = (\n             op.balance_owner_key == balance->owner ||\n             pts_address(op.balance_owner_key, false) == balance->owner || // version = 56 (default)\n             pts_address(op.balance_owner_key, true) == balance->owner ); // version = 56 (default)\n   is_balance_owner = (\n             is_balance_owner ||\n             pts_address(op.balance_owner_key, false, 0) == balance->owner ||\n             pts_address(op.balance_owner_key, true, 0) == balance->owner );\n   GRAPHENE_ASSERT( is_balance_owner,\n             balance_claim_owner_mismatch,\n             \"Balance owner key was specified as '${op}' but balance's actual owner is '${bal}'\",\n             (\"op\", op.balance_owner_key)\n             (\"bal\", balance->owner)\n             );\n\n   FC_ASSERT(op.total_claimed.asset_id == balance->asset_type());\n\n   if( balance->is_vesting_balance() )\n   {\n      GRAPHENE_ASSERT(\n         balance->vesting_policy->is_withdraw_allowed(\n            { balance->balance,\n              d.head_block_time(),\n              op.total_claimed } ),\n         balance_claim_invalid_claim_amount,\n         \"Attempted to claim ${c} from a vesting balance with ${a} available\",\n         (\"c\", op.total_claimed)(\"a\", balance->available(d.head_block_time()))\n         );\n      GRAPHENE_ASSERT(\n         d.head_block_time() - balance->last_claim_date >= fc::days(1),\n         balance_claim_claimed_too_often,\n         \"Genesis vesting balances may not be claimed more than once per day.\"\n         );\n      return {};\n   }\n\n   FC_ASSERT(op.total_claimed == balance->balance);\n   return {};\n}\n\n/**\n * @note the fee is always 0 for this particular operation because once the\n * balance is claimed it frees up memory and it cannot be used to spam the network\n */\nvoid_result balance_claim_evaluator::do_apply(const balance_claim_operation& op)\n{\n   database& d = db();\n\n   if( balance->is_vesting_balance() && op.total_claimed < balance->balance )\n      d.modify(*balance, [&](balance_object& b) {\n         b.vesting_policy->on_withdraw({b.balance, d.head_block_time(), op.total_claimed});\n         b.balance -= op.total_claimed;\n         b.last_claim_date = d.head_block_time();\n      });\n   else\n      d.remove(*balance);\n\n   d.adjust_balance(op.deposit_to_account, op.total_claimed);\n   return {};\n}\n} } // namespace graphene::chain\n"
  },
  {
    "path": "libraries/chain/block_database.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/block_database.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <fc/io/raw.hpp>\n#include <boost/endian/buffers.hpp>\n\nnamespace graphene { namespace chain {\n\nstruct index_entry\n{\n   index_entry() {\n      block_pos = 0;\n      block_size = 0;\n   };\n   boost::endian::little_uint64_buf_t block_pos;\n   boost::endian::little_uint32_buf_t block_size;\n   block_id_type                      block_id;\n};\n }}\nFC_REFLECT( graphene::chain::index_entry, (block_pos)(block_size)(block_id) );\n\nnamespace graphene { namespace chain {\n\nvoid block_database::open( const fc::path& dbdir )\n{ try {\n   fc::create_directories(dbdir);\n   _block_num_to_pos.exceptions(std::ios_base::failbit | std::ios_base::badbit);\n   _blocks.exceptions(std::ios_base::failbit | std::ios_base::badbit);\n\n   _index_filename = dbdir / \"index\";\n   if( !fc::exists( _index_filename ) )\n   {\n     _block_num_to_pos.open( _index_filename.generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc);\n     _blocks.open( (dbdir/\"blocks\").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out | std::fstream::trunc);\n   }\n   else\n   {\n     _block_num_to_pos.open( _index_filename.generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out );\n     _blocks.open( (dbdir/\"blocks\").generic_string().c_str(), std::fstream::binary | std::fstream::in | std::fstream::out );\n   }\n} FC_CAPTURE_AND_RETHROW( (dbdir) ) }\n\nbool block_database::is_open()const\n{\n  return _blocks.is_open();\n}\n\nvoid block_database::close()\n{\n  _blocks.close();\n  _block_num_to_pos.close();\n}\n\nvoid block_database::flush()\n{\n  _blocks.flush();\n  _block_num_to_pos.flush();\n}\n\nvoid block_database::store( const block_id_type& _id, const signed_block& b )\n{\n   block_id_type id = _id;\n   if( id == block_id_type() )\n   {\n      id = b.id();\n      elog( \"id argument of block_database::store() was not initialized for block ${id}\", (\"id\", id) );\n   }\n   _block_num_to_pos.seekp( sizeof( index_entry ) * int64_t(block_header::num_from_id(id)) );\n   index_entry e;\n   _blocks.seekp( 0, _blocks.end );\n   auto vec = fc::raw::pack( b );\n   e.block_pos  = _blocks.tellp();\n   e.block_size = vec.size();\n   e.block_id   = id;\n   _blocks.write( vec.data(), vec.size() );\n   _block_num_to_pos.write( (char*)&e, sizeof(e) );\n}\n\nvoid block_database::remove( const block_id_type& id )\n{ try {\n   index_entry e;\n   int64_t index_pos = sizeof(e) * int64_t(block_header::num_from_id(id));\n   _block_num_to_pos.seekg( 0, _block_num_to_pos.end );\n   if ( _block_num_to_pos.tellg() <= index_pos )\n      FC_THROW_EXCEPTION(fc::key_not_found_exception, \"Block ${id} not contained in block database\", (\"id\", id));\n\n   _block_num_to_pos.seekg( index_pos );\n   _block_num_to_pos.read( (char*)&e, sizeof(e) );\n\n   if( e.block_id == id )\n   {\n      e.block_size = 0;\n      _block_num_to_pos.seekp( sizeof(e) * int64_t(block_header::num_from_id(id)) );\n      _block_num_to_pos.write( (char*)&e, sizeof(e) );\n   }\n} FC_CAPTURE_AND_RETHROW( (id) ) }\n\nbool block_database::contains( const block_id_type& id )const\n{\n   if( id == block_id_type() )\n      return false;\n\n   index_entry e;\n   int64_t index_pos = sizeof(e) * int64_t(block_header::num_from_id(id));\n   _block_num_to_pos.seekg( 0, _block_num_to_pos.end );\n   if ( _block_num_to_pos.tellg() < int64_t(index_pos + sizeof(e)) )\n      return false;\n   _block_num_to_pos.seekg( index_pos );\n   _block_num_to_pos.read( (char*)&e, sizeof(e) );\n\n   return e.block_id == id && e.block_size.value() > 0;\n}\n\nblock_id_type block_database::fetch_block_id( uint32_t block_num )const\n{\n   assert( block_num != 0 );\n   index_entry e;\n   int64_t index_pos = sizeof(e) * int64_t(block_num);\n   _block_num_to_pos.seekg( 0, _block_num_to_pos.end );\n   if ( _block_num_to_pos.tellg() <= index_pos )\n      FC_THROW_EXCEPTION(fc::key_not_found_exception, \"Block number ${block_num} not contained in block database\", (\"block_num\", block_num));\n\n   _block_num_to_pos.seekg( index_pos );\n   _block_num_to_pos.read( (char*)&e, sizeof(e) );\n\n   FC_ASSERT( e.block_id != block_id_type(), \"Empty block_id in block_database (maybe corrupt on disk?)\" );\n   return e.block_id;\n}\n\noptional<signed_block> block_database::fetch_optional( const block_id_type& id )const\n{\n   try\n   {\n      index_entry e;\n      int64_t index_pos = sizeof(e) * int64_t(block_header::num_from_id(id));\n      _block_num_to_pos.seekg( 0, _block_num_to_pos.end );\n      if ( _block_num_to_pos.tellg() <= index_pos )\n         return {};\n\n      _block_num_to_pos.seekg( index_pos );\n      _block_num_to_pos.read( (char*)&e, sizeof(e) );\n\n      if( e.block_id != id ) return optional<signed_block>();\n\n      vector<char> data( e.block_size.value() );\n      _blocks.seekg( e.block_pos.value() );\n      if (e.block_size.value())\n         _blocks.read( data.data(), e.block_size.value() );\n      auto result = fc::raw::unpack<signed_block>(data);\n      FC_ASSERT( result.id() == e.block_id );\n      return result;\n   }\n   catch (const fc::exception&)\n   {\n   }\n   catch (const std::exception&)\n   {\n   }\n   return optional<signed_block>();\n}\n\noptional<signed_block> block_database::fetch_by_number( uint32_t block_num )const\n{\n   try\n   {\n      index_entry e;\n      int64_t index_pos = sizeof(e) * int64_t(block_num);\n      _block_num_to_pos.seekg( 0, _block_num_to_pos.end );\n      if ( _block_num_to_pos.tellg() <= index_pos )\n         return {};\n\n      _block_num_to_pos.seekg( index_pos, _block_num_to_pos.beg );\n      _block_num_to_pos.read( (char*)&e, sizeof(e) );\n\n      vector<char> data( e.block_size.value() );\n      _blocks.seekg( e.block_pos.value() );\n      _blocks.read( data.data(), e.block_size.value() );\n      auto result = fc::raw::unpack<signed_block>(data);\n      FC_ASSERT( result.id() == e.block_id );\n      return result;\n   }\n   catch (const fc::exception&)\n   {\n   }\n   catch (const std::exception&)\n   {\n   }\n   return optional<signed_block>();\n}\n\noptional<index_entry> block_database::last_index_entry()const {\n   try\n   {\n      index_entry e;\n\n      _block_num_to_pos.seekg( 0, _block_num_to_pos.end );\n      std::streampos pos = _block_num_to_pos.tellg();\n      if( pos < long(sizeof(index_entry)) )\n         return optional<index_entry>();\n\n      pos -= pos % sizeof(index_entry);\n\n      _blocks.seekg( 0, _block_num_to_pos.end );\n      const std::streampos blocks_size = _blocks.tellg();\n      while( pos > 0 )\n      {\n         pos -= sizeof(index_entry);\n         _block_num_to_pos.seekg( pos );\n         _block_num_to_pos.read( (char*)&e, sizeof(e) );\n         if( _block_num_to_pos.gcount() == sizeof(e) && e.block_size.value() > 0\n                && int64_t(e.block_pos.value() + e.block_size.value()) <= blocks_size )\n            try\n            {\n               vector<char> data( e.block_size.value() );\n               _blocks.seekg( e.block_pos.value() );\n               _blocks.read( data.data(), e.block_size.value() );\n               if( _blocks.gcount() == long(e.block_size.value()) )\n               {\n                  const signed_block block = fc::raw::unpack<signed_block>(data);\n                  if( block.id() == e.block_id )\n                     return e;\n               }\n            }\n            catch (const fc::exception&)\n            {\n            }\n            catch (const std::exception&)\n            {\n            }\n         fc::resize_file( _index_filename, pos );\n      }\n   }\n   catch (const fc::exception&)\n   {\n   }\n   catch (const std::exception&)\n   {\n   }\n   return optional<index_entry>();\n}\n\noptional<signed_block> block_database::last()const\n{\n   optional<index_entry> entry = last_index_entry();\n   if( entry.valid() ) return fetch_by_number( block_header::num_from_id(entry->block_id) );\n   return optional<signed_block>();\n}\n\noptional<block_id_type> block_database::last_id()const\n{\n   optional<index_entry> entry = last_index_entry();\n   if( entry.valid() ) return entry->block_id;\n   return optional<block_id_type>();\n}\n\nsize_t block_database::blocks_current_position()const\n{\n   return (size_t)_blocks.tellg();\n}\n\nsize_t block_database::total_block_size()const\n{\n   _blocks.seekg( 0, _blocks.end );\n   return (size_t)_blocks.tellg();\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/buyback.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/buyback.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid evaluate_buyback_account_options( const database& db, const buyback_account_options& bbo )\n{\n   const asset_object& a = bbo.asset_to_buy(db);\n   GRAPHENE_ASSERT( a.issuer == bbo.asset_to_buy_issuer,\n      account_create_buyback_incorrect_issuer, \"Incorrect asset issuer specified in buyback_account_options\", (\"asset\", a)(\"bbo\", bbo) );\n   GRAPHENE_ASSERT( !a.buyback_account.valid(),\n      account_create_buyback_already_exists, \"Cannot create buyback for asset which already has buyback\", (\"asset\", a)(\"bbo\", bbo) );\n   // TODO:  Replace with chain parameter #554\n   GRAPHENE_ASSERT( bbo.markets.size() < GRAPHENE_DEFAULT_MAX_BUYBACK_MARKETS,\n      account_create_buyback_too_many_markets, \"Too many buyback markets\", (\"asset\", a)(\"bbo\", bbo) );\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/committee_member_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/committee_member_evaluator.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <graphene/protocol/vote.hpp>\n#include <graphene/chain/transaction_evaluation_state.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result committee_member_create_evaluator::do_evaluate( const committee_member_create_operation& op )\n{ try {\n   FC_ASSERT(db().get(op.committee_member_account).is_lifetime_member());\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nobject_id_type committee_member_create_evaluator::do_apply( const committee_member_create_operation& op )\n{ try {\n   vote_id_type vote_id;\n   db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) {\n      vote_id = vote_id_type(vote_id_type::committee, p.next_available_vote_id++);\n   });\n\n   const auto& new_del_object = db().create<committee_member_object>( [&]( committee_member_object& obj ){\n         obj.committee_member_account   = op.committee_member_account;\n         obj.vote_id            = vote_id;\n         obj.url                = op.url;\n   });\n   return new_del_object.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result committee_member_update_evaluator::do_evaluate( const committee_member_update_operation& op )\n{ try {\n   FC_ASSERT(db().get(op.committee_member).committee_member_account == op.committee_member_account);\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result committee_member_update_evaluator::do_apply( const committee_member_update_operation& op )\n{ try {\n   database& _db = db();\n   _db.modify(\n      _db.get(op.committee_member),\n      [&]( committee_member_object& com )\n      {\n         if( op.new_url.valid() )\n            com.url = *op.new_url;\n      });\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result committee_member_update_global_parameters_evaluator::do_evaluate(\n        const committee_member_update_global_parameters_operation& o)\n{ try {\n   FC_ASSERT(trx_state->_is_proposed_trx);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result committee_member_update_global_parameters_evaluator::do_apply(\n        const committee_member_update_global_parameters_operation& o)\n{ try {\n   db().modify(db().get_global_properties(), [&o](global_property_object& p) {\n      p.pending_parameters = o.new_parameters;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/confidential_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/protocol/confidential.hpp>\n#include <graphene/chain/confidential_evaluator.hpp>\n#include <graphene/chain/confidential_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/fba_accumulator_id.hpp>\n#include <graphene/chain/hardfork.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result transfer_to_blind_evaluator::do_evaluate( const transfer_to_blind_operation& o )\n{ try {\n   const auto& d = db();\n\n   const auto& atype = o.amount.asset_id(db()); \n   FC_ASSERT( atype.allow_confidential() );\n   FC_ASSERT( !atype.is_transfer_restricted() );\n   FC_ASSERT( !(atype.options.flags & white_list) );\n\n   for( const auto& out : o.outputs )\n   {\n      for( const auto& a : out.owner.account_auths )\n         a.first(d); // verify all accounts exist and are valid\n   }\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\n\nvoid_result transfer_to_blind_evaluator::do_apply( const transfer_to_blind_operation& o ) \n{ try {\n   db().adjust_balance( o.from, -o.amount ); \n\n   const auto& add = o.amount.asset_id(db()).dynamic_asset_data_id(db());  // verify fee is a legit asset \n   db().modify( add, [&]( asset_dynamic_data_object& obj ){\n      obj.confidential_supply += o.amount.amount;\n      FC_ASSERT( obj.confidential_supply >= 0 );\n   });\n   for( const auto& out : o.outputs )\n   {\n      db().create<blinded_balance_object>( [&]( blinded_balance_object& obj ){\n          obj.asset_id   = o.amount.asset_id;\n          obj.owner      = out.owner;\n          obj.commitment = out.commitment;\n      });\n   }\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid transfer_to_blind_evaluator::pay_fee()\n{\n   if( db().head_block_time() >= HARDFORK_563_TIME )\n      pay_fba_fee( fba_accumulator_id_transfer_to_blind );\n   else\n      generic_evaluator::pay_fee();\n}\n\nvoid_result transfer_from_blind_evaluator::do_evaluate( const transfer_from_blind_operation& o )\n{ try {\n   const auto& d = db();\n   o.fee.asset_id(d);  // verify fee is a legit asset \n   const auto& bbi = d.get_index_type<blinded_balance_index>();\n   const auto& cidx = bbi.indices().get<by_commitment>();\n   for( const auto& in : o.inputs )\n   {\n      auto itr = cidx.find( in.commitment );\n      FC_ASSERT( itr != cidx.end() );\n      FC_ASSERT( itr->asset_id == o.fee.asset_id );\n      FC_ASSERT( itr->owner == in.owner );\n   }\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result transfer_from_blind_evaluator::do_apply( const transfer_from_blind_operation& o ) \n{ try {\n   db().adjust_balance( o.fee_payer(), o.fee ); \n   db().adjust_balance( o.to, o.amount ); \n   const auto& bbi = db().get_index_type<blinded_balance_index>();\n   const auto& cidx = bbi.indices().get<by_commitment>();\n   for( const auto& in : o.inputs )\n   {\n      auto itr = cidx.find( in.commitment );\n      FC_ASSERT( itr != cidx.end() );\n      db().remove( *itr );\n   }\n   const auto& add = o.amount.asset_id(db()).dynamic_asset_data_id(db());  // verify fee is a legit asset \n   db().modify( add, [&]( asset_dynamic_data_object& obj ){\n      obj.confidential_supply -= o.amount.amount + o.fee.amount;\n      FC_ASSERT( obj.confidential_supply >= 0 );\n   });\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid transfer_from_blind_evaluator::pay_fee()\n{\n   if( db().head_block_time() >= HARDFORK_563_TIME )\n      pay_fba_fee( fba_accumulator_id_transfer_from_blind );\n   else\n      generic_evaluator::pay_fee();\n}\n\nvoid_result blind_transfer_evaluator::do_evaluate( const blind_transfer_operation& o )\n{ try {\n   const auto& d = db();\n   o.fee.asset_id(db());  // verify fee is a legit asset \n   const auto& bbi = db().get_index_type<blinded_balance_index>();\n   const auto& cidx = bbi.indices().get<by_commitment>();\n   for( const auto& out : o.outputs )\n   {\n      for( const auto& a : out.owner.account_auths )\n         a.first(d); // verify all accounts exist and are valid\n   }\n   for( const auto& in : o.inputs )\n   {\n      auto itr = cidx.find( in.commitment );\n      GRAPHENE_ASSERT( itr != cidx.end(), blind_transfer_unknown_commitment, \"\", (\"commitment\",in.commitment) );\n      FC_ASSERT( itr->asset_id == o.fee.asset_id );\n      FC_ASSERT( itr->owner == in.owner );\n   }\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid_result blind_transfer_evaluator::do_apply( const blind_transfer_operation& o ) \n{ try {\n   db().adjust_balance( o.fee_payer(), o.fee ); // deposit the fee to the temp account\n   const auto& bbi = db().get_index_type<blinded_balance_index>();\n   const auto& cidx = bbi.indices().get<by_commitment>();\n   for( const auto& in : o.inputs )\n   {\n      auto itr = cidx.find( in.commitment );\n      GRAPHENE_ASSERT( itr != cidx.end(), blind_transfer_unknown_commitment, \"\", (\"commitment\",in.commitment) );\n      db().remove( *itr );\n   }\n   for( const auto& out : o.outputs )\n   {\n      db().create<blinded_balance_object>( [&]( blinded_balance_object& obj ){\n          obj.asset_id   = o.fee.asset_id;\n          obj.owner      = out.owner;\n          obj.commitment = out.commitment;\n      });\n   }\n   const auto& add = o.fee.asset_id(db()).dynamic_asset_data_id(db());  \n   db().modify( add, [&]( asset_dynamic_data_object& obj ){\n      obj.confidential_supply -= o.fee.amount;\n      FC_ASSERT( obj.confidential_supply >= 0 );\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid blind_transfer_evaluator::pay_fee()\n{\n   if( db().head_block_time() >= HARDFORK_563_TIME )\n      pay_fba_fee( fba_accumulator_id_blind_transfer );\n   else\n      generic_evaluator::pay_fee();\n}\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/credit_offer_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n\n#include <graphene/chain/credit_offer_evaluator.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <graphene/protocol/credit_offer.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result credit_offer_create_evaluator::do_evaluate(const credit_offer_create_operation& op) const\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), \"Not allowed until the core-2362 hardfork\" );\n\n   if( op.enabled )\n   {\n      FC_ASSERT( op.auto_disable_time > block_time, \"Auto-disable time should be in the future\" );\n      FC_ASSERT( op.auto_disable_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS),\n                 \"Auto-disable time should not be later than ${d} days in the future\",\n                 (\"d\", GRAPHENE_MAX_CREDIT_OFFER_DAYS) );\n   }\n\n   // Make sure all the collateral asset types exist\n   for( const auto& collateral : op.acceptable_collateral )\n   {\n      collateral.first(d);\n   }\n\n   // Make sure all the accounts exist\n   for( const auto& borrower : op.acceptable_borrowers )\n   {\n      borrower.first(d);\n   }\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, op.asset_type(d) ),\n              \"The account is unauthorized by the asset\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nobject_id_type credit_offer_create_evaluator::do_apply(const credit_offer_create_operation& op) const\n{ try {\n   database& d = db();\n\n   d.adjust_balance( op.owner_account, -asset( op.balance, op.asset_type ) );\n\n   const auto& new_credit_offer_object = d.create<credit_offer_object>([&op](credit_offer_object& obj){\n      obj.owner_account = op.owner_account;\n      obj.asset_type = op.asset_type;\n      obj.total_balance = op.balance;\n      obj.current_balance = op.balance;\n      obj.fee_rate = op.fee_rate;\n      obj.max_duration_seconds = op.max_duration_seconds;\n      obj.min_deal_amount = op.min_deal_amount;\n      obj.enabled = op.enabled;\n      obj.auto_disable_time = op.auto_disable_time;\n      obj.acceptable_collateral = op.acceptable_collateral;\n      obj.acceptable_borrowers = op.acceptable_borrowers;\n   });\n   return new_credit_offer_object.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_offer_delete_evaluator::do_evaluate(const credit_offer_delete_operation& op)\n{ try {\n   const database& d = db();\n\n   _offer = &op.offer_id(d);\n\n   FC_ASSERT( _offer->owner_account == op.owner_account, \"The account is not the owner of the credit offer\" );\n\n   FC_ASSERT( _offer->total_balance == _offer->current_balance,\n              \"Can only delete a credit offer when the unpaid amount is zero\" );\n\n   // Note: no asset authorization check here, allow funds to be moved to account balance\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nasset credit_offer_delete_evaluator::do_apply(const credit_offer_delete_operation& op) const\n{ try {\n   database& d = db();\n\n   asset released( _offer->current_balance, _offer->asset_type );\n\n   if( _offer->current_balance != 0 )\n   {\n      d.adjust_balance( op.owner_account, released );\n   }\n\n   d.remove( *_offer );\n\n   return released;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_offer_update_evaluator::do_evaluate(const credit_offer_update_operation& op)\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   _offer = &op.offer_id(d);\n\n   FC_ASSERT( _offer->owner_account == op.owner_account, \"The account is not the owner of the credit offer\" );\n\n   if( op.delta_amount.valid() )\n   {\n      FC_ASSERT( _offer->asset_type == op.delta_amount->asset_id, \"Asset type mismatch\" );\n\n      if( op.delta_amount->amount > 0 )\n      {\n         // Check asset authorization only when moving funds from account balance to somewhere else\n         FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _offer->asset_type(d) ),\n                    \"The account is unauthorized by the asset\" );\n      }\n      else\n      {\n         FC_ASSERT( _offer->total_balance > -op.delta_amount->amount,\n                    \"Should leave some funds in the credit offer when updating\" );\n         FC_ASSERT( _offer->current_balance >= -op.delta_amount->amount, \"Insufficient balance in the credit offer\" );\n      }\n   }\n\n   bool enabled = op.enabled.valid() ? *op.enabled : _offer->enabled;\n   if( enabled )\n   {\n      auto auto_disable_time = op.auto_disable_time.valid() ? *op.auto_disable_time : _offer->auto_disable_time;\n      FC_ASSERT( auto_disable_time > block_time, \"Auto-disable time should be in the future\" );\n      FC_ASSERT( auto_disable_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS),\n                 \"Auto-disable time should not be later than ${d} days in the future\",\n                 (\"d\", GRAPHENE_MAX_CREDIT_OFFER_DAYS) );\n   }\n\n   // Make sure all the collateral asset types exist\n   if( op.acceptable_collateral.valid() )\n   {\n      for( const auto& collateral : *op.acceptable_collateral )\n      {\n         collateral.first(d);\n         FC_ASSERT( _offer->asset_type == collateral.second.base.asset_id,\n                    \"Asset type mismatch in a price of acceptable collateral\" );\n      }\n   }\n\n   // Make sure all the accounts exist\n   if( op.acceptable_borrowers.valid() )\n   {\n      for( const auto& borrower : *op.acceptable_borrowers )\n      {\n         borrower.first(d);\n      }\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_offer_update_evaluator::do_apply( const credit_offer_update_operation& op) const\n{ try {\n   database& d = db();\n\n   if( op.delta_amount.valid() )\n      d.adjust_balance( op.owner_account, -(*op.delta_amount) );\n\n   d.modify( *_offer, [&op]( credit_offer_object& coo ){\n      if( op.delta_amount.valid() ) {\n         coo.total_balance += op.delta_amount->amount;\n         coo.current_balance += op.delta_amount->amount;\n      }\n      if( op.fee_rate.valid() )\n         coo.fee_rate = *op.fee_rate;\n      if( op.max_duration_seconds.valid() )\n         coo.max_duration_seconds = *op.max_duration_seconds;\n      if( op.min_deal_amount.valid() )\n         coo.min_deal_amount = *op.min_deal_amount;\n      if( op.enabled.valid() )\n         coo.enabled = *op.enabled;\n      if( op.auto_disable_time.valid() )\n         coo.auto_disable_time = *op.auto_disable_time;\n      if( op.acceptable_collateral.valid() )\n         coo.acceptable_collateral = *op.acceptable_collateral;\n      if( op.acceptable_borrowers.valid() )\n         coo.acceptable_borrowers = *op.acceptable_borrowers;\n   });\n\n   // Defensive checks\n   FC_ASSERT( _offer->total_balance > 0, \"Total balance in the credit offer should be positive\" );\n   FC_ASSERT( _offer->current_balance >= 0, \"Current balance in the credit offer should not be negative\" );\n   FC_ASSERT( _offer->total_balance >= _offer->current_balance,\n              \"Total balance in the credit offer should not be less than current balance\" );\n   if( _offer->enabled )\n   {\n      FC_ASSERT( _offer->auto_disable_time > d.head_block_time(),\n                 \"Auto-disable time should be in the future if the credit offer is enabled\" );\n      FC_ASSERT( _offer->auto_disable_time - d.head_block_time() <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS),\n                 \"Auto-disable time should not be too late in the future\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_offer_accept_evaluator::do_evaluate(const credit_offer_accept_operation& op)\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   if( !HARDFORK_CORE_2595_PASSED(block_time) )\n   {\n      FC_ASSERT( !op.extensions.value.auto_repay.valid(),\n                 \"auto_repay unavailable until the core-2595 hardfork\");\n   }\n\n   _offer = &op.offer_id(d);\n\n   FC_ASSERT( _offer->enabled, \"The credit offer is not enabled\" );\n\n   FC_ASSERT( _offer->asset_type == op.borrow_amount.asset_id, \"Asset type mismatch\" );\n\n   FC_ASSERT( _offer->current_balance >= op.borrow_amount.amount,\n              \"Insufficient balance in the credit offer thus unable to borrow\" );\n\n   FC_ASSERT( _offer->min_deal_amount <= op.borrow_amount.amount,\n              \"Borrowing amount should not be less than minimum deal amount\" );\n\n   FC_ASSERT( _offer->fee_rate <= op.max_fee_rate,\n              \"The maximum accceptable fee rate is lower than offered\" );\n\n   FC_ASSERT( _offer->max_duration_seconds >= op.min_duration_seconds,\n              \"The minimum accceptable duration is longer than offered\" );\n\n   auto coll_itr = _offer->acceptable_collateral.find( op.collateral.asset_id );\n   FC_ASSERT( coll_itr != _offer->acceptable_collateral.end(),\n              \"Collateral asset type is not acceptable by the credit offer\" );\n\n   const asset_object& debt_asset_obj = _offer->asset_type(d);\n   const asset_object& collateral_asset_obj = op.collateral.asset_id(d);\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, debt_asset_obj ),\n              \"The borrower is unauthorized by the borrowing asset\" );\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, collateral_asset_obj ),\n              \"The borrower is unauthorized by the collateral asset\" );\n\n   const account_object& offer_owner = _offer->owner_account(d);\n\n   FC_ASSERT( is_authorized_asset( d, offer_owner, debt_asset_obj ),\n              \"The owner of the credit offer is unauthorized by the borrowing asset\" );\n   FC_ASSERT( is_authorized_asset( d, offer_owner, collateral_asset_obj ),\n              \"The owner of the credit offer is unauthorized by the collateral asset\" );\n\n   auto required_collateral = op.borrow_amount.multiply_and_round_up( coll_itr->second );\n   FC_ASSERT( required_collateral.amount <= op.collateral.amount,\n              \"Insufficient collateral provided, requires ${r}, provided ${p}\",\n              (\"r\", required_collateral.amount) (\"p\", op.collateral.amount) );\n\n   optional<share_type> max_allowed;\n   if( !_offer->acceptable_borrowers.empty() )\n   {\n      auto itr = _offer->acceptable_borrowers.find( op.borrower );\n      FC_ASSERT( itr != _offer->acceptable_borrowers.end(), \"Account is not in acceptable borrowers\" );\n      max_allowed = itr->second;\n   }\n\n   share_type already_borrowed = 0;\n   const auto& deal_summary_idx = d.get_index_type<credit_deal_summary_index>().indices().get<by_offer_borrower>();\n   auto summ_itr = deal_summary_idx.find( boost::make_tuple( op.offer_id, op.borrower ) );\n   if( summ_itr != deal_summary_idx.end() )\n   {\n      _deal_summary = &(*summ_itr);\n      already_borrowed = _deal_summary->total_debt_amount;\n   }\n\n   if( max_allowed.valid() )\n   {\n      FC_ASSERT( already_borrowed + op.borrow_amount.amount <= *max_allowed,\n                 \"Unable to borrow ${b}, already borrowed ${a}, maximum allowed ${m}\",\n                 (\"b\", op.borrow_amount.amount) (\"a\", already_borrowed) (\"m\", max_allowed) );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nextendable_operation_result credit_offer_accept_evaluator::do_apply( const credit_offer_accept_operation& op) const\n{ try {\n   database& d = db();\n\n   d.adjust_balance( op.borrower, -op.collateral );\n   d.adjust_balance( op.borrower, op.borrow_amount );\n\n   d.modify( *_offer, [&op]( credit_offer_object& coo ){\n      coo.current_balance -= op.borrow_amount.amount;\n   });\n\n   const auto block_time = d.head_block_time();\n   auto repay_time = ( fc::time_point_sec::maximum() - block_time ) >= fc::seconds(_offer->max_duration_seconds)\n                     ? ( block_time + _offer->max_duration_seconds )\n                     : fc::time_point_sec::maximum();\n\n   const auto& new_deal = d.create<credit_deal_object>([&op,this,&repay_time](credit_deal_object& obj){\n      obj.borrower = op.borrower;\n      obj.offer_id = op.offer_id;\n      obj.offer_owner = _offer->owner_account;\n      obj.debt_asset = _offer->asset_type;\n      obj.debt_amount = op.borrow_amount.amount;\n      obj.collateral_asset = op.collateral.asset_id;\n      obj.collateral_amount = op.collateral.amount;\n      obj.fee_rate = _offer->fee_rate;\n      obj.latest_repay_time = repay_time;\n      obj.auto_repay = ( op.extensions.value.auto_repay.valid() ? *op.extensions.value.auto_repay : 0 );\n   });\n\n   if( _deal_summary != nullptr )\n   {\n      d.modify( *_deal_summary, [&op]( credit_deal_summary_object& obj ){\n         obj.total_debt_amount += op.borrow_amount.amount;\n      });\n   }\n   else\n   {\n      d.create<credit_deal_summary_object>([&op,this](credit_deal_summary_object& obj){\n         obj.borrower = op.borrower;\n         obj.offer_id = op.offer_id;\n         obj.offer_owner = _offer->owner_account;\n         obj.debt_asset = _offer->asset_type;\n         obj.total_debt_amount = op.borrow_amount.amount;\n      });\n   }\n\n   // Defensive check\n   FC_ASSERT( _offer->total_balance > 0, \"Total balance in the credit offer should be positive\" );\n   FC_ASSERT( _offer->current_balance >= 0, \"Current balance in the credit offer should not be negative\" );\n   FC_ASSERT( _offer->total_balance >= _offer->current_balance,\n              \"Total balance in the credit offer should not be less than current balance\" );\n   FC_ASSERT( new_deal.latest_repay_time > block_time,\n              \"Latest repayment time should be in the future\" );\n   FC_ASSERT( new_deal.latest_repay_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_DEAL_DAYS),\n              \"Latest repayment time should not be too late in the future\" );\n\n   extendable_operation_result result;\n   // Note: only return the deal here, deal summary is impl so we do not return it\n   result.value.new_objects = flat_set<object_id_type>({ new_deal.id });\n   result.value.impacted_accounts = flat_set<account_id_type>({ _offer->owner_account });\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_deal_repay_evaluator::do_evaluate(const credit_deal_repay_operation& op)\n{ try {\n   const database& d = db();\n\n   _deal = &op.deal_id(d);\n\n   FC_ASSERT( _deal->borrower == op.account, \"A credit deal can only be repaid by the borrower\" );\n\n   FC_ASSERT( _deal->debt_asset == op.repay_amount.asset_id, \"Asset type mismatch\" );\n\n   FC_ASSERT( _deal->debt_amount >= op.repay_amount.amount,\n              \"Repay amount should not be greater than unpaid amount\" );\n\n   // Note: the result can be larger than 64 bit, but since we don't store it, it is allowed\n   auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->fee_rate )\n                           + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up\n\n   FC_ASSERT( fc::uint128_t(op.credit_fee.amount.value) >= required_fee,\n              \"Insuffient credit fee, requires ${r}, offered ${p}\",\n              (\"r\", required_fee) (\"p\", op.credit_fee.amount) );\n\n   const asset_object& debt_asset_obj = _deal->debt_asset(d);\n   // Note: allow collateral to be moved to account balance regardless of collateral asset authorization\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, debt_asset_obj ),\n              \"The account is unauthorized by the repaying asset\" );\n   FC_ASSERT( is_authorized_asset( d, _deal->offer_owner(d), debt_asset_obj ),\n              \"The owner of the credit offer is unauthorized by the repaying asset\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nextendable_operation_result credit_deal_repay_evaluator::do_apply( const credit_deal_repay_operation& op) const\n{ try {\n   database& d = db();\n\n   share_type total_amount = op.repay_amount.amount + op.credit_fee.amount;\n\n   d.adjust_balance( op.account, asset( -total_amount, op.repay_amount.asset_id ) );\n\n   // Update offer\n   const credit_offer_object& offer = _deal->offer_id(d);\n   d.modify( offer, [&op,&total_amount]( credit_offer_object& obj ){\n      obj.total_balance += op.credit_fee.amount;\n      obj.current_balance += total_amount;\n   });\n   // Defensive check\n   FC_ASSERT( offer.total_balance >= offer.current_balance,\n              \"Total balance in the credit offer should not be less than current balance\" );\n\n   extendable_operation_result result;\n   result.value.impacted_accounts = flat_set<account_id_type>({ offer.owner_account });\n   result.value.updated_objects = flat_set<object_id_type>({ offer.id });\n\n   // Process deal summary\n   const auto& deal_summary_idx = d.get_index_type<credit_deal_summary_index>().indices().get<by_offer_borrower>();\n   auto summ_itr = deal_summary_idx.find( boost::make_tuple( _deal->offer_id, op.account ) );\n   FC_ASSERT( summ_itr != deal_summary_idx.end(), \"Internal error\" );\n\n   const credit_deal_summary_object& summ_obj = *summ_itr;\n   if( summ_obj.total_debt_amount == op.repay_amount.amount )\n   {\n      d.remove( summ_obj );\n   }\n   else\n   {\n      d.modify( summ_obj, [&op]( credit_deal_summary_object& obj ){\n         obj.total_debt_amount -= op.repay_amount.amount;\n      });\n   }\n\n   // Process deal\n   asset collateral_released( _deal->collateral_amount, _deal->collateral_asset );\n   if( _deal->debt_amount == op.repay_amount.amount ) // to fully repay\n   {\n      result.value.removed_objects = flat_set<object_id_type>({ _deal->id });\n      d.remove( *_deal );\n   }\n   else // to partially repay\n   {\n      // Note:\n      // Due to rounding, it is possible that the account is paying too much debt asset for too little collateral,\n      // in extreme cases, the amount to release can be zero.\n      auto amount_to_release = ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->collateral_amount.value )\n                                 / _deal->debt_amount.value; // Round down\n      FC_ASSERT( amount_to_release < fc::uint128_t( _deal->collateral_amount.value ), \"Internal error\" );\n      collateral_released.amount = static_cast<int64_t>( amount_to_release );\n\n      d.modify( *_deal, [&op,&collateral_released]( credit_deal_object& obj ){\n         obj.debt_amount -= op.repay_amount.amount;\n         obj.collateral_amount -= collateral_released.amount;\n      });\n\n      result.value.updated_objects->insert( _deal->id );\n   }\n\n   d.adjust_balance( op.account, collateral_released );\n   result.value.received = vector<asset>({ collateral_released });\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_deal_update_evaluator::do_evaluate(const credit_deal_update_operation& op)\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   FC_ASSERT( HARDFORK_CORE_2595_PASSED(block_time), \"Not allowed until the core-2595 hardfork\" );\n\n   _deal = &op.deal_id(d);\n\n   FC_ASSERT( _deal->borrower == op.account, \"A credit deal can only be updated by the borrower\" );\n\n   FC_ASSERT( _deal->auto_repay != op.auto_repay, \"The automatic repayment type does not change\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result credit_deal_update_evaluator::do_apply( const credit_deal_update_operation& op) const\n{ try {\n   database& d = db();\n\n   d.modify( *_deal, [&op]( credit_deal_object& obj ){\n      obj.auto_repay = op.auto_repay;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/custom_authority_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/custom_authority_evaluator.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork_visitor.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result custom_authority_create_evaluator::do_evaluate(const custom_authority_create_operation& op)\n{ try {\n   const database& d = db();\n   auto now = d.head_block_time();\n   FC_ASSERT(HARDFORK_BSIP_40_PASSED(now), \"Custom active authorities are not yet enabled\");\n\n   op.account(d);\n\n   const auto& config = d.get_global_properties().parameters.extensions.value.custom_authority_options;\n   FC_ASSERT(config.valid(), \"Cannot use custom authorities yet: global configuration not set\");\n   FC_ASSERT(op.valid_to > now, \"Custom authority expiration must be in the future\");\n   FC_ASSERT((op.valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds,\n             \"Custom authority lifetime exceeds maximum limit\");\n\n   bool operation_forked_in = hardfork_visitor(now).visit((operation::tag_type)op.operation_type.value);\n   FC_ASSERT(operation_forked_in, \"Cannot create custom authority for operation which is not valid yet\");\n\n   auto restriction_count = restriction::restriction_count(op.restrictions);\n   FC_ASSERT(restriction_count <= config->max_custom_authority_restrictions,\n             \"Custom authority has more than the maximum number of restrictions\");\n\n   for (const auto& account_weight_pair : op.auth.account_auths)\n      account_weight_pair.first(d);\n\n   const auto& index = d.get_index_type<custom_authority_index>().indices().get<by_account_custom>();\n   auto range = index.equal_range(op.account);\n   FC_ASSERT(std::distance(range.first, range.second) < config->max_custom_authorities_per_account,\n             \"Cannot create custom authority: account already has maximum number\");\n   range = index.equal_range(boost::make_tuple(op.account, op.operation_type));\n   FC_ASSERT(std::distance(range.first, range.second) < config->max_custom_authorities_per_account_op,\n             \"Cannot create custom authority: account already has maximum number for this operation type\");\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((op)) }\n\nobject_id_type custom_authority_create_evaluator::do_apply(const custom_authority_create_operation& op)\n{ try {\n   database& d = db();\n\n   return d.create<custom_authority_object>([&op] (custom_authority_object& obj) mutable {\n      obj.account = op.account;\n      obj.enabled = op.enabled;\n      obj.valid_from = op.valid_from;\n      obj.valid_to = op.valid_to;\n      obj.operation_type = op.operation_type;\n      obj.auth = op.auth;\n      std::for_each(op.restrictions.begin(), op.restrictions.end(), [&obj](const restriction& r) mutable {\n         obj.restrictions.insert(std::make_pair(obj.restriction_counter++, r));\n      });\n   }).id;\n} FC_CAPTURE_AND_RETHROW((op)) }\n\nvoid_result custom_authority_update_evaluator::do_evaluate(const custom_authority_update_operation& op)\n{ try {\n   const database& d = db();\n   auto now = d.head_block_time();\n   old_object = &op.authority_to_update(d);\n   FC_ASSERT(old_object->account == op.account, \"Cannot update a different account's custom authority\");\n\n   if (op.new_enabled)\n      FC_ASSERT(*op.new_enabled != old_object->enabled,\n                \"Custom authority update specifies an enabled flag, but flag is not changed\");\n\n   const auto& config = d.get_global_properties().parameters.extensions.value.custom_authority_options;\n   auto valid_from = old_object->valid_from;\n   auto valid_to = old_object->valid_to;\n   if (op.new_valid_from) {\n      FC_ASSERT(*op.new_valid_from != old_object->valid_from,\n                \"Custom authority update specifies a new valid from date, but date is not changed\");\n      valid_from = *op.new_valid_from;\n   }\n   if (op.new_valid_to) {\n      FC_ASSERT(*op.new_valid_to != old_object->valid_to,\n                \"Custom authority update specifies a new valid to date, but date is not changed\");\n      FC_ASSERT(*op.new_valid_to > now, \"Custom authority expiration must be in the future\");\n      FC_ASSERT((*op.new_valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds,\n                \"Custom authority lifetime exceeds maximum limit\");\n      valid_to = *op.new_valid_to;\n   }\n   FC_ASSERT(valid_from < valid_to, \"Custom authority validity begin date must be before expiration date\");\n\n   if (op.new_auth) {\n      FC_ASSERT(*op.new_auth != old_object->auth,\n                \"Custom authority update specifies a new authentication authority, but authority is not changed\");\n      for (const auto& account_weight_pair : op.new_auth->account_auths)\n         account_weight_pair.first(d);\n   }\n\n   std::for_each(op.restrictions_to_remove.begin(), op.restrictions_to_remove.end(), [this](uint16_t id) {\n      FC_ASSERT(old_object->restrictions.count(id) == 1, \"Cannot remove restriction ID ${I}: ID not found\",\n                (\"I\", id));\n   });\n   if (!op.restrictions_to_add.empty()) {\n      // Sanity check\n      if (!old_object->restrictions.empty())\n         FC_ASSERT((--old_object->restrictions.end())->first < old_object->restriction_counter,\n                   \"LOGIC ERROR: Restriction counter overlaps restrictions. Please report this error.\");\n      FC_ASSERT(old_object->restriction_counter + op.restrictions_to_add.size() > old_object->restriction_counter,\n                \"Unable to add restrictions: causes wraparound of restriction IDs\");\n   }\n\n   // Add up the restriction counts for all old restrictions not being removed, and all new ones\n   size_t restriction_count = 0;\n   for (const auto& restriction_pair : old_object->restrictions)\n      if (op.restrictions_to_remove.count(restriction_pair.first) == 0)\n         restriction_count += restriction_pair.second.restriction_count();\n   restriction_count += restriction::restriction_count(op.restrictions_to_add);\n   // Check restriction count against limit\n   FC_ASSERT(restriction_count <= config->max_custom_authority_restrictions,\n             \"Cannot update custom authority: updated authority would exceed the maximum number of restrictions\");\n\n   get_restriction_predicate(op.restrictions_to_add, old_object->operation_type);\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((op)) }\n\nvoid_result custom_authority_update_evaluator::do_apply(const custom_authority_update_operation& op)\n{ try {\n   database& d = db();\n\n   d.modify(*old_object, [&op](custom_authority_object& obj) {\n      if (op.new_enabled) obj.enabled = *op.new_enabled;\n      if (op.new_valid_from) obj.valid_from = *op.new_valid_from;\n      if (op.new_valid_to) obj.valid_to = *op.new_valid_to;\n      if (op.new_auth) obj.auth = *op.new_auth;\n\n      std::for_each(op.restrictions_to_remove.begin(), op.restrictions_to_remove.end(), [&obj](auto id) mutable {\n         obj.restrictions.erase(id);\n      });\n      std::for_each(op.restrictions_to_add.begin(), op.restrictions_to_add.end(), [&obj](const auto& r) mutable {\n         obj.restrictions.insert(std::make_pair(obj.restriction_counter++, r));\n      });\n\n      // Clear the predicate cache\n      obj.clear_predicate_cache();\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((op)) }\n\nvoid_result custom_authority_delete_evaluator::do_evaluate(const custom_authority_delete_operation& op)\n{ try {\n   const database& d = db();\n\n   old_object = &op.authority_to_delete(d);\n   FC_ASSERT(old_object->account == op.account, \"Cannot delete a different account's custom authority\");\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((op)) }\n\nvoid_result custom_authority_delete_evaluator::do_apply(const custom_authority_delete_operation& op)\n{ try {\n   database& d = db();\n\n   d.remove(*old_object);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW((op)) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/database.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"db_balance.cpp\"\n#include \"db_block.cpp\"\n#include \"db_debug.cpp\"\n#include \"db_genesis.cpp\"\n#include \"db_getter.cpp\"\n#include \"db_init.cpp\"\n#include \"db_maint.cpp\"\n#include \"db_management.cpp\"\n#include \"db_market.cpp\"\n#include \"db_notify.cpp\"\n#include \"db_update.cpp\"\n#include \"db_witness_schedule.cpp\"\n"
  },
  {
    "path": "libraries/chain/db_balance.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <boost/range/algorithm.hpp>\n\nnamespace graphene { namespace chain {\n\nasset database::get_balance(account_id_type owner, asset_id_type asset_id) const\n{\n   auto& index = get_index_type< primary_index< account_balance_index > >().get_secondary_index<balances_by_account_index>();\n   auto abo = index.get_account_balance( owner, asset_id );\n   if( !abo )\n      return asset(0, asset_id);\n   return abo->get_balance();\n}\n\nasset database::get_balance(const account_object& owner, const asset_object& asset_obj) const\n{\n   return get_balance(owner.get_id(), asset_obj.get_id());\n}\n\nstring database::to_pretty_string( const asset& a )const\n{\n   return a.asset_id(*this).amount_to_pretty_string(a.amount);\n}\n\nvoid database::adjust_balance(account_id_type account, asset delta )\n{ try {\n   if( delta.amount == 0 )\n      return;\n\n   auto& index = get_index_type< primary_index< account_balance_index > >().get_secondary_index<balances_by_account_index>();\n   auto abo = index.get_account_balance( account, delta.asset_id );\n   if( !abo )\n   {\n      FC_ASSERT( delta.amount > 0, \"Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}\",\n                 (\"a\",account(*this).name)\n                 (\"b\",to_pretty_string(asset(0,delta.asset_id)))\n                 (\"r\",to_pretty_string(-delta)));\n      create<account_balance_object>([account,&delta](account_balance_object& b) {\n         b.owner = account;\n         b.asset_type = delta.asset_id;\n         b.balance = delta.amount.value;\n         if( b.asset_type == asset_id_type() ) // CORE asset\n            b.maintenance_flag = true;\n      });\n   } else {\n      if( delta.amount < 0 )\n         FC_ASSERT( abo->get_balance() >= -delta, \"Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}\",\n                    (\"a\",account(*this).name)(\"b\",to_pretty_string(abo->get_balance()))(\"r\",to_pretty_string(-delta)));\n      modify(*abo, [delta](account_balance_object& b) {\n         b.adjust_balance(delta);\n      });\n   }\n\n} FC_CAPTURE_AND_RETHROW( (account)(delta) ) }\n\nnamespace detail {\n\n   /**\n    * Used as a key to search vesting_balance_object in the index\n   */\n   struct vbo_mfs_key\n   {\n      account_id_type   account_id;\n      asset_id_type     asset_id;\n\n      vbo_mfs_key(const account_id_type& account, const asset_id_type& asset):\n         account_id(account),\n         asset_id(asset)\n      {}\n\n      bool operator()(const vbo_mfs_key& k, const vesting_balance_object& vbo)const\n      {\n         return ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) &&\n                  ( k.asset_id == vbo.balance.asset_id ) &&\n                  ( k.account_id == vbo.owner );\n      }\n\n      uint64_t operator()(const vbo_mfs_key& k)const\n      {\n         return vbo_mfs_hash(k.account_id, k.asset_id);\n      }\n   };\n} //detail\n\nasset database::get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id)\n{\n   auto& vesting_balances = get_index_type<vesting_balance_index>().indices().get<by_vesting_type>();\n   const auto& key = detail::vbo_mfs_key{account_id, asset_id};\n   auto vbo_it = vesting_balances.find(key, key, key);\n\n   if( vbo_it == vesting_balances.end() )\n   {\n      return asset(0, asset_id);\n   }\n   return vbo_it->balance;\n}\n\nvoid database::deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta)\n{ try {\n   FC_ASSERT( delta.amount >= 0, \"Invalid negative value for balance\");\n\n   if( delta.amount == 0 )\n      return;\n\n   auto& vesting_balances = get_index_type<vesting_balance_index>().indices().get<by_vesting_type>();\n   const auto& key = detail::vbo_mfs_key{account_id, delta.asset_id};\n   auto vbo_it = vesting_balances.find(key, key, key);\n\n   auto block_time = head_block_time();\n\n   if( vbo_it == vesting_balances.end() )\n   {\n      create<vesting_balance_object>([&account_id, &delta](vesting_balance_object &vbo) {\n         vbo.owner = account_id;\n         vbo.balance = delta;\n         vbo.balance_type = vesting_balance_type::market_fee_sharing;\n         vbo.policy = instant_vesting_policy{};\n      });\n   } else {\n      modify( *vbo_it, [&block_time, &delta]( vesting_balance_object& vbo )\n      {\n         vbo.deposit_vested(block_time, delta);\n      });\n   }\n} FC_CAPTURE_AND_RETHROW( (account_id)(delta) ) }\n\noptional< vesting_balance_id_type > database::deposit_lazy_vesting(\n   const optional< vesting_balance_id_type >& ovbid,\n   share_type amount, uint32_t req_vesting_seconds,\n   vesting_balance_type balance_type,\n   account_id_type req_owner,\n   bool require_vesting )\n{\n   if( amount == 0 )\n      return optional< vesting_balance_id_type >();\n\n   fc::time_point_sec now = head_block_time();\n\n   if( ovbid.valid() )\n   {\n      const vesting_balance_object& vbo = (*ovbid)(*this);\n      if( vbo.owner == req_owner && vbo.policy.is_type< cdd_vesting_policy >()\n            && vbo.policy.get< cdd_vesting_policy >().vesting_seconds == req_vesting_seconds )\n      {\n         modify( vbo, [require_vesting, &now, &amount]( vesting_balance_object& _vbo )\n         {\n            if( require_vesting )\n               _vbo.deposit(now, amount);\n            else\n               _vbo.deposit_vested(now, amount);\n         } );\n         return optional< vesting_balance_id_type >();\n      }\n   }\n\n   cdd_vesting_policy policy;\n   policy.vesting_seconds = req_vesting_seconds;\n   policy.coin_seconds_earned = require_vesting ? 0 : amount.value * policy.vesting_seconds;\n   policy.coin_seconds_earned_last_update = now;\n\n   const vesting_balance_object& vbo = create< vesting_balance_object >(\n         [&req_owner, &amount, &balance_type, &policy ]( vesting_balance_object& _vbo )\n   {\n      _vbo.owner = req_owner;\n      _vbo.balance = amount;\n      _vbo.balance_type = balance_type;\n      _vbo.policy = policy;\n   } );\n\n   return vbo.id;\n}\n\nvoid database::deposit_cashback(const account_object& acct, share_type amount, bool require_vesting)\n{\n   // If we don't have a VBO, or if it has the wrong maturity\n   // due to a policy change, cut it loose.\n\n   if( amount == 0 )\n      return;\n\n   account_id_type acct_id = acct.get_id();\n\n   // Note: missing 'PROXY_TO_SELF' here\n   bool is_reserved_account = ( acct_id == GRAPHENE_COMMITTEE_ACCOUNT || acct_id == GRAPHENE_WITNESS_ACCOUNT ||\n                                acct_id == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT );\n   is_reserved_account = ( is_reserved_account || acct_id == GRAPHENE_NULL_ACCOUNT ||\n                           acct_id == GRAPHENE_TEMP_ACCOUNT );\n   if( is_reserved_account )\n   {\n      // The blockchain's accounts do not get cashback; it simply goes to the reserve pool.\n      modify( get_core_dynamic_data(), [amount](asset_dynamic_data_object& d) {\n         d.current_supply -= amount;\n      });\n      return;\n   }\n\n   optional< vesting_balance_id_type > new_vbid = deposit_lazy_vesting(\n      acct.cashback_vb,\n      amount,\n      get_global_properties().parameters.cashback_vesting_period_seconds,\n      vesting_balance_type::cashback,\n      acct_id,\n      require_vesting );\n\n   if( new_vbid.valid() )\n   {\n      modify( acct, [&new_vbid]( account_object& _acct )\n      {\n         _acct.cashback_vb = *new_vbid;\n      } );\n      modify( acct.statistics( *this ), []( account_statistics_object& aso )\n      {\n         aso.has_cashback_vb = true;\n      } );\n   }\n\n   return;\n}\n\nvoid database::deposit_witness_pay(const witness_object& wit, share_type amount)\n{\n   if( amount == 0 )\n      return;\n\n   optional< vesting_balance_id_type > new_vbid = deposit_lazy_vesting(\n      wit.pay_vb,\n      amount,\n      get_global_properties().parameters.witness_pay_vesting_seconds,\n      vesting_balance_type::witness,\n      wit.witness_account,\n      true );\n\n   if( new_vbid.valid() )\n   {\n      modify( wit, [&]( witness_object& _wit )\n      {\n         _wit.pay_vb = *new_vbid;\n      } );\n   }\n\n   return;\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_block.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/db_with.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/block_summary_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/transaction_history_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/thread/parallel.hpp>\n\nnamespace graphene { namespace chain {\n\nbool database::is_known_block( const block_id_type& id )const\n{\n   return _fork_db.is_known_block(id) || _block_id_to_block.contains(id);\n}\n/**\n * Only return true *if* the transaction has not expired or been invalidated. If this\n * method is called with a VERY old transaction we will return false, they should\n * query things by blocks if they are that old.\n */\nbool database::is_known_transaction( const transaction_id_type& id )const\n{\n   const auto& trx_idx = get_index_type<transaction_index>().indices().get<by_trx_id>();\n   return trx_idx.find( id ) != trx_idx.end();\n}\n\nblock_id_type  database::get_block_id_for_num( uint32_t block_num )const\n{ try {\n   return _block_id_to_block.fetch_block_id( block_num );\n} FC_CAPTURE_AND_RETHROW( (block_num) ) } // GCOVR_EXCL_LINE\n\noptional<signed_block> database::fetch_block_by_id( const block_id_type& id )const\n{\n   auto b = _fork_db.fetch_block( id );\n   if( !b )\n      return _block_id_to_block.fetch_optional(id);\n   return b->data;\n}\n\noptional<signed_block> database::fetch_block_by_number( uint32_t num )const\n{\n   auto results = _fork_db.fetch_block_by_number(num);\n   if( results.size() == 1 )\n      return results[0]->data;\n   else\n      return _block_id_to_block.fetch_by_number(num);\n}\n\nconst signed_transaction& database::get_recent_transaction(const transaction_id_type& trx_id) const\n{\n   auto& index = get_index_type<transaction_index>().indices().get<by_trx_id>();\n   auto itr = index.find(trx_id);\n   FC_ASSERT(itr != index.end());\n   return itr->trx;\n}\n\nstd::vector<block_id_type> database::get_block_ids_on_fork(block_id_type head_of_fork) const\n{\n  pair<fork_database::branch_type, fork_database::branch_type> branches\n        = _fork_db.fetch_branch_from(head_block_id(), head_of_fork);\n  if( !((branches.first.back()->previous_id() == branches.second.back()->previous_id())) )\n  {\n     edump( (head_of_fork)\n            (head_block_id())\n            (branches.first.size())\n            (branches.second.size()) );\n     assert(branches.first.back()->previous_id() == branches.second.back()->previous_id());\n  }\n  std::vector<block_id_type> result;\n  for (const item_ptr& fork_block : branches.second)\n    result.emplace_back(fork_block->id);\n  result.emplace_back(branches.first.back()->previous_id());\n  return result;\n}\n\n/**\n * Push block \"may fail\" in which case every partial change is unwound.  After\n * push block is successful the block is appended to the chain database on disk.\n *\n * @return true if we switched forks as a result of this push.\n */\nbool database::push_block(const signed_block& new_block, uint32_t skip)\n{\n//   idump((new_block.block_num())(new_block.id())(new_block.timestamp)(new_block.previous));\n   bool result;\n   detail::with_skip_flags( *this, skip, [&]()\n   {\n      detail::without_pending_transactions( *this, std::move(_pending_tx),\n      [&]()\n      {\n         result = _push_block(new_block);\n      });\n   });\n   return result;\n}\n\nbool database::_push_block(const signed_block& new_block)\n{ try {\n   uint32_t skip = get_node_properties().skip_flags;\n\n   const auto now = fc::time_point::now().sec_since_epoch();\n   if( _fork_db.head() && new_block.timestamp.sec_since_epoch() > now - 86400 )\n   {\n      // verify that the block signer is in the current set of active witnesses.\n      shared_ptr<fork_item> prev_block = _fork_db.fetch_block( new_block.previous );\n      GRAPHENE_ASSERT( prev_block, unlinkable_block_exception, \"block does not link to known chain\" );\n      if( prev_block->scheduled_witnesses && 0 == (skip&(skip_witness_schedule_check|skip_witness_signature)) )\n         verify_signing_witness( new_block, *prev_block );\n   }\n\n   const shared_ptr<fork_item> new_head = _fork_db.push_block(new_block);\n   //If the head block from the longest chain does not build off of the current head, we need to switch forks.\n   if( new_head->data.previous != head_block_id() )\n   {\n      //If the newly pushed block is the same height as head, we get head back in new_head\n      //Only switch forks if new_head is actually higher than head\n      if( new_head->data.block_num() > head_block_num() )\n      {\n         wlog( \"Switching to fork: ${id}\", (\"id\",new_head->data.id()) );\n         auto branches = _fork_db.fetch_branch_from(new_head->data.id(), head_block_id());\n\n         // pop blocks until we hit the forked block\n         while( head_block_id() != branches.second.back()->data.previous )\n         {\n            ilog( \"popping block #${n} ${id}\", (\"n\",head_block_num())(\"id\",head_block_id()) );\n            pop_block();\n         }\n\n         // push all blocks on the new fork\n         for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr )\n         {\n               ilog( \"pushing block from fork #${n} ${id}\", (\"n\",(*ritr)->data.block_num())(\"id\",(*ritr)->id) );\n               optional<fc::exception> except;\n               try {\n                  undo_database::session session = _undo_db.start_undo_session();\n                  apply_block( (*ritr)->data, skip );\n                  update_witnesses( **ritr );\n                  _block_id_to_block.store( (*ritr)->id, (*ritr)->data );\n                  session.commit();\n               }\n               catch ( const fc::exception& e ) { except = e; }\n               if( except )\n               {\n                  wlog( \"exception thrown while switching forks ${e}\", (\"e\",except->to_detail_string() ) );\n                  // remove the rest of branches.first from the fork_db, those blocks are invalid\n                  while( ritr != branches.first.rend() )\n                  {\n                     ilog( \"removing block from fork_db #${n} ${id}\",\n                           (\"n\",(*ritr)->data.block_num())(\"id\",(*ritr)->id) );\n                     _fork_db.remove( (*ritr)->id );\n                     ++ritr;\n                  }\n                  _fork_db.set_head( branches.second.front() );\n\n                  // pop all blocks from the bad fork\n                  while( head_block_id() != branches.second.back()->data.previous )\n                  {\n                     ilog( \"popping block #${n} ${id}\", (\"n\",head_block_num())(\"id\",head_block_id()) );\n                     pop_block();\n                  }\n\n                  ilog( \"Switching back to fork: ${id}\", (\"id\",branches.second.front()->data.id()) );\n                  // restore all blocks from the good fork\n                  for( auto ritr2 = branches.second.rbegin(); ritr2 != branches.second.rend(); ++ritr2 )\n                  {\n                     ilog( \"pushing block #${n} ${id}\", (\"n\",(*ritr2)->data.block_num())(\"id\",(*ritr2)->id) );\n                     auto session = _undo_db.start_undo_session();\n                     apply_block( (*ritr2)->data, skip );\n                     _block_id_to_block.store( (*ritr2)->id, (*ritr2)->data );\n                     session.commit();\n                  }\n                  throw *except;\n               }\n         }\n         return true;\n      }\n      else return false;\n   }\n\n   try {\n      auto session = _undo_db.start_undo_session();\n      apply_block(new_block, skip);\n      if( new_block.timestamp.sec_since_epoch() > now - 86400 )\n         update_witnesses( *new_head );\n      _block_id_to_block.store(new_block.id(), new_block);\n      session.commit();\n   } catch ( const fc::exception& e ) {\n      elog(\"Failed to push new block:\\n${e}\", (\"e\", e.to_detail_string()));\n      _fork_db.remove( new_block.id() );\n      throw;\n   }\n\n   return false;\n} FC_CAPTURE_AND_RETHROW( (new_block) ) } // GCOVR_EXCL_LINE\n\nvoid database::verify_signing_witness( const signed_block& new_block, const fork_item& fork_entry )const\n{\n   FC_ASSERT( new_block.timestamp >= fork_entry.next_block_time );\n   uint32_t slot_num = ( new_block.timestamp - fork_entry.next_block_time ).to_seconds() / block_interval();\n   uint64_t index = ( fork_entry.next_block_aslot + slot_num ) % fork_entry.scheduled_witnesses->size();\n   const auto& scheduled_witness = (*fork_entry.scheduled_witnesses)[index];\n   FC_ASSERT( new_block.witness == scheduled_witness.first, \"Witness produced block at wrong time\",\n              (\"block witness\",new_block.witness)(\"scheduled\",scheduled_witness)(\"slot_num\",slot_num) );\n   FC_ASSERT( new_block.validate_signee( scheduled_witness.second ) );\n}\n\nvoid database::update_witnesses( fork_item& fork_entry )const\n{\n   if( fork_entry.scheduled_witnesses ) return;\n\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n   fork_entry.next_block_aslot = dpo.current_aslot + 1;\n   fork_entry.next_block_time = get_slot_time( 1 );\n\n   const witness_schedule_object& wso = get_witness_schedule_object();\n   fork_entry.scheduled_witnesses = std::make_shared< vector< pair< witness_id_type, public_key_type > > >();\n   fork_entry.scheduled_witnesses->reserve( wso.current_shuffled_witnesses.size() );\n   for( size_t i = 0; i < wso.current_shuffled_witnesses.size(); ++i )\n   {\n       const auto& witness = wso.current_shuffled_witnesses[i](*this);\n       fork_entry.scheduled_witnesses->emplace_back( wso.current_shuffled_witnesses[i], witness.signing_key );\n   }\n}\n\n/**\n * Attempts to push the transaction into the pending queue\n *\n * When called to push a locally generated transaction, set the skip_block_size_check bit on the skip argument. This\n * will allow the transaction to be pushed even if it causes the pending block size to exceed the maximum block size.\n * Although the transaction will probably not propagate further now, as the peers are likely to have their pending\n * queues full as well, it will be kept in the queue to be propagated later when a new block flushes out the pending\n * queues.\n */\nprocessed_transaction database::push_transaction( const precomputable_transaction& trx, uint32_t skip )\n{ try {\n   // see https://github.com/bitshares/bitshares-core/issues/1573\n   FC_ASSERT( fc::raw::pack_size( trx ) < (1024 * 1024), \"Transaction exceeds maximum transaction size.\" );\n   processed_transaction result;\n   detail::with_skip_flags( *this, skip, [&]()\n   {\n      result = _push_transaction( trx );\n   } );\n   return result;\n} FC_CAPTURE_AND_RETHROW( (trx) ) } // GCOVR_EXCL_LINE\n\nprocessed_transaction database::_push_transaction( const precomputable_transaction& trx )\n{\n   // If this is the first transaction pushed after applying a block, start a new undo session.\n   // This allows us to quickly rewind to the clean state of the head block, in case a new block arrives.\n   if( !_pending_tx_session.valid() )\n      _pending_tx_session = _undo_db.start_undo_session();\n\n   // Create a temporary undo session as a child of _pending_tx_session.\n   // The temporary session will be discarded by the destructor if\n   // _apply_transaction fails.  If we make it to merge(), we\n   // apply the changes.\n\n   auto temp_session = _undo_db.start_undo_session();\n   auto processed_trx = _apply_transaction( trx );\n   _pending_tx.push_back(processed_trx);\n\n   // notify_changed_objects();\n   // The transaction applied successfully. Merge its changes into the pending block session.\n   temp_session.merge();\n\n   // notify anyone listening to pending transactions\n   notify_on_pending_transaction( trx );\n   return processed_trx;\n}\n\nprocessed_transaction database::validate_transaction( const signed_transaction& trx )\n{\n   auto session = _undo_db.start_undo_session();\n   return _apply_transaction( trx );\n}\n\nclass undo_session_nesting_guard {\npublic:\n   undo_session_nesting_guard( uint32_t& nesting_counter, const database& db )\n      : orig_value(nesting_counter), counter(nesting_counter)\n   {\n      FC_ASSERT( counter < db.get_global_properties().active_witnesses.size() * 2,\n                 \"Max undo session nesting depth exceeded!\" );\n      ++counter;\n   }\n   ~undo_session_nesting_guard()\n   {\n      --counter;\n      // GCOVR_EXCL_START\n      // Defensive code, should not happen\n      if( counter != orig_value )\n         elog( \"Unexpected undo session nesting count value: ${n} != ${o}\", (\"n\",counter)(\"o\",orig_value) );\n      // GCOVR_EXCL_STOP\n   }\nprivate:\n    const uint32_t  orig_value;\n    uint32_t& counter;\n};\n\nprocessed_transaction database::push_proposal(const proposal_object& proposal)\n{ try {\n   transaction_evaluation_state eval_state(this);\n   eval_state._is_proposed_trx = true;\n\n   eval_state.operation_results.reserve(proposal.proposed_transaction.operations.size());\n   processed_transaction ptrx(proposal.proposed_transaction);\n   eval_state._trx = &ptrx;\n   size_t old_applied_ops_size = _applied_ops.size();\n   auto old_vop = _current_virtual_op;\n\n   try {\n      undo_session_nesting_guard guard( _undo_session_nesting_depth, *this );\n      if( _undo_db.size() >= _undo_db.max_size() )\n         _undo_db.set_max_size( _undo_db.size() + 1 );\n      auto session = _undo_db.start_undo_session(true);\n      for( auto& op : proposal.proposed_transaction.operations )\n         eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); // This is a virtual operation\n      // Make sure there is no unpaid samet fund debt\n      const auto& samet_fund_idx = get_index_type<samet_fund_index>().indices().get<by_unpaid>();\n      FC_ASSERT( samet_fund_idx.empty() || samet_fund_idx.begin()->unpaid_amount == 0,\n                 \"Unpaid SameT Fund debt detected\" );\n      remove(proposal);\n      session.merge();\n   } catch ( const fc::exception& e ) {\n      if( head_block_time() <= HARDFORK_483_TIME )\n      {\n         for( size_t i=old_applied_ops_size,n=_applied_ops.size(); i<n; i++ )\n         {\n            ilog( \"removing failed operation from applied_ops: ${op}\", (\"op\", *(_applied_ops[i])) );\n            _applied_ops[i].reset();\n         }\n      }\n      else\n      {\n         _current_virtual_op = old_vop;\n         _applied_ops.resize( old_applied_ops_size );\n      }\n      wlog( \"${e}\", (\"e\",e.to_detail_string() ) );\n      throw;\n   }\n\n   ptrx.operation_results = std::move(eval_state.operation_results);\n   return ptrx;\n} FC_CAPTURE_AND_RETHROW( (proposal) ) } // GCOVR_EXCL_LINE\n\nsigned_block database::generate_block(\n   fc::time_point_sec when,\n   witness_id_type witness_id,\n   const fc::ecc::private_key& block_signing_private_key,\n   uint32_t skip /* = 0 */\n   )\n{ try {\n   signed_block result;\n   detail::with_skip_flags( *this, skip, [&]()\n   {\n      result = _generate_block( when, witness_id, block_signing_private_key );\n   } );\n   return result;\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nsigned_block database::_generate_block(\n   fc::time_point_sec when,\n   witness_id_type witness_id,\n   const fc::ecc::private_key& block_signing_private_key\n   )\n{\n   try {\n   uint32_t skip = get_node_properties().skip_flags;\n   uint32_t slot_num = get_slot_at_time( when );\n   FC_ASSERT( slot_num > 0 );\n   witness_id_type scheduled_witness = get_scheduled_witness( slot_num );\n   FC_ASSERT( scheduled_witness == witness_id );\n\n   //\n   // The following code throws away existing pending_tx_session and\n   // rebuilds it by re-applying pending transactions.\n   //\n   // This rebuild is necessary because pending transactions' validity\n   // and semantics may have changed since they were received, because\n   // time-based semantics are evaluated based on the current block\n   // time.  These changes can only be reflected in the database when\n   // the value of the \"when\" variable is known, which means we need to\n   // re-apply pending transactions in this method.\n   //\n\n   // pop pending state (reset to head block state)\n   _pending_tx_session.reset();\n\n   // Check witness signing key\n   if( 0 == (skip & skip_witness_signature) )\n   {\n      // Note: if this check failed (which won't happen in normal situations),\n      // we would have temporarily broken the invariant that\n      // _pending_tx_session is the result of applying _pending_tx.\n      // In this case, when the node received a new block,\n      // the push_block() call will re-create the _pending_tx_session.\n      FC_ASSERT( witness_id(*this).signing_key == block_signing_private_key.get_public_key() );\n   }\n\n   static const size_t max_partial_block_header_size = ( fc::raw::pack_size( signed_block_header() )\n                                                       - fc::raw::pack_size( witness_id_type() ) ) // witness_id\n                                                       + 3; // max space to store size of transactions\n                                                            // (out of block header),\n                                                            // +3 means 3*7=21 bits so it's practically safe\n   const size_t max_block_header_size = max_partial_block_header_size + fc::raw::pack_size( witness_id );\n   auto maximum_block_size = get_global_properties().parameters.maximum_block_size;\n   size_t total_block_size = max_block_header_size;\n\n   signed_block pending_block;\n\n   _pending_tx_session = _undo_db.start_undo_session();\n\n   uint64_t postponed_tx_count = 0;\n   for( const processed_transaction& tx : _pending_tx )\n   {\n      size_t new_total_size = total_block_size + fc::raw::pack_size( tx );\n\n      // postpone transaction if it would make block too big\n      if( new_total_size > maximum_block_size )\n      {\n         postponed_tx_count++;\n         continue;\n      }\n\n      try\n      {\n         auto temp_session = _undo_db.start_undo_session();\n         processed_transaction ptx = _apply_transaction( tx );\n         // Clear results to save disk space and network bandwidth.\n         // This may break client applications which rely on the results.\n         ptx.operation_results.clear();\n\n         // We have to recompute pack_size(ptx) because it may be different\n         // than pack_size(tx) (i.e. if one or more results increased\n         // their size)\n         new_total_size = total_block_size + fc::raw::pack_size( ptx );\n         // postpone transaction if it would make block too big\n         if( new_total_size > maximum_block_size )\n         {\n            postponed_tx_count++;\n            continue;\n         }\n\n         temp_session.merge();\n\n         total_block_size = new_total_size;\n         pending_block.transactions.push_back( ptx );\n      }\n      catch ( const fc::exception& e )\n      {\n         // Do nothing, transaction will not be re-applied\n         wlog( \"Transaction was not processed while generating block due to ${e}\", (\"e\", e) );\n         wlog( \"The transaction was ${t}\", (\"t\", tx) );\n      }\n   }\n   if( postponed_tx_count > 0 )\n   {\n      wlog( \"Postponed ${n} transactions due to block size limit\", (\"n\", postponed_tx_count) );\n   }\n\n   _pending_tx_session.reset();\n\n   // We have temporarily broken the invariant that\n   // _pending_tx_session is the result of applying _pending_tx, as\n   // _pending_tx now consists of the set of postponed transactions.\n   // However, the push_block() call below will re-create the\n   // _pending_tx_session.\n\n   pending_block.previous = head_block_id();\n   pending_block.timestamp = when;\n   pending_block.transaction_merkle_root = pending_block.calculate_merkle_root();\n   pending_block.witness = witness_id;\n\n   if( 0 == (skip & skip_witness_signature) )\n      pending_block.sign( block_signing_private_key );\n\n   push_block( pending_block, skip | skip_transaction_signatures ); // skip authority check when pushing\n                                                                    // self-generated blocks\n\n   return pending_block;\n} FC_CAPTURE_AND_RETHROW( (witness_id) ) } // GCOVR_EXCL_LINE\n\n/**\n * Removes the most recent block from the database and\n * undoes any changes it made.\n */\nvoid database::pop_block()\n{ try {\n   _pending_tx_session.reset();\n   auto fork_db_head = _fork_db.head();\n   FC_ASSERT( fork_db_head, \"Trying to pop() from empty fork database!?\" );\n   if( fork_db_head->id == head_block_id() )\n      _fork_db.pop_block();\n   else\n   {\n      fork_db_head = _fork_db.fetch_block( head_block_id() );\n      FC_ASSERT( fork_db_head, \"Trying to pop() block that's not in fork database!?\" );\n   }\n   pop_undo();\n   _popped_tx.insert( _popped_tx.begin(),\n                      fork_db_head->data.transactions.begin(),\n                      fork_db_head->data.transactions.end() );\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid database::clear_pending()\n{ try {\n   assert( (_pending_tx.size() == 0) || _pending_tx_session.valid() );\n   _pending_tx.clear();\n   _pending_tx_session.reset();\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nuint32_t database::push_applied_operation( const operation& op, bool is_virtual /* = true */ )\n{\n   _applied_ops.emplace_back( operation_history_object( op, _current_block_num, _current_trx_in_block,\n                                    _current_op_in_trx, _current_virtual_op, is_virtual, _current_block_time ) );\n   ++_current_virtual_op;\n   return _applied_ops.size() - 1;\n}\nvoid database::set_applied_operation_result( uint32_t op_id, const operation_result& result )\n{\n   assert( op_id < _applied_ops.size() );\n   if( _applied_ops[op_id] )\n      _applied_ops[op_id]->result = result;\n   else\n   {\n      elog( \"Could not set operation result (head_block_num=${b})\", (\"b\", head_block_num()) );\n   }\n}\n\nconst vector<optional< operation_history_object > >& database::get_applied_operations() const\n{\n   return _applied_ops;\n}\n\n//////////////////// private methods ////////////////////\n\nvoid database::apply_block( const signed_block& next_block, uint32_t skip )\n{\n   auto block_num = next_block.block_num();\n   if( !_checkpoints.empty() && _checkpoints.rbegin()->second != block_id_type() )\n   {\n      auto itr = _checkpoints.find( block_num );\n      if( itr != _checkpoints.end() )\n         FC_ASSERT( next_block.id() == itr->second, \"Block did not match checkpoint\", (\"checkpoint\",*itr)(\"block_id\",next_block.id()) );\n\n      if( _checkpoints.rbegin()->first >= block_num )\n         skip = ~0;// WE CAN SKIP ALMOST EVERYTHING\n   }\n\n   detail::with_skip_flags( *this, skip, [&]()\n   {\n      _apply_block( next_block );\n   } );\n   return;\n}\n\nvoid database::_apply_block( const signed_block& next_block )\n{ try {\n   uint32_t next_block_num = next_block.block_num();\n   uint32_t skip = get_node_properties().skip_flags;\n   _applied_ops.clear();\n\n   if( 0 == (skip & skip_block_size_check) )\n   {\n      FC_ASSERT( fc::raw::pack_size(next_block) <= get_global_properties().parameters.maximum_block_size );\n   }\n\n   FC_ASSERT( (skip & skip_merkle_check) || next_block.transaction_merkle_root == next_block.calculate_merkle_root(),\n              \"\",\n              (\"next_block.transaction_merkle_root\",next_block.transaction_merkle_root)\n              (\"calc\",next_block.calculate_merkle_root())\n              (\"next_block\",next_block)\n              (\"id\",next_block.id()) );\n\n   const witness_object& signing_witness = validate_block_header(skip, next_block);\n   const auto& dynamic_global_props = get_dynamic_global_properties();\n   bool maint_needed = (dynamic_global_props.next_maintenance_time <= next_block.timestamp);\n\n   // trx_in_block starts from 0.\n   // For real operations which are explicitly included in a transaction, op_in_trx starts from 0, virtual_op is 0.\n   // For virtual operations that are derived directly from a real operation,\n   //     use the real operation's (block_num,trx_in_block,op_in_trx), virtual_op starts from 1.\n   // For virtual operations created after processed all transactions,\n   //     trx_in_block = the_block.trsanctions.size(), op_in_trx is 0, virtual_op starts from 0.\n   _current_block_num    = next_block_num;\n   _current_trx_in_block = 0;\n\n   _current_block_time   = next_block.timestamp;\n\n   _issue_453_affected_assets.clear();\n\n   signed_block processed_block( next_block ); // make a copy\n   for( auto& trx : processed_block.transactions )\n   {\n      /* We do not need to push the undo state for each transaction\n       * because they either all apply and are valid or the\n       * entire block fails to apply.  We only need an \"undo\" state\n       * for transactions when validating broadcast transactions or\n       * when building a block.\n       */\n      trx.operation_results = apply_transaction( trx, skip ).operation_results;\n      ++_current_trx_in_block;\n   }\n\n   _current_op_in_trx    = 0;\n   _current_virtual_op   = 0;\n\n   const uint32_t missed = update_witness_missed_blocks( next_block );\n   update_global_dynamic_data( next_block, missed );\n   update_signing_witness(signing_witness, next_block);\n   update_last_irreversible_block();\n\n   process_tickets();\n\n   // Are we at the maintenance interval?\n   if( maint_needed )\n      perform_chain_maintenance( next_block );\n\n   create_block_summary(next_block);\n   clear_expired_transactions();\n   clear_expired_proposals();\n   clear_expired_orders();\n   clear_expired_force_settlements();\n   clear_expired_htlcs();\n   update_expired_feeds();       // this will update expired feeds and some core exchange rates\n   update_core_exchange_rates(); // this will update remaining core exchange rates\n   update_withdraw_permissions();\n   update_credit_offers_and_deals();\n\n   // n.b., update_maintenance_flag() happens this late\n   // because get_slot_time() / get_slot_at_time() is needed above\n   // TODO:  figure out if we could collapse this function into\n   // update_global_dynamic_data() as perhaps these methods only need\n   // to be called for header validation?\n   update_maintenance_flag( maint_needed );\n   update_witness_schedule();\n   if( !_node_property_object.debug_updates.empty() )\n      apply_debug_updates();\n\n   // notify observers that the block has been applied\n   notify_applied_block( processed_block ); //emit\n   _applied_ops.clear();\n\n   notify_changed_objects();\n} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) )  } // GCOVR_EXCL_LINE\n\n/**\n * @note if a @c processed_transaction is passed in, it is cast into @c signed_transaction here.\n *       It also means that the @c operation_results field is ignored by consensus, although it\n *       is a part of block data.\n */\nprocessed_transaction database::apply_transaction(const signed_transaction& trx, uint32_t skip)\n{\n   processed_transaction result;\n   detail::with_skip_flags( *this, skip, [&]()\n   {\n      result = _apply_transaction(trx);\n   });\n   return result;\n}\n\nprocessed_transaction database::_apply_transaction(const signed_transaction& trx)\n{ try {\n   uint32_t skip = get_node_properties().skip_flags;\n\n   trx.validate();\n\n   auto& trx_idx = get_mutable_index_type<transaction_index>();\n   const chain_id_type& chain_id = get_chain_id();\n   if( 0 == (skip & skip_transaction_dupe_check) )\n   {\n      GRAPHENE_ASSERT( trx_idx.indices().get<by_trx_id>().find(trx.id()) == trx_idx.indices().get<by_trx_id>().end(),\n                       duplicate_transaction,\n                       \"Transaction '${txid}' is already in the database\",\n                       (\"txid\",trx.id()) );\n   }\n   transaction_evaluation_state eval_state(this);\n   const chain_parameters& chain_parameters = get_global_properties().parameters;\n   eval_state._trx = &trx;\n\n   if( 0 == (skip & skip_transaction_signatures) )\n   {\n      bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME );\n      auto get_active = [this]( account_id_type id ) { return &id(*this).active; };\n      auto get_owner  = [this]( account_id_type id ) { return &id(*this).owner;  };\n      auto get_custom = [this]( account_id_type id, const operation& op, rejected_predicate_map* rejects ) {\n         return get_viable_custom_authorities(id, op, rejects);\n      };\n\n      trx.verify_authority(chain_id, get_active, get_owner, get_custom, allow_non_immediate_owner,\n                           MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(head_block_time()),\n                           get_global_properties().parameters.max_authority_depth);\n   }\n\n   //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is\n   //expired, and TaPoS makes no sense as no blocks exist.\n   if( BOOST_LIKELY(head_block_num() > 0) )\n   {\n      if( 0 == (skip & skip_tapos_check) )\n      {\n         const auto& tapos_block_summary = block_summary_id_type( trx.ref_block_num )(*this);\n\n         //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration\n         FC_ASSERT( trx.ref_block_prefix == tapos_block_summary.block_id._hash[1].value() );\n      }\n\n      fc::time_point_sec now = head_block_time();\n\n      FC_ASSERT( trx.expiration <= now + chain_parameters.maximum_time_until_expiration, \"\",\n                 (\"trx.expiration\",trx.expiration)(\"now\",now)(\"max_til_exp\",chain_parameters.maximum_time_until_expiration));\n      FC_ASSERT( now <= trx.expiration, \"\", (\"now\",now)(\"trx.exp\",trx.expiration) );\n      if ( 0 == (skip & skip_block_size_check ) ) // don't waste time on replay\n         FC_ASSERT( head_block_time() <= HARDFORK_CORE_1573_TIME\n               || trx.get_packed_size() <= chain_parameters.maximum_transaction_size,\n               \"Transaction exceeds maximum transaction size.\" );\n   }\n\n   //Insert transaction into unique transactions database.\n   if( 0 == (skip & skip_transaction_dupe_check) )\n   {\n      create<transaction_history_object>([&trx](transaction_history_object& transaction) {\n         transaction.trx_id = trx.id();\n         transaction.trx = trx;\n      });\n   }\n\n   eval_state.operation_results.reserve(trx.operations.size());\n\n   //Finally process the operations\n   processed_transaction ptrx(trx);\n   _current_op_in_trx = 0;\n   for( const auto& op : ptrx.operations )\n   {\n      _current_virtual_op = 0;\n      eval_state.operation_results.emplace_back(apply_operation(eval_state, op, false)); // This is NOT a virtual op\n      ++_current_op_in_trx;\n   }\n   ptrx.operation_results = std::move(eval_state.operation_results);\n\n   // Make sure there is no unpaid samet fund debt\n   const auto& samet_fund_idx = get_index_type<samet_fund_index>().indices().get<by_unpaid>();\n   FC_ASSERT( samet_fund_idx.empty() || samet_fund_idx.begin()->unpaid_amount == 0,\n              \"Unpaid SameT Fund debt detected\" );\n\n   return ptrx;\n} FC_CAPTURE_AND_RETHROW( (trx) ) } // GCOVR_EXCL_LINE\n\noperation_result database::apply_operation( transaction_evaluation_state& eval_state, const operation& op,\n                                            bool is_virtual /* = true */ )\n{ try {\n   int i_which = op.which();\n   uint64_t u_which = uint64_t( i_which );\n   FC_ASSERT( i_which >= 0, \"Negative operation tag in operation ${op}\", (\"op\",op) );\n   FC_ASSERT( u_which < _operation_evaluators.size(), \"No registered evaluator for operation ${op}\", (\"op\",op) );\n   unique_ptr<op_evaluator>& eval = _operation_evaluators[ u_which ];\n   FC_ASSERT( eval, \"No registered evaluator for operation ${op}\", (\"op\",op) );\n   auto op_id = push_applied_operation( op, is_virtual );\n   auto result = eval->evaluate( eval_state, op, true );\n   set_applied_operation_result( op_id, result );\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\noperation_result database::try_push_virtual_operation( transaction_evaluation_state& eval_state, const operation& op )\n{\n   operation_validate( op );\n\n   // Note: these variables could be updated during the apply_operation() call\n   size_t old_applied_ops_size = _applied_ops.size();\n   auto old_vop = _current_virtual_op;\n\n   try\n   {\n      undo_session_nesting_guard guard( _undo_session_nesting_depth, *this );\n      if( _undo_db.size() >= _undo_db.max_size() )\n         _undo_db.set_max_size( _undo_db.size() + 1 );\n      auto temp_session = _undo_db.start_undo_session(true);\n      auto result = apply_operation( eval_state, op ); // This is a virtual operation\n      temp_session.merge();\n      return result;\n   }\n   catch( const fc::exception& e )\n   {\n      wlog( \"Failed to push virtual operation ${op} at block ${n}; exception was ${e}\",\n            (\"op\", op)(\"n\", head_block_num())(\"e\", e.to_detail_string()) );\n      _current_virtual_op = old_vop;\n      _applied_ops.resize( old_applied_ops_size );\n      throw;\n   }\n}\n\nconst witness_object& database::validate_block_header( uint32_t skip, const signed_block& next_block )const\n{\n   FC_ASSERT( head_block_id() == next_block.previous, \"\", (\"head_block_id\",head_block_id())(\"next.prev\",next_block.previous) );\n   FC_ASSERT( head_block_time() < next_block.timestamp, \"\", (\"head_block_time\",head_block_time())(\"next\",next_block.timestamp)(\"blocknum\",next_block.block_num()) );\n   const witness_object& witness = next_block.witness(*this);\n\n   if( 0 == (skip&skip_witness_signature) )\n      FC_ASSERT( next_block.validate_signee( witness.signing_key ) );\n\n   if( 0 == (skip&skip_witness_schedule_check) )\n   {\n      uint32_t slot_num = get_slot_at_time( next_block.timestamp );\n      FC_ASSERT( slot_num > 0 );\n\n      witness_id_type scheduled_witness = get_scheduled_witness( slot_num );\n\n      FC_ASSERT( next_block.witness == scheduled_witness, \"Witness produced block at wrong time\",\n                 (\"block witness\",next_block.witness)(\"scheduled\",scheduled_witness)(\"slot_num\",slot_num) );\n   }\n\n   return witness;\n}\n\nvoid database::create_block_summary(const signed_block& next_block)\n{\n   block_summary_id_type sid(next_block.block_num() & 0xffff );\n   modify( sid(*this), [&](block_summary_object& p) {\n         p.block_id = next_block.id();\n   });\n}\n\nvoid database::add_checkpoints( const flat_map<uint32_t,block_id_type>& checkpts )\n{\n   for( const auto& i : checkpts )\n      _checkpoints[i.first] = i.second;\n}\n\nbool database::before_last_checkpoint()const\n{\n   return (_checkpoints.size() > 0) && (_checkpoints.rbegin()->first >= head_block_num());\n}\n\n\nstatic const uint32_t skip_expensive = database::skip_transaction_signatures | database::skip_witness_signature\n                                       | database::skip_merkle_check | database::skip_transaction_dupe_check;\n\ntemplate<typename Trx>\nvoid database::_precompute_parallel( const Trx* trx, const size_t count, const uint32_t skip )const\n{\n   for( size_t i = 0; i < count; ++i, ++trx )\n   {\n      trx->validate(); // TODO - parallelize wrt confidential operations\n      if( 0 == (skip & skip_block_size_check) )\n         trx->get_packed_size();\n      if( 0 == (skip&skip_transaction_dupe_check) )\n         trx->id();\n      if( 0 == (skip&skip_transaction_signatures) )\n         trx->get_signature_keys( get_chain_id() );\n   }\n}\n\nfc::future<void> database::precompute_parallel( const signed_block& block, const uint32_t skip )const\n{ try {\n   std::vector<fc::future<void>> workers;\n   if( !block.transactions.empty() )\n   {\n      if( (skip & skip_expensive) == skip_expensive )\n         _precompute_parallel( &block.transactions[0], block.transactions.size(), skip );\n      else\n      {\n         uint32_t chunks = fc::asio::default_io_service_scope::get_num_threads();\n         uint32_t chunk_size = ( block.transactions.size() + chunks - 1 ) / chunks;\n         workers.reserve( chunks + 1 );\n         for( size_t base = 0; base < block.transactions.size(); base += chunk_size )\n            workers.push_back( fc::do_parallel( [this,&block,base,chunk_size,skip] () {\n               _precompute_parallel( &block.transactions[base],\n                                     ( ( base + chunk_size ) < block.transactions.size() ) ? chunk_size\n                                                 : ( block.transactions.size() - base ),\n                                     skip );\n            }) );\n      }\n   }\n\n   if( 0 == (skip&skip_witness_signature) )\n      workers.push_back( fc::do_parallel( [&block] () { block.signee(); } ) );\n   if( 0 == (skip&skip_merkle_check) )\n      block.calculate_merkle_root();\n   block.id();\n\n   if( workers.empty() )\n      return fc::future< void >( fc::promise< void >::create( true ) );\n\n   auto first = workers.begin();\n   auto worker = first;\n   while( ++worker != workers.end() )\n      worker->wait();\n   return *first;\n} FC_LOG_AND_RETHROW() }\n\nfc::future<void> database::precompute_parallel( const precomputable_transaction& trx )const\n{\n   return fc::do_parallel([this,&trx] () {\n      _precompute_parallel( &trx, 1, skip_nothing );\n   });\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_debug.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n *  This method dumps the state of the blockchain in a semi-human readable form for the\n *  purpose of tracking down funds and mismatches in currency allocation\n */\nvoid database::debug_dump()\n{\n   const auto& db = *this;\n   const asset_dynamic_data_object& core_asset_data = db.get_core_asset().dynamic_asset_data_id(db);\n\n   const auto& balance_index = db.get_index_type<account_balance_index>().indices();\n   const auto& statistics_index = db.get_index_type<account_stats_index>().indices();\n   const auto& bids = db.get_index_type<collateral_bid_index>().indices();\n   const auto& settle_index = db.get_index_type<force_settlement_index>().indices();\n   const auto& htlcs = db.get_index_type<htlc_index>().indices();\n   map<asset_id_type,share_type> total_balances;\n   map<asset_id_type,share_type> total_debts;\n   share_type core_in_orders;\n   share_type reported_core_in_orders;\n\n   for( const account_balance_object& a : balance_index )\n   {\n    //  idump((\"balance\")(a));\n      total_balances[a.asset_type] += a.balance;\n   }\n   for( const force_settlement_object& s : settle_index )\n   {\n      total_balances[s.balance.asset_id] += s.balance.amount;\n   }\n   for( const vesting_balance_object& vbo : db.get_index_type< vesting_balance_index >().indices() )\n      total_balances[ vbo.balance.asset_id ] += vbo.balance.amount;\n   for( const fba_accumulator_object& fba : db.get_index_type< simple_index< fba_accumulator_object > >() )\n      total_balances[ asset_id_type() ] += fba.accumulated_fba_fees;\n   for( const account_statistics_object& s : statistics_index )\n   {\n    //  idump((\"statistics\")(s));\n      reported_core_in_orders += s.total_core_in_orders;\n   }\n   for( const collateral_bid_object& b : bids )\n      total_balances[b.inv_swan_price.base.asset_id] += b.inv_swan_price.base.amount;\n   for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )\n   {\n //     idump((\"limit_order\")(o));\n      auto for_sale = o.amount_for_sale();\n      if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount;\n      total_balances[for_sale.asset_id] += for_sale.amount;\n   }\n   for( const call_order_object& o : db.get_index_type<call_order_index>().indices() )\n   {\n//      idump((\"call_order\")(o));\n      auto col = o.get_collateral();\n      if( col.asset_id == asset_id_type() ) core_in_orders += col.amount;\n      total_balances[col.asset_id] += col.amount;\n      total_debts[o.get_debt().asset_id] += o.get_debt().amount;\n   }\n   for( const asset_object& asset_obj : db.get_index_type<asset_index>().indices() )\n   {\n      total_balances[asset_obj.get_id()] += asset_obj.dynamic_asset_data_id(db).accumulated_fees;\n      total_balances[asset_id_type()] += asset_obj.dynamic_asset_data_id(db).fee_pool;\n//      edump((total_balances[asset_obj.id])(asset_obj.dynamic_asset_data_id(db).current_supply ) );\n   }\n   for( const auto& htlc : htlcs )\n      total_balances[htlc.transfer.asset_id] += htlc.transfer.amount;\n\n   if( total_balances[asset_id_type()].value != core_asset_data.current_supply.value )\n   {\n      FC_THROW( \"computed balance of CORE mismatch\",\n                (\"computed value\",total_balances[asset_id_type()].value)\n                (\"current supply\",core_asset_data.current_supply.value) );\n   }\n\n\n   /*\n   const auto& vbidx = db.get_index_type<simple_index<vesting_balance_object>>();\n   for( const auto& s : vbidx )\n   {\n//      idump((\"vesting_balance\")(s));\n   }\n   */\n}\n\nvoid debug_apply_update( database& db, const fc::variant_object& vo )\n{\n   constexpr uint8_t db_action_nil = 0;\n   constexpr uint8_t db_action_create = 1;\n   constexpr uint8_t db_action_write = 2;\n   constexpr uint8_t db_action_update = 3;\n   constexpr uint8_t db_action_delete = 4;\n\n   // \"_action\" : \"create\"   object must not exist, unspecified fields take defaults\n   // \"_action\" : \"write\"    object may exist, is replaced entirely, unspecified fields take defaults\n   // \"_action\" : \"update\"   object must exist, unspecified fields don't change\n   // \"_action\" : \"delete\"   object must exist, will be deleted\n\n   // if _action is unspecified:\n   // - delete if object contains only ID field\n   // - otherwise, write\n\n   object_id_type oid;\n   uint8_t action = db_action_nil;\n   auto it_id = vo.find(\"id\");\n   FC_ASSERT( it_id != vo.end() );\n\n   from_variant( it_id->value(), oid );\n   action = ( vo.size() == 1 ) ? db_action_delete : db_action_write;\n\n   from_variant( vo[\"id\"], oid );\n   if( vo.size() == 1 )\n      action = db_action_delete;\n   auto it_action = vo.find(\"_action\" );\n   if( it_action != vo.end() )\n   {\n      const std::string& str_action = it_action->value().get_string();\n      if( str_action == \"create\" )\n         action = db_action_create;\n      else if( str_action == \"write\" )\n         action = db_action_write;\n      else if( str_action == \"update\" )\n         action = db_action_update;\n      else if( str_action == \"delete\" )\n         action = db_action_delete;\n   }\n\n   auto& idx = db.get_index( oid );\n\n   switch( action )\n   {\n      case db_action_create:\n         FC_ASSERT( false );\n         break;\n      case db_action_write:\n         db.modify( db.get_object( oid ), [&]( object& obj )\n         {\n            idx.object_default( obj );\n            idx.object_from_variant( vo, obj, GRAPHENE_MAX_NESTED_OBJECTS );\n         } );\n         break;\n      case db_action_update:\n         db.modify( db.get_object( oid ), [&]( object& obj )\n         {\n            idx.object_from_variant( vo, obj, GRAPHENE_MAX_NESTED_OBJECTS );\n         } );\n         break;\n      case db_action_delete:\n         db.remove( db.get_object( oid ) );\n         break;\n      default:\n         FC_ASSERT( false );\n   }\n}\n\nvoid database::apply_debug_updates()\n{\n   block_id_type head_id = head_block_id();\n   auto it = _node_property_object.debug_updates.find( head_id );\n   if( it == _node_property_object.debug_updates.end() )\n      return;\n   for( const fc::variant_object& update : it->second )\n      debug_apply_update( *this, update );\n}\n\nvoid database::debug_update( const fc::variant_object& update )\n{\n   block_id_type head_id = head_block_id();\n   auto it = _node_property_object.debug_updates.find( head_id );\n   if( it == _node_property_object.debug_updates.end() )\n      it = _node_property_object.debug_updates.emplace( head_id, std::vector< fc::variant_object >() ).first;\n   it->second.emplace_back( update );\n\n   optional<signed_block> head_block = fetch_block_by_id( head_id );\n   FC_ASSERT( head_block.valid() );\n\n   // What the last block does has been changed by adding to node_property_object, so we have to re-apply it\n   pop_block();\n   push_block( *head_block );\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_genesis.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/fba_accumulator_id.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/block_summary_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include <boost/algorithm/string.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid database::init_genesis(const genesis_state_type& genesis_state)\n{ try {\n   FC_ASSERT( genesis_state.initial_timestamp != time_point_sec(), \"Must initialize genesis timestamp.\" );\n   FC_ASSERT( genesis_state.initial_timestamp.sec_since_epoch() % GRAPHENE_DEFAULT_BLOCK_INTERVAL == 0,\n              \"Genesis timestamp must be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL.\" );\n   FC_ASSERT(genesis_state.initial_witness_candidates.size() > 0,\n             \"Cannot start a chain with zero witnesses.\");\n   FC_ASSERT(genesis_state.initial_active_witnesses <= genesis_state.initial_witness_candidates.size(),\n             \"initial_active_witnesses is larger than the number of candidate witnesses.\");\n\n   _undo_db.disable();\n   struct auth_inhibitor {\n      explicit auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags)\n      { db.node_properties().skip_flags |= skip_transaction_signatures; }\n      ~auth_inhibitor()\n      { db.node_properties().skip_flags = old_flags; }\n      auth_inhibitor(const auth_inhibitor&) = delete;\n   private:\n      database& db;\n      uint32_t old_flags;\n   };\n   auth_inhibitor inhibitor(*this);\n\n   transaction_evaluation_state genesis_eval_state(this);\n\n   _current_block_time = genesis_state.initial_timestamp;\n\n   // Create blockchain accounts\n   fc::ecc::private_key null_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")));\n   create<account_balance_object>([](account_balance_object& b) {\n      b.balance = GRAPHENE_MAX_SHARE_SUPPLY;\n   });\n   const account_object& committee_account =\n      create<account_object>( [this](account_object& n) {\n         n.membership_expiration_date = time_point_sec::maximum();\n         n.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n         n.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n         n.owner.weight_threshold = 1;\n         n.active.weight_threshold = 1;\n         n.name = \"committee-account\";\n         n.statistics = create<account_statistics_object>( [&n](account_statistics_object& s){\n                           s.owner = n.id;\n                           s.name = n.name;\n                           s.core_in_balance = GRAPHENE_MAX_SHARE_SUPPLY;\n                        }).id;\n         n.creation_block_num = 0;\n         n.creation_time = _current_block_time;\n      });\n   FC_ASSERT(committee_account.get_id() == GRAPHENE_COMMITTEE_ACCOUNT);\n   FC_ASSERT(create<account_object>([this](account_object& a) {\n       a.name = \"witness-account\";\n       a.statistics = create<account_statistics_object>([&a](account_statistics_object& s){\n                         s.owner = a.id;\n                         s.name = a.name;\n                      }).id;\n       a.owner.weight_threshold = 1;\n       a.active.weight_threshold = 1;\n       a.registrar = GRAPHENE_WITNESS_ACCOUNT;\n       a.referrer = a.registrar;\n       a.lifetime_referrer = a.registrar;\n       a.membership_expiration_date = time_point_sec::maximum();\n       a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n       a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n       a.creation_block_num = 0;\n       a.creation_time = _current_block_time;\n   }).get_id() == GRAPHENE_WITNESS_ACCOUNT);\n   FC_ASSERT(create<account_object>([this](account_object& a) {\n       a.name = \"relaxed-committee-account\";\n       a.statistics = create<account_statistics_object>([&a](account_statistics_object& s){\n                         s.owner = a.id;\n                         s.name = a.name;\n                      }).id;\n       a.owner.weight_threshold = 1;\n       a.active.weight_threshold = 1;\n       a.registrar = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT;\n       a.referrer = a.registrar;\n       a.lifetime_referrer = a.registrar;\n       a.membership_expiration_date = time_point_sec::maximum();\n       a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n       a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n       a.creation_block_num = 0;\n       a.creation_time = _current_block_time;\n   }).get_id() == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT);\n   // The same data set is assigned to more than one account\n   auto init_account_data_as_null = [this](account_object& a) {\n       a.statistics = create<account_statistics_object>([&a](account_statistics_object& s){\n                         s.owner = a.id;\n                         s.name = a.name;\n                      }).id;\n       a.owner.weight_threshold = 1;\n       a.active.weight_threshold = 1;\n       a.registrar = GRAPHENE_NULL_ACCOUNT;\n       a.referrer = a.registrar;\n       a.lifetime_referrer = a.registrar;\n       a.membership_expiration_date = time_point_sec::maximum();\n       a.network_fee_percentage = 0;\n       a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;\n       a.creation_block_num = 0;\n       a.creation_time = _current_block_time;\n   };\n   FC_ASSERT(create<account_object>([&init_account_data_as_null](account_object& a) {\n       a.name = \"null-account\";\n       init_account_data_as_null(a);\n   }).get_id() == GRAPHENE_NULL_ACCOUNT);\n   FC_ASSERT(create<account_object>([this](account_object& a) {\n       a.name = \"temp-account\";\n       a.statistics = create<account_statistics_object>([&a](account_statistics_object& s){\n                         s.owner = a.id;\n                         s.name = a.name;\n                      }).id;\n       a.owner.weight_threshold = 0;\n       a.active.weight_threshold = 0;\n       a.registrar = GRAPHENE_TEMP_ACCOUNT;\n       a.referrer = a.registrar;\n       a.lifetime_referrer = a.registrar;\n       a.membership_expiration_date = time_point_sec::maximum();\n       a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n       a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n       a.creation_block_num = 0;\n       a.creation_time = _current_block_time;\n   }).get_id() == GRAPHENE_TEMP_ACCOUNT);\n   FC_ASSERT(create<account_object>([&init_account_data_as_null](account_object& a) {\n       a.name = \"proxy-to-self\";\n       init_account_data_as_null(a);\n   }).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT);\n\n   // Create more special accounts and remove them, reserve the IDs\n   while( true )\n   {\n      uint64_t id = get_index<account_object>().get_next_id().instance();\n      if( id >= genesis_state.immutable_parameters.num_special_accounts )\n         break;\n      const account_object& acct = create<account_object>([this,id](account_object& a) {\n          a.name = \"special-account-\" + std::to_string(id);\n          a.statistics = create<account_statistics_object>([&a](account_statistics_object& s){\n                            s.owner = a.id;\n                            s.name = a.name;\n                         }).id;\n          a.owner.weight_threshold = 1;\n          a.active.weight_threshold = 1;\n          a.registrar = account_id_type(id);\n          a.referrer = a.registrar;\n          a.lifetime_referrer = a.registrar;\n          a.membership_expiration_date = time_point_sec::maximum();\n          a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n          a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n          a.creation_block_num = 0;\n          a.creation_time = _current_block_time;\n      });\n      FC_ASSERT( acct.get_id() == account_id_type(id) );\n      remove( acct.statistics(*this) );\n      remove( acct );\n   }\n\n   // Create core asset\n   const asset_dynamic_data_object& core_dyn_asset =\n      create<asset_dynamic_data_object>([](asset_dynamic_data_object& a) {\n         a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      });\n   const asset_object& core_asset =\n     create<asset_object>( [&genesis_state,&core_dyn_asset,this]( asset_object& a ) {\n         a.symbol = GRAPHENE_SYMBOL;\n         a.options.max_supply = genesis_state.max_core_supply;\n         a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;\n         a.options.flags = 0;\n         a.options.issuer_permissions = 0;\n         a.issuer = GRAPHENE_NULL_ACCOUNT;\n         a.options.core_exchange_rate.base.amount = 1;\n         a.options.core_exchange_rate.base.asset_id = asset_id_type(0);\n         a.options.core_exchange_rate.quote.amount = 1;\n         a.options.core_exchange_rate.quote.asset_id = asset_id_type(0);\n         a.dynamic_asset_data_id = core_dyn_asset.id;\n         a.creation_block_num = 0;\n         a.creation_time = _current_block_time;\n      });\n   FC_ASSERT( core_dyn_asset.id == asset_dynamic_data_id_type() );\n   FC_ASSERT( asset_id_type(core_asset.id) == asset().asset_id );\n   FC_ASSERT( get_balance(account_id_type(), asset_id_type()) == asset(core_dyn_asset.current_supply) );\n   _p_core_asset_obj = &core_asset;\n   _p_core_dynamic_data_obj = &core_dyn_asset;\n   // Create more special assets and remove them, reserve the IDs\n   while( true )\n   {\n      uint64_t id = get_index<asset_object>().get_next_id().instance();\n      if( id >= genesis_state.immutable_parameters.num_special_assets )\n         break;\n      const asset_dynamic_data_object& dyn_asset =\n         create<asset_dynamic_data_object>([](asset_dynamic_data_object& a) {\n            a.current_supply = 0;\n         });\n      const asset_object& asset_obj = create<asset_object>( [id,&dyn_asset,this]( asset_object& a ) {\n         a.symbol = \"SPECIAL\" + std::to_string( id );\n         a.options.max_supply = 0;\n         a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;\n         a.options.flags = 0;\n         a.options.issuer_permissions = 0;\n         a.issuer = GRAPHENE_NULL_ACCOUNT;\n         a.options.core_exchange_rate.base.amount = 1;\n         a.options.core_exchange_rate.base.asset_id = asset_id_type(0);\n         a.options.core_exchange_rate.quote.amount = 1;\n         a.options.core_exchange_rate.quote.asset_id = asset_id_type(0);\n         a.dynamic_asset_data_id = dyn_asset.id;\n         a.creation_block_num = 0;\n         a.creation_time = _current_block_time;\n      });\n      FC_ASSERT( asset_obj.get_id() == asset_id_type(id) );\n      remove( dyn_asset );\n      remove( asset_obj );\n   }\n\n   chain_id_type chain_id = genesis_state.compute_chain_id();\n\n   // Create global properties\n   _p_global_prop_obj = & create<global_property_object>([&genesis_state](global_property_object& p) {\n       p.parameters = genesis_state.initial_parameters;\n       // Set fees to zero initially, so that genesis initialization needs not pay them\n       // We'll fix it at the end of the function\n       p.parameters.get_mutable_fees().zero_all_fees();\n\n   });\n   _p_dyn_global_prop_obj = & create<dynamic_global_property_object>(\n                                 [&genesis_state](dynamic_global_property_object& p) {\n      p.time = genesis_state.initial_timestamp;\n      p.dynamic_flags = 0;\n      p.witness_budget = 0;\n      p.recent_slots_filled = std::numeric_limits<fc::uint128_t>::max();\n   });\n\n   FC_ASSERT( (genesis_state.immutable_parameters.min_witness_count & 1) == 1, \"min_witness_count must be odd\" );\n   FC_ASSERT( (genesis_state.immutable_parameters.min_committee_member_count & 1) == 1,\n              \"min_committee_member_count must be odd\" );\n\n   _p_chain_property_obj = & create<chain_property_object>([chain_id,&genesis_state](chain_property_object& p)\n   {\n      p.chain_id = chain_id;\n      p.immutable_parameters = genesis_state.immutable_parameters;\n   } );\n\n   constexpr uint32_t block_summary_object_count = 0x10000;\n   for (uint32_t i = 0; i <= block_summary_object_count; ++i)\n   {\n      create<block_summary_object>( [](const block_summary_object&) {\n         // Nothing to do\n      } );\n   }\n\n   // Create initial accounts\n   for( const auto& account : genesis_state.initial_accounts )\n   {\n      account_create_operation cop;\n      cop.name = account.name;\n      cop.registrar = GRAPHENE_TEMP_ACCOUNT;\n      cop.owner = authority(1, account.owner_key, 1);\n      if( account.active_key == public_key_type() )\n      {\n         cop.active = cop.owner;\n         cop.options.memo_key = account.owner_key;\n      }\n      else\n      {\n         cop.active = authority(1, account.active_key, 1);\n         cop.options.memo_key = account.active_key;\n      }\n      account_id_type account_id(apply_operation(genesis_eval_state, cop).get<object_id_type>());\n\n      if( account.is_lifetime_member )\n      {\n          account_upgrade_operation op;\n          op.account_to_upgrade = account_id;\n          op.upgrade_to_lifetime_member = true;\n          apply_operation(genesis_eval_state, op);\n      }\n   }\n\n   // Helper function to get account ID by name\n   const auto& accounts_by_name = get_index_type<account_index>().indices().get<by_name>();\n   auto get_account_id = [&accounts_by_name](const string& name) {\n      auto itr = accounts_by_name.find(name);\n      FC_ASSERT(itr != accounts_by_name.end(),\n                \"Unable to find account '${acct}'. Did you forget to add a record for it to initial_accounts?\",\n                (\"acct\", name));\n      return itr->get_id();\n   };\n\n   // Helper function to get asset ID by symbol\n   const auto& assets_by_symbol = get_index_type<asset_index>().indices().get<by_symbol>();\n   const auto get_asset_id = [&assets_by_symbol](const string& symbol) {\n      auto itr = assets_by_symbol.find(symbol);\n      FC_ASSERT(itr != assets_by_symbol.end(),\n                \"Unable to find asset '${sym}'. Did you forget to add a record for it to initial_assets?\",\n                (\"sym\", symbol));\n      return itr->get_id();\n   };\n\n   map<asset_id_type, share_type> total_supplies;\n   map<asset_id_type, share_type> total_debts;\n\n   // Create initial assets\n   for( const genesis_state_type::initial_asset_type& asst : genesis_state.initial_assets )\n   {\n      asset_id_type new_asset_id { get_index_type<asset_index>().get_next_id() };\n      total_supplies[ new_asset_id ] = 0;\n\n      asset_dynamic_data_id_type dynamic_data_id;\n      optional<asset_bitasset_data_id_type> bitasset_data_id;\n      if( asst.is_bitasset )\n      {\n         size_t collateral_holder_number = 0;\n         total_debts[ new_asset_id ] = 0;\n         for( const auto& collateral_rec : asst.collateral_records )\n         {\n            account_create_operation cop;\n            cop.name = asst.symbol + \"-collateral-holder-\" + std::to_string(collateral_holder_number);\n            boost::algorithm::to_lower(cop.name);\n            cop.registrar = GRAPHENE_TEMP_ACCOUNT;\n            cop.owner = authority(1, collateral_rec.owner, 1);\n            cop.active = cop.owner;\n            account_id_type owner_account_id { apply_operation(genesis_eval_state, cop).get<object_id_type>() };\n\n            modify( owner_account_id(*this).statistics(*this), [&collateral_rec]( account_statistics_object& o ) {\n               o.total_core_in_orders = collateral_rec.collateral;\n            });\n\n            create<call_order_object>(\n                     [&owner_account_id,&collateral_rec,&new_asset_id,&core_asset](call_order_object& c) {\n               c.borrower = owner_account_id;\n               c.collateral = collateral_rec.collateral;\n               c.debt = collateral_rec.debt;\n               c.call_price = price::call_price(chain::asset(c.debt, new_asset_id),\n                                                chain::asset(c.collateral, core_asset.get_id()),\n                                                GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n            });\n\n            total_supplies[ asset_id_type(0) ] += collateral_rec.collateral;\n            total_debts[ new_asset_id ] += collateral_rec.debt;\n            ++collateral_holder_number;\n         }\n\n         bitasset_data_id = create<asset_bitasset_data_object>([&core_asset,new_asset_id](asset_bitasset_data_object& b) {\n            b.options.short_backing_asset = core_asset.id;\n            b.options.minimum_feeds = GRAPHENE_DEFAULT_MINIMUM_FEEDS;\n            b.asset_id = new_asset_id;\n         }).id;\n      }\n\n      dynamic_data_id = create<asset_dynamic_data_object>([&asst](asset_dynamic_data_object& d) {\n         d.accumulated_fees = asst.accumulated_fees;\n      }).id;\n\n      total_supplies[ new_asset_id ] += asst.accumulated_fees;\n\n      create<asset_object>([&asst,&get_account_id,&dynamic_data_id,&bitasset_data_id,this](asset_object& a)\n      {\n         a.symbol = asst.symbol;\n         a.options.description = asst.description;\n         a.precision = asst.precision;\n         string issuer_name = asst.issuer_name;\n         a.issuer = get_account_id(issuer_name);\n         a.options.max_supply = asst.max_supply;\n         a.options.flags = witness_fed_asset;\n         a.options.issuer_permissions = ( asst.is_bitasset ? ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK\n                                                           : DEFAULT_UIA_ASSET_ISSUER_PERMISSION );\n         a.dynamic_asset_data_id = dynamic_data_id;\n         a.bitasset_data_id = bitasset_data_id;\n         a.creation_block_num = 0;\n         a.creation_time = _current_block_time;\n      });\n   }\n\n   // Create initial balances\n   share_type total_allocation;\n   for( const auto& handout : genesis_state.initial_balances )\n   {\n      const auto asset_id = get_asset_id(handout.asset_symbol);\n      create<balance_object>([&handout,total_allocation,asset_id](balance_object& b) {\n         b.balance = asset(handout.amount, asset_id);\n         b.owner = handout.owner;\n      });\n\n      total_supplies[ asset_id ] += handout.amount;\n   }\n\n   // Create initial vesting balances\n   for( const genesis_state_type::initial_vesting_balance_type& vest : genesis_state.initial_vesting_balances )\n   {\n      const auto asset_id = get_asset_id(vest.asset_symbol);\n      create<balance_object>([&vest,&asset_id](balance_object& b) {\n         b.owner = vest.owner;\n         b.balance = asset(vest.amount, asset_id);\n\n         linear_vesting_policy policy;\n         policy.begin_timestamp = vest.begin_timestamp;\n         policy.vesting_cliff_seconds = 0;\n         policy.vesting_duration_seconds = vest.vesting_duration_seconds;\n         policy.begin_balance = vest.begin_balance;\n\n         b.vesting_policy = std::move(policy);\n      });\n\n      total_supplies[ asset_id ] += vest.amount;\n   }\n\n   if( total_supplies[ asset_id_type(0) ] > 0 )\n   {\n       adjust_balance(GRAPHENE_COMMITTEE_ACCOUNT, -get_balance(GRAPHENE_COMMITTEE_ACCOUNT,{}));\n   }\n   else\n   {\n       total_supplies[ asset_id_type(0) ] = GRAPHENE_MAX_SHARE_SUPPLY;\n   }\n\n   const auto& idx = get_index_type<asset_index>().indices().get<by_symbol>();\n   auto it = idx.begin();\n   bool has_imbalanced_assets = false;\n\n   while( it != idx.end() )\n   {\n      if( it->bitasset_data_id.valid() )\n      {\n         auto supply_itr = total_supplies.find( it->get_id() );\n         auto debt_itr = total_debts.find( it->get_id() );\n         FC_ASSERT( supply_itr != total_supplies.end() );\n         FC_ASSERT( debt_itr != total_debts.end() );\n         if( supply_itr->second != debt_itr->second )\n         {\n            has_imbalanced_assets = true;\n            elog( \"Genesis for asset ${aname} is not balanced\\n\"\n                  \"   Debt is ${debt}\\n\"\n                  \"   Supply is ${supply}\\n\",\n                  (\"aname\", it->symbol)\n                  (\"debt\", debt_itr->second)\n                  (\"supply\", supply_itr->second)\n                );\n         }\n      }\n      ++it;\n   }\n   FC_ASSERT( !has_imbalanced_assets );\n\n   // Save tallied supplies\n   for( const auto& item : total_supplies )\n   {\n      const auto& asset_id = item.first;\n      const auto& total_supply = item.second;\n\n      modify( get(get(asset_id).dynamic_asset_data_id), [&total_supply]( asset_dynamic_data_object& asset_data ) {\n         asset_data.current_supply = total_supply;\n      } );\n   }\n\n   // Create special witness account and remove it, reserve the id\n   const witness_object& wit = create<witness_object>([](const witness_object&) {\n      // Nothing to do\n   });\n   FC_ASSERT( wit.id == GRAPHENE_NULL_WITNESS );\n   remove(wit);\n\n   // Create initial witnesses\n   std::for_each( genesis_state.initial_witness_candidates.begin(),\n                  genesis_state.initial_witness_candidates.end(),\n                  [this,&get_account_id,&genesis_eval_state](const auto& witness) {\n      witness_create_operation op;\n      op.witness_account = get_account_id(witness.owner_name);\n      op.block_signing_key = witness.block_signing_key;\n      this->apply_operation(genesis_eval_state, op);\n   });\n\n   // Create initial committee members\n   std::for_each( genesis_state.initial_committee_candidates.begin(),\n                  genesis_state.initial_committee_candidates.end(),\n                  [this,&get_account_id,&genesis_eval_state](const auto& member) {\n      committee_member_create_operation op;\n      op.committee_member_account = get_account_id(member.owner_name);\n      this->apply_operation(genesis_eval_state, op);\n   });\n\n   // Create initial workers\n   std::for_each( genesis_state.initial_worker_candidates.begin(),\n                  genesis_state.initial_worker_candidates.end(),\n                  [this,&get_account_id,&genesis_state,&genesis_eval_state](const auto& worker)\n   {\n       worker_create_operation op;\n       op.owner = get_account_id(worker.owner_name);\n       op.work_begin_date = genesis_state.initial_timestamp;\n       op.work_end_date = time_point_sec::maximum();\n       op.daily_pay = worker.daily_pay;\n       op.name = \"Genesis-Worker-\" + worker.owner_name;\n       op.initializer = vesting_balance_worker_initializer{uint16_t(0)};\n\n       this->apply_operation(genesis_eval_state, std::move(op));\n   });\n\n   // Set active witnesses\n   modify(get_global_properties(), [&genesis_state](global_property_object& p) {\n      for( uint32_t i = 1; i <= genesis_state.initial_active_witnesses; ++i )\n      {\n         p.active_witnesses.insert(witness_id_type(i));\n      }\n   });\n\n   // Enable fees\n   modify(get_global_properties(), [&genesis_state](global_property_object& p) {\n      p.parameters.get_mutable_fees() = genesis_state.initial_parameters.get_current_fees();\n   });\n\n   // Create witness scheduler\n   _p_witness_schedule_obj = & create<witness_schedule_object>([this]( witness_schedule_object& wso )\n   {\n      for( const witness_id_type& wid : get_global_properties().active_witnesses )\n         wso.current_shuffled_witnesses.push_back( wid );\n   });\n\n   // Create FBA counters\n   create<fba_accumulator_object>([]( fba_accumulator_object& acc )\n   {\n      FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_to_blind ) );\n      acc.accumulated_fba_fees = 0;\n#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET\n      acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET;\n#endif\n   });\n\n   create<fba_accumulator_object>([]( fba_accumulator_object& acc )\n   {\n      FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_blind_transfer ) );\n      acc.accumulated_fba_fees = 0;\n#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET\n      acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET;\n#endif\n   });\n\n   create<fba_accumulator_object>([]( fba_accumulator_object& acc )\n   {\n      FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_from_blind ) );\n      acc.accumulated_fba_fees = 0;\n#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET\n      acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET;\n#endif\n   });\n\n   FC_ASSERT( get_index<fba_accumulator_object>().get_next_id() == fba_accumulator_id_type( fba_accumulator_id_count ) );\n\n   //debug_dump(); // for debug\n\n   _undo_db.enable();\n} FC_CAPTURE_AND_RETHROW() }\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_getter.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n\nnamespace graphene { namespace chain {\n\nconst asset_object& database::get_core_asset() const\n{\n   return *_p_core_asset_obj;\n}\n\nconst asset_dynamic_data_object& database::get_core_dynamic_data() const\n{\n   return *_p_core_dynamic_data_obj;\n}\n\nconst global_property_object& database::get_global_properties()const\n{\n   return *_p_global_prop_obj;\n}\n\nconst chain_property_object& database::get_chain_properties()const\n{\n   return *_p_chain_property_obj;\n}\n\nconst dynamic_global_property_object& database::get_dynamic_global_properties() const\n{\n   return *_p_dyn_global_prop_obj;\n}\n\nconst fee_schedule&  database::current_fee_schedule()const\n{\n   return get_global_properties().parameters.get_current_fees();\n}\n\ntime_point_sec database::head_block_time()const\n{\n   return get_dynamic_global_properties().time;\n}\n\nuint32_t database::head_block_num()const\n{\n   return get_dynamic_global_properties().head_block_number;\n}\n\nblock_id_type database::head_block_id()const\n{\n   return get_dynamic_global_properties().head_block_id;\n}\n\ndecltype( chain_parameters::block_interval ) database::block_interval( )const\n{\n   return get_global_properties().parameters.block_interval;\n}\n\nconst chain_id_type& database::get_chain_id( )const\n{\n   return get_chain_properties().chain_id;\n}\n\nconst node_property_object& database::get_node_properties()const\n{\n   return _node_property_object;\n}\n\nnode_property_object& database::node_properties()\n{\n   return _node_property_object;\n}\n\nvector<authority> database::get_viable_custom_authorities(\n      account_id_type account, const operation &op,\n      rejected_predicate_map* rejected_authorities) const\n{\n   const auto& index = get_index_type<custom_authority_index>().indices().get<by_account_custom>();\n   auto range = index.equal_range(boost::make_tuple(account, unsigned_int(op.which()), true));\n\n   auto is_valid = [now=head_block_time()](const custom_authority_object& auth) { return auth.is_valid(now); };\n   vector<std::reference_wrapper<const custom_authority_object>> valid_auths;\n   std::copy_if(range.first, range.second, std::back_inserter(valid_auths), is_valid);\n\n   vector<authority> results;\n   for (const auto& cust_auth : valid_auths) {\n      try {\n         auto result = cust_auth.get().get_predicate()(op);\n         if (result.success)\n            results.emplace_back(cust_auth.get().auth);\n         else if (rejected_authorities != nullptr)\n            rejected_authorities->insert(std::make_pair(cust_auth.get().get_id(), std::move(result)));\n      } catch (fc::exception& e) {\n         if (rejected_authorities != nullptr)\n            rejected_authorities->insert(std::make_pair(cust_auth.get().get_id(), std::move(e)));\n      }\n   }\n\n   return results;\n}\n\nuint32_t database::last_non_undoable_block_num() const\n{\n   //see https://github.com/bitshares/bitshares-core/issues/377\n   /*\n   There is a case when a value of undo_db.size() is greater then head_block_num(),\n   and as result we get a wrong value for last_non_undoable_block_num.\n   To resolve it we should take into account a number of active_sessions in calculations of\n   last_non_undoable_block_num (active sessions are related to a new block which is under generation).\n   */\n   return head_block_num() - ( _undo_db.size() - _undo_db.active_sessions() );\n}\n\nconst account_statistics_object& database::get_account_stats_by_owner( account_id_type owner )const\n{\n   return account_statistics_id_type(owner.instance)(*this);\n}\n\nconst witness_schedule_object& database::get_witness_schedule_object()const\n{\n   return *_p_witness_schedule_obj;\n}\n\nconst limit_order_object* database::find_settled_debt_order( const asset_id_type& a )const\n{\n   const auto& limit_index = get_index_type<limit_order_index>().indices().get<by_is_settled_debt>();\n   auto itr = limit_index.lower_bound( std::make_tuple( true, a ) );\n   if( itr != limit_index.end() && itr->receive_asset_id() == a )\n      return &(*itr);\n   return nullptr;\n}\n\nconst call_order_object* database::find_least_collateralized_short( const asset_bitasset_data_object& bitasset,\n                                                                    bool force_by_collateral_index )const\n{\n   bool find_by_collateral = true;\n   if( !force_by_collateral_index )\n      // core-1270 hard fork : call price caching issue\n      find_by_collateral = ( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME );\n\n   const call_order_object* call_ptr = nullptr; // place holder\n\n   auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id );\n\n   if( !find_by_collateral ) // before core-1270 hard fork, check with call_price\n   {\n      const auto& call_price_index = get_index_type<call_order_index>().indices().get<by_price>();\n      auto call_itr = call_price_index.lower_bound( call_min );\n      if( call_itr != call_price_index.end() ) // found a call order\n         call_ptr = &(*call_itr);\n   }\n   else // after core-1270 hard fork, check with collateralization\n   {\n      // Note: it is safe to check here even if there is no call order due to individual settlements\n      const auto& call_collateral_index = get_index_type<call_order_index>().indices().get<by_collateral>();\n      auto call_itr = call_collateral_index.lower_bound( call_min );\n      if( call_itr != call_collateral_index.end() ) // found a call order\n         call_ptr = &(*call_itr);\n   }\n   if( !call_ptr ) // not found\n      return nullptr;\n   if( call_ptr->debt_type() != bitasset.asset_id ) // call order is of another asset\n      return nullptr;\n   return call_ptr;\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_init.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/block_summary_object.hpp>\n#include <graphene/chain/budget_record_object.hpp>\n#include <graphene/chain/buyback_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/confidential_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/special_authority_object.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/transaction_history_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n\n#include <graphene/chain/account_evaluator.hpp>\n#include <graphene/chain/asset_evaluator.hpp>\n#include <graphene/chain/assert_evaluator.hpp>\n#include <graphene/chain/balance_evaluator.hpp>\n#include <graphene/chain/committee_member_evaluator.hpp>\n#include <graphene/chain/confidential_evaluator.hpp>\n#include <graphene/chain/credit_offer_evaluator.hpp>\n#include <graphene/chain/custom_evaluator.hpp>\n#include <graphene/chain/liquidity_pool_evaluator.hpp>\n#include <graphene/chain/market_evaluator.hpp>\n#include <graphene/chain/proposal_evaluator.hpp>\n#include <graphene/chain/samet_fund_evaluator.hpp>\n#include <graphene/chain/ticket_evaluator.hpp>\n#include <graphene/chain/transfer_evaluator.hpp>\n#include <graphene/chain/vesting_balance_evaluator.hpp>\n#include <graphene/chain/withdraw_permission_evaluator.hpp>\n#include <graphene/chain/witness_evaluator.hpp>\n#include <graphene/chain/worker_evaluator.hpp>\n#include <graphene/chain/htlc_evaluator.hpp>\n#include <graphene/chain/custom_authority_evaluator.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid database::initialize_evaluators()\n{\n   constexpr size_t max_num_of_evaluators = 255;\n   _operation_evaluators.resize(max_num_of_evaluators);\n   register_evaluator<account_create_evaluator>();\n   register_evaluator<account_update_evaluator>();\n   register_evaluator<account_upgrade_evaluator>();\n   register_evaluator<account_whitelist_evaluator>();\n   register_evaluator<committee_member_create_evaluator>();\n   register_evaluator<committee_member_update_evaluator>();\n   register_evaluator<committee_member_update_global_parameters_evaluator>();\n   register_evaluator<custom_evaluator>();\n   register_evaluator<asset_create_evaluator>();\n   register_evaluator<asset_issue_evaluator>();\n   register_evaluator<asset_reserve_evaluator>();\n   register_evaluator<asset_update_evaluator>();\n   register_evaluator<asset_update_bitasset_evaluator>();\n   register_evaluator<asset_update_feed_producers_evaluator>();\n   register_evaluator<asset_settle_evaluator>();\n   register_evaluator<asset_global_settle_evaluator>();\n   register_evaluator<assert_evaluator>();\n   register_evaluator<limit_order_create_evaluator>();\n   register_evaluator<limit_order_update_evaluator>();\n   register_evaluator<limit_order_cancel_evaluator>();\n   register_evaluator<call_order_update_evaluator>();\n   register_evaluator<bid_collateral_evaluator>();\n   register_evaluator<transfer_evaluator>();\n   register_evaluator<override_transfer_evaluator>();\n   register_evaluator<asset_fund_fee_pool_evaluator>();\n   register_evaluator<asset_publish_feeds_evaluator>();\n   register_evaluator<proposal_create_evaluator>();\n   register_evaluator<proposal_update_evaluator>();\n   register_evaluator<proposal_delete_evaluator>();\n   register_evaluator<vesting_balance_create_evaluator>();\n   register_evaluator<vesting_balance_withdraw_evaluator>();\n   register_evaluator<witness_create_evaluator>();\n   register_evaluator<witness_update_evaluator>();\n   register_evaluator<withdraw_permission_create_evaluator>();\n   register_evaluator<withdraw_permission_claim_evaluator>();\n   register_evaluator<withdraw_permission_update_evaluator>();\n   register_evaluator<withdraw_permission_delete_evaluator>();\n   register_evaluator<worker_create_evaluator>();\n   register_evaluator<balance_claim_evaluator>();\n   register_evaluator<transfer_to_blind_evaluator>();\n   register_evaluator<transfer_from_blind_evaluator>();\n   register_evaluator<blind_transfer_evaluator>();\n   register_evaluator<asset_claim_fees_evaluator>();\n   register_evaluator<asset_update_issuer_evaluator>();\n   register_evaluator<asset_claim_pool_evaluator>();\n   register_evaluator<htlc_create_evaluator>();\n   register_evaluator<htlc_redeem_evaluator>();\n   register_evaluator<htlc_extend_evaluator>();\n   register_evaluator<custom_authority_create_evaluator>();\n   register_evaluator<custom_authority_update_evaluator>();\n   register_evaluator<custom_authority_delete_evaluator>();\n   register_evaluator<ticket_create_evaluator>();\n   register_evaluator<ticket_update_evaluator>();\n   register_evaluator<liquidity_pool_create_evaluator>();\n   register_evaluator<liquidity_pool_delete_evaluator>();\n   register_evaluator<liquidity_pool_update_evaluator>();\n   register_evaluator<liquidity_pool_deposit_evaluator>();\n   register_evaluator<liquidity_pool_withdraw_evaluator>();\n   register_evaluator<liquidity_pool_exchange_evaluator>();\n   register_evaluator<samet_fund_create_evaluator>();\n   register_evaluator<samet_fund_delete_evaluator>();\n   register_evaluator<samet_fund_update_evaluator>();\n   register_evaluator<samet_fund_borrow_evaluator>();\n   register_evaluator<samet_fund_repay_evaluator>();\n   register_evaluator<credit_offer_create_evaluator>();\n   register_evaluator<credit_offer_delete_evaluator>();\n   register_evaluator<credit_offer_update_evaluator>();\n   register_evaluator<credit_offer_accept_evaluator>();\n   register_evaluator<credit_deal_repay_evaluator>();\n   register_evaluator<credit_deal_update_evaluator>();\n}\n\nvoid database::initialize_indexes()\n{\n   reset_indexes();\n   _undo_db.set_max_size( GRAPHENE_MIN_UNDO_HISTORY );\n\n   //Protocol object indexes\n   add_index< primary_index<asset_index, 13> >(); // 8192 assets per chunk\n   add_index< primary_index<force_settlement_index> >();\n\n   add_index< primary_index<account_index, 20> >(); // ~1 million accounts per chunk\n   add_index< primary_index<committee_member_index, 8> >(); // 256 members per chunk\n   add_index< primary_index<witness_index, 10> >(); // 1024 witnesses per chunk\n   add_index< primary_index<limit_order_index > >();\n   add_index< primary_index<call_order_index > >();\n   add_index< primary_index<proposal_index > >();\n   add_index< primary_index<withdraw_permission_index > >();\n   add_index< primary_index<vesting_balance_index> >();\n   add_index< primary_index<worker_index> >();\n   add_index< primary_index<balance_index> >();\n   add_index< primary_index<blinded_balance_index> >();\n   add_index< primary_index< htlc_index> >();\n   add_index< primary_index< custom_authority_index> >();\n   add_index< primary_index<ticket_index> >();\n   add_index< primary_index<liquidity_pool_index> >();\n   add_index< primary_index<samet_fund_index> >();\n   add_index< primary_index<credit_offer_index> >();\n   add_index< primary_index<credit_deal_index> >();\n\n   //Implementation object indexes\n   add_index< primary_index<transaction_index                             > >();\n\n   auto bal_idx = add_index< primary_index<account_balance_index          > >();\n   bal_idx->add_secondary_index<balances_by_account_index>();\n\n   add_index< primary_index<asset_bitasset_data_index,                 13 > >(); // 8192\n   add_index< primary_index<simple_index<global_property_object          >> >();\n   add_index< primary_index<simple_index<dynamic_global_property_object  >> >();\n   add_index< primary_index<account_stats_index,                       20 > >(); // 1 Mi\n   add_index< primary_index<simple_index<asset_dynamic_data_object       >> >();\n   add_index< primary_index<simple_index<block_summary_object            >> >();\n   add_index< primary_index<simple_index<chain_property_object          > > >();\n   add_index< primary_index<simple_index<witness_schedule_object        > > >();\n   add_index< primary_index<simple_index<budget_record_object           > > >();\n   add_index< primary_index< special_authority_index                      > >();\n   add_index< primary_index< buyback_index                                > >();\n   add_index< primary_index<collateral_bid_index                          > >();\n   add_index< primary_index< simple_index< fba_accumulator_object       > > >();\n   add_index< primary_index<credit_deal_summary_index                     > >();\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_maint.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <fc/uint128.hpp>\n\n#include <graphene/protocol/market.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/fba_accumulator_id.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/budget_record_object.hpp>\n#include <graphene/chain/buyback_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/special_authority_object.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/vote_count.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n\nnamespace graphene { namespace chain {\n\ntemplate<class Index>\nvector<std::reference_wrapper<const typename Index::object_type>> database::sort_votable_objects(size_t count) const\n{\n   using ObjectType = typename Index::object_type;\n   const auto& all_objects = get_index_type<Index>().indices();\n   count = std::min(count, all_objects.size());\n   vector<std::reference_wrapper<const ObjectType>> refs;\n   refs.reserve(all_objects.size());\n   std::transform(all_objects.begin(), all_objects.end(),\n                  std::back_inserter(refs),\n                  [](const ObjectType& o) { return std::cref(o); });\n   std::partial_sort(refs.begin(), refs.begin() + count, refs.end(),\n                   [this](const ObjectType& a, const ObjectType& b)->bool {\n      share_type oa_vote = _vote_tally_buffer[a.vote_id];\n      share_type ob_vote = _vote_tally_buffer[b.vote_id];\n      if( oa_vote != ob_vote )\n         return oa_vote > ob_vote;\n      return a.vote_id < b.vote_id;\n   });\n\n   refs.resize(count, refs.front());\n   return refs;\n}\n\ntemplate<class Type>\nvoid database::perform_account_maintenance(Type tally_helper)\n{\n   const auto& bal_idx = get_index_type< account_balance_index >().indices().get< by_maintenance_flag >();\n   if( bal_idx.begin() != bal_idx.end() )\n   {\n      auto bal_itr = bal_idx.rbegin();\n      while( bal_itr->maintenance_flag )\n      {\n         const account_balance_object& bal_obj = *bal_itr;\n\n         modify( get_account_stats_by_owner( bal_obj.owner ), [&bal_obj](account_statistics_object& aso) {\n            aso.core_in_balance = bal_obj.balance;\n         });\n\n         modify( bal_obj, []( account_balance_object& abo ) {\n            abo.maintenance_flag = false;\n         });\n\n         bal_itr = bal_idx.rbegin();\n      }\n   }\n\n   const auto& stats_idx = get_index_type< account_stats_index >().indices().get< by_maintenance_seq >();\n   auto stats_itr = stats_idx.lower_bound( true );\n\n   while( stats_itr != stats_idx.end() )\n   {\n      const account_statistics_object& acc_stat = *stats_itr;\n      const account_object& acc_obj = acc_stat.owner( *this );\n      ++stats_itr;\n\n      if( acc_stat.has_some_core_voting() )\n         tally_helper( acc_obj, acc_stat );\n\n      if( acc_stat.has_pending_fees() )\n         acc_stat.process_fees( acc_obj, *this );\n   }\n\n}\n\n/// @brief A visitor for @ref worker_type which calls pay_worker on the worker within\nstruct worker_pay_visitor\n{\n   private:\n      share_type pay;\n      database& db;\n\n   public:\n      worker_pay_visitor(share_type pay, database& db)\n         : pay(pay), db(db) {}\n\n      typedef void result_type;\n      template<typename W>\n      void operator()(W& worker)const\n      {\n         worker.pay_worker(pay, db);\n      }\n};\n\nvoid database::update_worker_votes()\n{\n   const auto& idx = get_index_type<worker_index>().indices().get<by_account>();\n   auto itr = idx.begin();\n   auto itr_end = idx.end();\n   bool allow_negative_votes = (head_block_time() < HARDFORK_607_TIME);\n   while( itr != itr_end )\n   {\n      modify( *itr, [this,allow_negative_votes]( worker_object& obj )\n      {\n         obj.total_votes_for = _vote_tally_buffer[obj.vote_for];\n         obj.total_votes_against = allow_negative_votes ? _vote_tally_buffer[obj.vote_against] : 0;\n      });\n      ++itr;\n   }\n}\n\nvoid database::pay_workers( share_type& budget )\n{\n   const auto head_time = head_block_time();\n//   ilog(\"Processing payroll! Available budget is ${b}\", (\"b\", budget));\n   vector<std::reference_wrapper<const worker_object>> active_workers;\n   // TODO optimization: add by_expiration index to avoid iterating through all objects\n   get_index_type<worker_index>().inspect_all_objects([head_time, &active_workers](const object& o) {\n      const worker_object& w = static_cast<const worker_object&>(o);\n      if( w.is_active(head_time) && w.approving_stake() > 0 )\n         active_workers.emplace_back(w);\n   });\n\n   // worker with more votes is preferred\n   // if two workers exactly tie for votes, worker with lower ID is preferred\n   std::sort(active_workers.begin(), active_workers.end(), [](const worker_object& wa, const worker_object& wb) {\n      share_type wa_vote = wa.approving_stake();\n      share_type wb_vote = wb.approving_stake();\n      if( wa_vote != wb_vote )\n         return wa_vote > wb_vote;\n      return wa.id < wb.id;\n   });\n\n   const auto last_budget_time = get_dynamic_global_properties().last_budget_time;\n   const auto passed_time_ms = head_time - last_budget_time;\n   const auto passed_time_count = passed_time_ms.count();\n   const auto day_count = fc::days(1).count();\n   for( uint32_t i = 0; i < active_workers.size() && budget > 0; ++i )\n   {\n      const worker_object& active_worker = active_workers[i];\n      share_type requested_pay = active_worker.daily_pay;\n\n      // Note: if there is a good chance that passed_time_count == day_count,\n      //       for better performance, can avoid the 128 bit calculation by adding a check.\n      //       Since it's not the case on BitShares mainnet, we're not using a check here.\n      fc::uint128_t pay = requested_pay.value;\n      pay *= passed_time_count;\n      pay /= day_count;\n      requested_pay = static_cast<uint64_t>(pay);\n\n      share_type actual_pay = std::min(budget, requested_pay);\n      //ilog(\" ==> Paying ${a} to worker ${w}\", (\"w\", active_worker.id)(\"a\", actual_pay));\n      modify(active_worker, [&](worker_object& w) {\n         w.worker.visit(worker_pay_visitor(actual_pay, *this));\n      });\n\n      budget -= actual_pay;\n   }\n}\n\nvoid database::update_active_witnesses()\n{ try {\n   assert( !_witness_count_histogram_buffer.empty() );\n   constexpr size_t two = 2;\n   constexpr auto vid_witness = static_cast<size_t>( vote_id_type::witness ); // 1\n   share_type stake_target = (_total_voting_stake[vid_witness]-_witness_count_histogram_buffer[0]) / two;\n\n   /// accounts that vote for 0 or 1 witness do not get to express an opinion on\n   /// the number of witnesses to have (they abstain and are non-voting accounts)\n\n   share_type stake_tally = 0;\n\n   size_t witness_count = 0;\n   if( stake_target > 0 )\n   {\n      while( (witness_count < _witness_count_histogram_buffer.size() - 1)\n             && (stake_tally <= stake_target) )\n      {\n         stake_tally += _witness_count_histogram_buffer[++witness_count];\n      }\n   }\n\n   const chain_property_object& cpo = get_chain_properties();\n\n   witness_count = std::max( ( witness_count * two ) + 1,\n                             (size_t)cpo.immutable_parameters.min_witness_count );\n   auto wits = sort_votable_objects<witness_index>( witness_count );\n\n   const global_property_object& gpo = get_global_properties();\n\n   auto update_witness_total_votes = [this]( const witness_object& wit ) {\n      modify( wit, [this]( witness_object& obj )\n      {\n         obj.total_votes = _vote_tally_buffer[obj.vote_id];\n      });\n   };\n\n   if( _track_standby_votes )\n   {\n      const auto& all_witnesses = get_index_type<witness_index>().indices();\n      for( const witness_object& wit : all_witnesses )\n      {\n         update_witness_total_votes( wit );\n      }\n   }\n   else\n   {\n      for( const witness_object& wit : wits )\n      {\n         update_witness_total_votes( wit );\n      }\n   }\n\n   // Update witness authority\n   modify( get(GRAPHENE_WITNESS_ACCOUNT), [this,&wits]( account_object& a )\n   {\n      if( head_block_time() < HARDFORK_533_TIME )\n      {\n         uint64_t total_votes = 0;\n         map<account_id_type, uint64_t> weights;\n         a.active.weight_threshold = 0;\n         a.active.clear();\n\n         for( const witness_object& wit : wits )\n         {\n            weights.emplace(wit.witness_account, _vote_tally_buffer[wit.vote_id]);\n            total_votes += _vote_tally_buffer[wit.vote_id];\n         }\n\n         // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits,\n         // then I want to keep the most significant 16 bits of what's left.\n         uint64_t votes_msb = boost::multiprecision::detail::find_msb(total_votes);\n         constexpr uint8_t bits_to_keep_minus_one = 15;\n         uint64_t bits_to_drop = (votes_msb > bits_to_keep_minus_one) ? (votes_msb - bits_to_keep_minus_one) : 0;\n         for( const auto& weight : weights )\n         {\n            // Ensure that everyone has at least one vote. Zero weights aren't allowed.\n            uint16_t votes = std::max((uint16_t)(weight.second >> bits_to_drop), uint16_t(1) );\n            a.active.account_auths[weight.first] += votes;\n            a.active.weight_threshold += votes;\n         }\n\n         a.active.weight_threshold /= two;\n         a.active.weight_threshold += 1;\n      }\n      else\n      {\n         vote_counter vc;\n         for( const witness_object& wit : wits )\n            vc.add( wit.witness_account, _vote_tally_buffer[wit.vote_id] );\n         vc.finish( a.active );\n      }\n   } );\n\n   modify( gpo, [&wits]( global_property_object& gp )\n   {\n      gp.active_witnesses.clear();\n      gp.active_witnesses.reserve(wits.size());\n      std::transform(wits.begin(), wits.end(),\n                     std::inserter(gp.active_witnesses, gp.active_witnesses.end()),\n                     [](const witness_object& w) {\n         return w.get_id();\n      });\n   });\n\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid database::update_active_committee_members()\n{ try {\n   assert( !_committee_count_histogram_buffer.empty() );\n   constexpr size_t two = 2;\n   constexpr auto vid_committee = static_cast<size_t>( vote_id_type::committee ); // 0\n   share_type stake_target = (_total_voting_stake[vid_committee]-_committee_count_histogram_buffer[0]) / two;\n\n   /// accounts that vote for 0 or 1 committee member do not get to express an opinion on\n   /// the number of committee members to have (they abstain and are non-voting accounts)\n   share_type stake_tally = 0;\n   size_t committee_member_count = 0;\n   if( stake_target > 0 )\n   {\n      while( (committee_member_count < _committee_count_histogram_buffer.size() - 1)\n             && (stake_tally <= stake_target.value) )\n      {\n         stake_tally += _committee_count_histogram_buffer[++committee_member_count];\n      }\n   }\n\n   const chain_property_object& cpo = get_chain_properties();\n\n   committee_member_count = std::max( ( committee_member_count * two ) + 1,\n                                      (size_t)cpo.immutable_parameters.min_committee_member_count );\n   auto committee_members = sort_votable_objects<committee_member_index>( committee_member_count );\n\n   auto update_committee_member_total_votes = [this]( const committee_member_object& cm ) {\n      modify( cm, [this]( committee_member_object& obj )\n      {\n         obj.total_votes = _vote_tally_buffer[obj.vote_id];\n      });\n   };\n\n   if( _track_standby_votes )\n   {\n      const auto& all_committee_members = get_index_type<committee_member_index>().indices();\n      for( const committee_member_object& cm : all_committee_members )\n      {\n         update_committee_member_total_votes( cm );\n      }\n   }\n   else\n   {\n      for( const committee_member_object& cm : committee_members )\n      {\n         update_committee_member_total_votes( cm );\n      }\n   }\n\n   // Update committee authorities\n   if( !committee_members.empty() )\n   {\n      const account_object& committee_account = get(GRAPHENE_COMMITTEE_ACCOUNT);\n      modify( committee_account, [this,&committee_members](account_object& a)\n      {\n         if( head_block_time() < HARDFORK_533_TIME )\n         {\n            uint64_t total_votes = 0;\n            map<account_id_type, uint64_t> weights;\n            a.active.weight_threshold = 0;\n            a.active.clear();\n\n            for( const committee_member_object& cm : committee_members )\n            {\n               weights.emplace( cm.committee_member_account, _vote_tally_buffer[cm.vote_id] );\n               total_votes += _vote_tally_buffer[cm.vote_id];\n            }\n\n            // total_votes is 64 bits.\n            // Subtract the number of leading low bits from 64 to get the number of useful bits,\n            // then I want to keep the most significant 16 bits of what's left.\n            uint64_t votes_msb = boost::multiprecision::detail::find_msb(total_votes);\n            constexpr uint8_t bits_to_keep_minus_one = 15;\n            uint64_t bits_to_drop = (votes_msb > bits_to_keep_minus_one) ? (votes_msb - bits_to_keep_minus_one) : 0;\n            for( const auto& weight : weights )\n            {\n               // Ensure that everyone has at least one vote. Zero weights aren't allowed.\n               uint16_t votes = std::max((uint16_t)(weight.second >> bits_to_drop), uint16_t(1) );\n               a.active.account_auths[weight.first] += votes;\n               a.active.weight_threshold += votes;\n            }\n\n            a.active.weight_threshold /= two;\n            a.active.weight_threshold += 1;\n         }\n         else\n         {\n            vote_counter vc;\n            for( const committee_member_object& cm : committee_members )\n               vc.add( cm.committee_member_account, _vote_tally_buffer[cm.vote_id] );\n            vc.finish( a.active );\n         }\n      });\n      modify( get(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT), [&committee_account](account_object& a)\n      {\n         a.active = committee_account.active;\n      });\n   }\n   modify( get_global_properties(), [&committee_members](global_property_object& gp)\n   {\n      gp.active_committee_members.clear();\n      std::transform(committee_members.begin(), committee_members.end(),\n                     std::inserter(gp.active_committee_members, gp.active_committee_members.begin()),\n                     [](const committee_member_object& d) { return d.get_id(); });\n   });\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const\n{\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n   const asset_object& core = get_core_asset();\n   const asset_dynamic_data_object& core_dd = get_core_dynamic_data();\n\n   rec.from_initial_reserve = core.reserved(*this);\n   rec.from_accumulated_fees = core_dd.accumulated_fees;\n   rec.from_unused_witness_budget = dpo.witness_budget;\n   rec.max_supply = core.options.max_supply;\n\n   if(    (dpo.last_budget_time == fc::time_point_sec())\n       || (now <= dpo.last_budget_time) )\n   {\n      rec.time_since_last_budget = 0;\n      return;\n   }\n\n   int64_t dt = (now - dpo.last_budget_time).to_seconds();\n   rec.time_since_last_budget = uint64_t( dt );\n\n   // We'll consider accumulated_fees to be reserved at the BEGINNING\n   // of the maintenance interval.  However, for speed we only\n   // call modify() on the asset_dynamic_data_object once at the\n   // end of the maintenance interval.  Thus the accumulated_fees\n   // are available for the budget at this point, but not included\n   // in core.reserved().\n   share_type reserve = rec.from_initial_reserve + core_dd.accumulated_fees;\n   // Similarly, we consider leftover witness_budget to be burned\n   // at the BEGINNING of the maintenance interval.\n   reserve += dpo.witness_budget;\n\n   fc::uint128_t budget_u128 = reserve.value;\n   budget_u128 *= uint64_t(dt);\n   budget_u128 *= GRAPHENE_CORE_ASSET_CYCLE_RATE;\n   //round up to the nearest satoshi -- this is necessary to ensure\n   //   there isn't an \"untouchable\" reserve, and we will eventually\n   //   be able to use the entire reserve\n   budget_u128 += ((uint64_t(1) << GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS) - 1);\n   budget_u128 >>= GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS;\n   if( budget_u128 < static_cast<fc::uint128_t>(reserve.value) )\n      rec.total_budget = share_type(static_cast<uint64_t>(budget_u128));\n   else\n      rec.total_budget = reserve;\n\n   return;\n}\n\n/**\n * Update the budget for witnesses and workers.\n */\nvoid database::process_budget()\n{\n   try\n   {\n      const global_property_object& gpo = get_global_properties();\n      const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n      const asset_dynamic_data_object& core = get_core_dynamic_data();\n      fc::time_point_sec now = head_block_time();\n\n      int64_t time_to_maint = (dpo.next_maintenance_time - now).to_seconds();\n      //\n      // The code that generates the next maintenance time should\n      //    only produce a result in the future.  If this assert\n      //    fails, then the next maintenance time algorithm is buggy.\n      //\n      assert( time_to_maint > 0 );\n      //\n      // Code for setting chain parameters should validate\n      //    block_interval > 0 (as well as the humans proposing /\n      //    voting on changes to block interval).\n      //\n      assert( gpo.parameters.block_interval > 0 );\n      uint64_t blocks_to_maint = ( ( uint64_t(time_to_maint) + gpo.parameters.block_interval ) - 1 )\n                                 / gpo.parameters.block_interval;\n\n      // blocks_to_maint > 0 because time_to_maint > 0,\n      // which means numerator is at least equal to block_interval\n\n      budget_record rec;\n      initialize_budget_record( now, rec );\n      share_type available_funds = rec.total_budget;\n\n      share_type witness_budget = gpo.parameters.witness_pay_per_block.value * blocks_to_maint;\n      rec.requested_witness_budget = witness_budget;\n      witness_budget = std::min(witness_budget, available_funds);\n      rec.witness_budget = witness_budget;\n      available_funds -= witness_budget;\n\n      fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value;\n      worker_budget_u128 *= uint64_t(time_to_maint);\n      constexpr uint64_t seconds_per_day = 86400;\n      worker_budget_u128 /= seconds_per_day;\n\n      share_type worker_budget;\n      if( worker_budget_u128 >= static_cast<fc::uint128_t>(available_funds.value) )\n         worker_budget = available_funds;\n      else\n         worker_budget = static_cast<uint64_t>(worker_budget_u128);\n      rec.worker_budget = worker_budget;\n      available_funds -= worker_budget;\n\n      share_type leftover_worker_funds = worker_budget;\n      pay_workers(leftover_worker_funds);\n      rec.leftover_worker_funds = leftover_worker_funds;\n      available_funds += leftover_worker_funds;\n\n      rec.supply_delta = ((( rec.witness_budget\n         + rec.worker_budget )\n         - rec.leftover_worker_funds )\n         - rec.from_accumulated_fees )\n         - rec.from_unused_witness_budget;\n\n      modify(core, [&rec\n#ifndef NDEBUG\n                    ,&witness_budget,&worker_budget,&leftover_worker_funds,&dpo\n#endif\n                   ] ( asset_dynamic_data_object& _core )\n      {\n         _core.current_supply = (_core.current_supply + rec.supply_delta );\n\n         assert( rec.supply_delta ==\n                                   witness_budget\n                                 + worker_budget\n                                 - leftover_worker_funds\n                                 - _core.accumulated_fees\n                                 - dpo.witness_budget\n                                );\n         _core.accumulated_fees = 0;\n      });\n\n      modify(dpo, [&witness_budget, &now]( dynamic_global_property_object& _dpo )\n      {\n         // Since initial witness_budget was rolled into\n         // available_funds, we replace it with witness_budget\n         // instead of adding it.\n         _dpo.witness_budget = witness_budget;\n         _dpo.last_budget_time = now;\n      });\n\n      rec.current_supply = core.current_supply;\n      create< budget_record_object >( [this,&rec]( budget_record_object& _rec )\n      {\n         _rec.time = head_block_time();\n         _rec.record = rec;\n      });\n\n      // available_funds is money we could spend, but don't want to.\n      // we simply let it evaporate back into the reserve.\n   }\n   FC_CAPTURE_AND_RETHROW() // GCOVR_EXCL_LINE\n}\n\ntemplate< typename Visitor >\nvoid visit_special_authorities( const database& db, Visitor visit )\n{\n   const auto& sa_idx = db.get_index_type< special_authority_index >().indices().get<by_id>();\n\n   for( const special_authority_object& sao : sa_idx )\n   {\n      const account_object& acct = sao.account(db);\n      if( !acct.owner_special_authority.is_type< no_special_authority >() )\n      {\n         visit( acct, true, acct.owner_special_authority );\n      }\n      if( !acct.active_special_authority.is_type< no_special_authority >() )\n      {\n         visit( acct, false, acct.active_special_authority );\n      }\n   }\n}\n\nvoid update_top_n_authorities( database& db )\n{\n   visit_special_authorities( db, [&db]( const account_object& acct, bool is_owner, const special_authority& auth )\n   {\n      if( auth.is_type< top_holders_special_authority >() )\n      {\n         // use index to grab the top N holders of the asset and vote_counter to obtain the weights\n\n         const top_holders_special_authority& tha = auth.get< top_holders_special_authority >();\n         vote_counter vc;\n         const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();\n         uint8_t num_needed = tha.num_top_holders;\n         if( 0 == num_needed )\n            return;\n\n         // find accounts\n         const auto range = bal_idx.equal_range( boost::make_tuple( tha.asset ) );\n         for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) )\n         {\n             assert( bal.asset_type == tha.asset );\n             if( bal.owner == acct.id )\n                continue;\n             vc.add( bal.owner, bal.balance.value );\n             --num_needed;\n             if( 0 == num_needed )\n                break;\n         }\n\n         db.modify( acct, [&vc,&is_owner]( account_object& a )\n         {\n            vc.finish( is_owner ? a.owner : a.active );\n            if( !vc.is_empty() )\n               a.top_n_control_flags |= (is_owner ? account_object::top_n_control_owner\n                                                  : account_object::top_n_control_active);\n         } );\n      }\n   } );\n}\n\nvoid split_fba_balance(\n   database& db,\n   uint64_t fba_id,\n   uint16_t network_pct,\n   uint16_t designated_asset_buyback_pct,\n   uint16_t designated_asset_issuer_pct\n)\n{\n   FC_ASSERT( ( uint32_t(network_pct) + designated_asset_buyback_pct ) + designated_asset_issuer_pct\n              == GRAPHENE_100_PERCENT );\n   const fba_accumulator_object& fba = fba_accumulator_id_type( fba_id )(db);\n   if( 0 == fba.accumulated_fba_fees )\n      return;\n\n   const asset_dynamic_data_object& core_dd = db.get_core_dynamic_data();\n\n   if( !fba.is_configured(db) )\n   {\n      ilog( \"${n} core given to network at block ${b} due to non-configured FBA\",\n            (\"n\", fba.accumulated_fba_fees)(\"b\", db.head_block_time()) );\n      db.modify( core_dd, [&fba]( asset_dynamic_data_object& _core_dd )\n      {\n         _core_dd.current_supply -= fba.accumulated_fba_fees;\n      } );\n      db.modify( fba, []( fba_accumulator_object& _fba )\n      {\n         _fba.accumulated_fba_fees = 0;\n      } );\n      return;\n   }\n\n   fc::uint128_t buyback_amount_128 = fba.accumulated_fba_fees.value;\n   buyback_amount_128 *= designated_asset_buyback_pct;\n   buyback_amount_128 /= GRAPHENE_100_PERCENT;\n   share_type buyback_amount = static_cast<uint64_t>(buyback_amount_128);\n\n   fc::uint128_t issuer_amount_128 = fba.accumulated_fba_fees.value;\n   issuer_amount_128 *= designated_asset_issuer_pct;\n   issuer_amount_128 /= GRAPHENE_100_PERCENT;\n   share_type issuer_amount = static_cast<uint64_t>(issuer_amount_128);\n\n   // this assert should never fail\n   FC_ASSERT( buyback_amount + issuer_amount <= fba.accumulated_fba_fees );\n\n   share_type network_amount = fba.accumulated_fba_fees - (buyback_amount + issuer_amount);\n\n   const asset_object& designated_asset = (*fba.designated_asset)(db);\n\n   if( network_amount != 0 )\n   {\n      db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd )\n      {\n         _core_dd.current_supply -= network_amount;\n      } );\n   }\n\n   fba_distribute_operation vop;\n   vop.account_id = *designated_asset.buyback_account;\n   vop.fba_id = fba.id;\n   vop.amount = buyback_amount;\n   if( vop.amount != 0 )\n   {\n      db.adjust_balance( *designated_asset.buyback_account, asset(buyback_amount) );\n      db.push_applied_operation(vop);\n   }\n\n   vop.account_id = designated_asset.issuer;\n   vop.fba_id = fba.id;\n   vop.amount = issuer_amount;\n   if( vop.amount != 0 )\n   {\n      db.adjust_balance( designated_asset.issuer, asset(issuer_amount) );\n      db.push_applied_operation(vop);\n   }\n\n   db.modify( fba, []( fba_accumulator_object& _fba )\n   {\n      _fba.accumulated_fba_fees = 0;\n   } );\n}\n\nvoid distribute_fba_balances( database& db )\n{\n   constexpr uint16_t twenty = 20;\n   constexpr uint16_t twenty_percent = twenty * GRAPHENE_1_PERCENT;\n   constexpr uint16_t  sixty = 60;\n   constexpr uint16_t sixty_percent  =  sixty * GRAPHENE_1_PERCENT;\n   split_fba_balance( db, fba_accumulator_id_transfer_to_blind  , twenty_percent, sixty_percent, twenty_percent );\n   split_fba_balance( db, fba_accumulator_id_blind_transfer     , twenty_percent, sixty_percent, twenty_percent );\n   split_fba_balance( db, fba_accumulator_id_transfer_from_blind, twenty_percent, sixty_percent, twenty_percent );\n}\n\nvoid create_buyback_orders( database& db )\n{\n   const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get<by_id>();\n   const auto& bal_idx = db.get_index_type< primary_index< account_balance_index > >()\n                           .get_secondary_index< balances_by_account_index >();\n\n   for( const buyback_object& bbo : bbo_idx )\n   {\n      const asset_object& asset_to_buy = bbo.asset_to_buy(db);\n      assert( asset_to_buy.buyback_account.valid() );\n\n      const account_object& buyback_account = (*(asset_to_buy.buyback_account))(db);\n\n      if( !buyback_account.allowed_assets.valid() )\n      {\n         wlog( \"skipping buyback account ${b} at block ${n} because allowed_assets does not exist\",\n               (\"b\", buyback_account)(\"n\", db.head_block_num()) );\n         continue;\n      }\n\n      for( const auto& entry : bal_idx.get_account_balances( buyback_account.get_id() ) )\n      {\n         const auto* it = entry.second;\n         asset_id_type asset_to_sell = it->asset_type;\n         share_type amount_to_sell = it->balance;\n         if( asset_to_sell == asset_to_buy.id )\n            continue;\n         if( amount_to_sell == 0 )\n            continue;\n         if( buyback_account.allowed_assets->find( asset_to_sell ) == buyback_account.allowed_assets->end() )\n         {\n            wlog( \"buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}\",\n                  (\"b\", buyback_account)(\"a\", asset_to_sell)(\"n\", db.head_block_num()) );\n            continue;\n         }\n\n         try\n         {\n            transaction_evaluation_state buyback_context(&db);\n            buyback_context.skip_fee_schedule_check = true;\n\n            limit_order_create_operation create_vop;\n            create_vop.fee = asset( 0, asset_id_type() );\n            create_vop.seller = buyback_account.id;\n            create_vop.amount_to_sell = asset( amount_to_sell, asset_to_sell );\n            create_vop.min_to_receive = asset( 1, asset_to_buy.get_id() );\n            create_vop.expiration = time_point_sec::maximum();\n            create_vop.fill_or_kill = false;\n\n            limit_order_id_type order_id{ db.apply_operation( buyback_context, create_vop ).get< object_id_type >() };\n\n            if( db.find( order_id ) != nullptr )\n            {\n               limit_order_cancel_operation cancel_vop;\n               cancel_vop.fee = asset( 0, asset_id_type() );\n               cancel_vop.order = order_id;\n               cancel_vop.fee_paying_account = buyback_account.id;\n\n               db.apply_operation( buyback_context, cancel_vop );\n            }\n         }\n         catch( const fc::exception& e )\n         {\n            // we can in fact get here,\n            // e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account\n            wlog( \"Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; \"\n                  \"exception was ${e}\",\n                  (\"as\", asset_to_sell)(\"ab\", asset_to_buy)(\"b\", buyback_account)\n                  (\"n\", db.head_block_num())(\"e\", e.to_detail_string()) );\n            continue;\n         }\n      }\n   }\n   return;\n}\n\nvoid deprecate_annual_members( database& db )\n{\n   const auto& account_idx = db.get_index_type<account_index>().indices().get<by_id>();\n   fc::time_point_sec now = db.head_block_time();\n   for( const account_object& acct : account_idx )\n   {\n      try\n      {\n         transaction_evaluation_state upgrade_context(&db);\n         upgrade_context.skip_fee_schedule_check = true;\n\n         if( acct.is_annual_member( now ) )\n         {\n            account_upgrade_operation upgrade_vop;\n            upgrade_vop.fee = asset( 0, asset_id_type() );\n            upgrade_vop.account_to_upgrade = acct.id;\n            upgrade_vop.upgrade_to_lifetime_member = true;\n            db.apply_operation( upgrade_context, upgrade_vop );\n         }\n      }\n      catch( const fc::exception& e )\n      {\n         // we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account\n         wlog( \"Skipping annual member deprecate processing for account ${a} (${an}) at block ${n}; exception was ${e}\",\n               (\"a\", acct.id)(\"an\", acct.name)(\"n\", db.head_block_num())(\"e\", e.to_detail_string()) );\n         continue;\n      }\n   }\n   return;\n}\n\nvoid database::process_bids( const asset_bitasset_data_object& bad )\n{\n   if( bad.is_prediction_market || bad.current_feed.settlement_price.is_null() )\n      return;\n\n   asset_id_type to_revive_id = bad.asset_id;\n   const asset_object& to_revive = to_revive_id( *this );\n   const asset_dynamic_data_object& bdd = to_revive.dynamic_data( *this );\n\n   if( 0 == bdd.current_supply ) // shortcut\n   {\n      _cancel_bids_and_revive_mpa( to_revive, bad );\n      return;\n   }\n\n   bool after_hf_core_2290 = HARDFORK_CORE_2290_PASSED( get_dynamic_global_properties().next_maintenance_time );\n\n   const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get<by_price>();\n   const auto start = bid_idx.lower_bound( to_revive_id );\n   auto end = bid_idx.upper_bound( to_revive_id );\n\n   share_type covered = 0;\n   auto itr = start;\n   auto revive_ratio = after_hf_core_2290 ? bad.current_feed.initial_collateral_ratio\n                                          : bad.current_feed.maintenance_collateral_ratio;\n   while( covered < bdd.current_supply && itr != end )\n   {\n      const collateral_bid_object& bid = *itr;\n      asset debt_in_bid = bid.inv_swan_price.quote;\n      if( debt_in_bid.amount > bdd.current_supply )\n         debt_in_bid.amount = bdd.current_supply;\n      asset total_collateral = debt_in_bid * bad.settlement_price;\n      total_collateral += bid.inv_swan_price.base;\n      price call_price = price::call_price( debt_in_bid, total_collateral, revive_ratio );\n      if( ~call_price >= bad.current_feed.settlement_price ) break;\n      covered += debt_in_bid.amount;\n      ++itr;\n   }\n   if( covered < bdd.current_supply ) return;\n\n   end = itr;\n   share_type to_cover = bdd.current_supply;\n   share_type remaining_fund = bad.settlement_fund;\n   itr = start;\n   while( itr != end )\n   {\n      const collateral_bid_object& bid = *itr;\n      ++itr;\n      asset debt_in_bid = bid.inv_swan_price.quote;\n      if( debt_in_bid.amount > bdd.current_supply )\n         debt_in_bid.amount = bdd.current_supply;\n      share_type debt = debt_in_bid.amount;\n      share_type collateral = (debt_in_bid * bad.settlement_price).amount;\n      if( debt >= to_cover )\n      {\n         debt = to_cover;\n         collateral = remaining_fund;\n      }\n      to_cover -= debt;\n      remaining_fund -= collateral;\n      execute_bid( bid, debt, collateral, bad.current_feed );\n   }\n   FC_ASSERT( remaining_fund == 0 );\n   FC_ASSERT( to_cover == 0 );\n\n   _cancel_bids_and_revive_mpa( to_revive, bad );\n}\n\n/// Reset call_price of all call orders according to their remaining collateral and debt.\n/// Do not update orders of prediction markets because we're sure they're up to date.\nvoid update_call_orders_hf_343( database& db )\n{\n   // Update call_price\n   wlog( \"Updating all call orders for hardfork core-343 at block ${n}\", (\"n\",db.head_block_num()) );\n   asset_id_type current_asset;\n   const asset_bitasset_data_object* abd = nullptr;\n   // by_collateral index won't change after call_price updated, so it's safe to iterate\n   for( const auto& call_obj : db.get_index_type<call_order_index>().indices().get<by_collateral>() )\n   {\n      if( current_asset != call_obj.debt_type() ) // debt type won't be asset_id_type(), abd will always get initialized\n      {\n         current_asset = call_obj.debt_type();\n         abd = &current_asset(db).bitasset_data(db);\n      }\n      if( !abd || abd->is_prediction_market ) // nothing to do with PM's; check !abd just to be safe\n         continue;\n      db.modify( call_obj, [abd]( call_order_object& call ) {\n         call.call_price  =  price::call_price( call.get_debt(), call.get_collateral(),\n                                                abd->current_feed.maintenance_collateral_ratio );\n      });\n   }\n   wlog( \"Done updating all call orders for hardfork core-343 at block ${n}\", (\"n\",db.head_block_num()) );\n}\n\n/// Reset call_price of all call orders to (1,1) since it won't be used in the future.\n/// Update PMs as well.\nvoid update_call_orders_hf_1270( database& db )\n{\n   // Update call_price\n   for( const auto& call_obj : db.get_index_type<call_order_index>().indices().get<by_id>() )\n   {\n      db.modify( call_obj, []( call_order_object& call ) {\n         call.call_price.base.amount = 1;\n         call.call_price.quote.amount = 1;\n      });\n   }\n}\n\n/// Match call orders for all bitAssets, including PMs.\nvoid match_call_orders( database& db )\n{\n   // Match call orders\n   wlog( \"Matching call orders at block ${n}\", (\"n\",db.head_block_num()) );\n   const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();\n   auto itr = asset_idx.lower_bound( true /** market issued */ );\n   auto itr_end = asset_idx.end();\n   while( itr != itr_end )\n   {\n      const asset_object& a = *itr;\n      ++itr;\n      // be here, next_maintenance_time should have been updated already\n      db.check_call_orders( a ); // allow black swan, and call orders are taker\n   }\n   wlog( \"Done matching call orders at block ${n}\", (\"n\",db.head_block_num()) );\n}\n\nvoid database::process_bitassets()\n{\n   time_point_sec head_time = head_block_time();\n   uint32_t head_epoch_seconds = head_time.sec_since_epoch();\n   bool after_hf_core_518 = ( head_time >= HARDFORK_CORE_518_TIME ); // clear expired feeds\n\n   const auto& update_bitasset = [this,&head_time,head_epoch_seconds,after_hf_core_518]\n                                 ( asset_bitasset_data_object &o )\n   {\n      o.force_settled_volume = 0; // Reset all BitAsset force settlement volumes to zero\n\n      // clear expired feeds if smartcoin (witness_fed or committee_fed) && check overflow\n      if( after_hf_core_518 && o.options.feed_lifetime_sec < head_epoch_seconds\n            && ( 0 != ( o.asset_id(*this).options.flags & ( witness_fed_asset | committee_fed_asset ) ) ) )\n      {\n         fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec;\n         auto itr = o.feeds.rbegin();\n         auto end = o.feeds.rend();\n         while( itr != end ) // loop feeds\n         {\n            auto feed_time = itr->second.first;\n            std::advance( itr, 1 );\n            if( feed_time < calculated )\n               o.feeds.erase( itr.base() ); // delete expired feed\n         }\n         // Note: we don't update current_feed here, and the update_expired_feeds() call is a bit too late,\n         //       so theoretically there could be an inconsistency between active feeds and current_feed.\n         //       And note that the next step \"process_bids()\" is based on current_feed.\n      }\n   };\n\n   for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )\n   {\n      modify( d, update_bitasset );\n      if( d.is_globally_settled() )\n         process_bids(d);\n   }\n}\n\n/****\n * @brief a one-time data process to correct max_supply\n *\n * NOTE: while exceeding max_supply happened in mainnet, it seemed to have corrected\n * itself before HF 1465. But this method must remain to correct some assets in testnet\n */\nvoid process_hf_1465( database& db )\n{\n   // for each market issued asset\n   const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();\n   auto asset_end = asset_idx.end();\n   for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_end; ++asset_itr )\n   {\n      const auto& current_asset = *asset_itr;\n      graphene::chain::share_type current_supply = current_asset.dynamic_data(db).current_supply;\n      graphene::chain::share_type max_supply = current_asset.options.max_supply;\n      if (current_supply > max_supply && max_supply != GRAPHENE_MAX_SHARE_SUPPLY)\n      {\n         wlog( \"Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.\",\n               (\"asset\", current_asset.symbol)\n               (\"current_supply\", current_supply.value)\n               (\"old\", max_supply));\n         db.modify<asset_object>( current_asset, [current_supply](asset_object& obj) {\n            obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value,\n                                                                          GRAPHENE_MAX_SHARE_SUPPLY));\n         });\n      }\n   }\n}\n\n/****\n * @brief a one-time data process to correct current_supply of BTS token in the BitShares mainnet\n */\nvoid process_hf_2103( database& db )\n{\n   const balance_object* bal = db.find( balance_id_type( HARDFORK_CORE_2103_BALANCE_ID ) );\n   if( bal != nullptr && bal->balance.amount < 0 )\n   {\n      const asset_dynamic_data_object& ddo = bal->balance.asset_id(db).dynamic_data(db);\n      db.modify<asset_dynamic_data_object>( ddo, [bal](asset_dynamic_data_object& obj) {\n         obj.current_supply -= bal->balance.amount;\n      });\n      db.remove( *bal );\n   }\n}\n\nstatic void update_bitasset_current_feeds(database& db)\n{\n   for( const auto& bitasset : db.get_index_type<asset_bitasset_data_index>().indices() )\n   {\n      db.update_bitasset_current_feed( bitasset );\n   }\n}\n\n/******\n * @brief one-time data process for hard fork core-868-890\n *\n * Prior to hardfork 868, switching a bitasset's shorting asset would not reset its\n * feeds. This method will run at the hardfork time, and erase (or nullify) feeds\n * that have incorrect backing assets.\n * https://github.com/bitshares/bitshares-core/issues/868\n *\n * Prior to hardfork 890, changing a bitasset's feed expiration time would not\n * trigger a median feed update. This method will run at the hardfork time, and\n * correct all median feed data.\n * https://github.com/bitshares/bitshares-core/issues/890\n *\n * @param db the database\n */\n// NOTE: Unable to remove this function for testnet nor mainnet. Unfortunately, bad\n//       feeds were found.\nvoid process_hf_868_890( database& db )\n{\n   // for each market issued asset\n   const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();\n   auto asset_end = asset_idx.end();\n   for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_end; ++asset_itr )\n   {\n      const auto& current_asset = *asset_itr;\n      // Incorrect witness & committee feeds can simply be removed.\n      // For non-witness-fed and non-committee-fed assets, set incorrect\n      // feeds to price(), since we can't simply remove them. For more information:\n      // https://github.com/bitshares/bitshares-core/pull/832#issuecomment-384112633\n      bool is_witness_or_committee_fed = false;\n      if ( current_asset.options.flags & ( witness_fed_asset | committee_fed_asset ) )\n         is_witness_or_committee_fed = true;\n\n      // for each feed\n      const asset_bitasset_data_object& bitasset_data = current_asset.bitasset_data(db);\n      auto itr = bitasset_data.feeds.begin();\n      while( itr != bitasset_data.feeds.end() )\n      {\n         // If the feed is invalid\n         if ( itr->second.second.settlement_price.quote.asset_id != bitasset_data.options.short_backing_asset\n               && ( is_witness_or_committee_fed || itr->second.second.settlement_price != price() ) )\n         {\n            db.modify( bitasset_data, [&itr, is_witness_or_committee_fed]( asset_bitasset_data_object& obj )\n            {\n               if( is_witness_or_committee_fed )\n               {\n                  // erase the invalid feed\n                  itr = obj.feeds.erase(itr);\n               }\n               else\n               {\n                  // nullify the invalid feed\n                  obj.feeds[itr->first].second.settlement_price = price();\n                  ++itr;\n               }\n            });\n         }\n         else\n         {\n            // Feed is valid. Skip it.\n            ++itr;\n         }\n      } // end loop of each feed\n\n      // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890\n      db.update_bitasset_current_feed( bitasset_data );\n      // NOTE: Normally we should call check_call_orders() after called update_bitasset_current_feed(), but for\n      // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance.\n\n   } // for each market issued asset\n}\n\n\n/**\n * @brief Remove any custom active authorities whose expiration dates are in the past\n * @param db A mutable database reference\n */\nvoid delete_expired_custom_auths( database& db )\n{\n   const auto& index = db.get_index_type<custom_authority_index>().indices().get<by_expiration>();\n   while (!index.empty() && index.begin()->valid_to < db.head_block_time())\n      db.remove(*index.begin());\n}\n\n/// A one-time data process to set values of existing liquid tickets to zero.\nvoid process_hf_2262( database& db )\n{\n   for( const auto& ticket_obj : db.get_index_type<ticket_index>().indices().get<by_id>() )\n   {\n      if( ticket_obj.current_type != liquid ) // only update liquid tickets\n         continue;\n      db.modify( db.get_account_stats_by_owner( ticket_obj.account ), [&ticket_obj](account_statistics_object& aso) {\n         aso.total_pol_value -= ticket_obj.value;\n      });\n      db.modify( ticket_obj, []( ticket_object& t ) {\n         t.value = 0;\n      });\n   }\n}\n\n/// A one-time data process to cancel all collateral bids for assets that disabled collateral bidding already\nvoid process_hf_2281( database& db )\n{\n   const auto& bid_idx = db.get_index_type< collateral_bid_index >().indices().get<by_price>();\n   auto bid_itr = bid_idx.begin();\n   auto bid_end = bid_idx.end();\n\n   asset_id_type current_asset_id;\n   bool can_bid_collateral = true;\n\n   while( bid_itr != bid_end )\n   {\n      const collateral_bid_object& bid = *bid_itr;\n      ++bid_itr;\n      if( current_asset_id != bid.inv_swan_price.quote.asset_id )\n      {\n         current_asset_id = bid.inv_swan_price.quote.asset_id;\n         can_bid_collateral = current_asset_id(db).can_bid_collateral();\n      }\n      if( !can_bid_collateral )\n         db.cancel_bid( bid );\n   }\n\n}\n\nnamespace detail {\n\n   struct vote_recalc_times\n   {\n      time_point_sec full_power_time;\n      time_point_sec zero_power_time;\n   };\n\n   struct vote_recalc_options\n   {\n      vote_recalc_options( uint32_t f, uint32_t d, uint32_t s )\n      : full_power_seconds(f), recalc_steps(d), seconds_per_step(s)\n      {\n         total_recalc_seconds = ( recalc_steps - 1 ) * seconds_per_step; // should not overflow\n         power_percents_to_subtract.reserve( recalc_steps - 1 );\n         for( uint32_t i = 1; i < recalc_steps; ++i )\n            // should not overflow\n            power_percents_to_subtract.push_back( (uint16_t)( ( GRAPHENE_100_PERCENT * i ) / recalc_steps ) );\n      }\n\n      vote_recalc_times get_vote_recalc_times( const time_point_sec now ) const\n      {\n         return { now - full_power_seconds, now - full_power_seconds - total_recalc_seconds };\n      }\n\n      uint32_t full_power_seconds;\n      uint32_t recalc_steps; // >= 1\n      uint32_t seconds_per_step;\n      uint32_t total_recalc_seconds;\n      vector<uint16_t> power_percents_to_subtract;\n\n      static const vote_recalc_options& witness();\n      static const vote_recalc_options& committee();\n      static const vote_recalc_options& worker();\n      static const vote_recalc_options& delegator();\n\n      // return the stake that is \"recalced to X\"\n      uint64_t get_recalced_voting_stake( const uint64_t stake, const time_point_sec last_vote_time,\n                                         const vote_recalc_times& recalc_times ) const\n      {\n         if( last_vote_time > recalc_times.full_power_time )\n            return stake;\n         if( last_vote_time <= recalc_times.zero_power_time )\n            return 0;\n         uint32_t diff = recalc_times.full_power_time.sec_since_epoch() - last_vote_time.sec_since_epoch();\n         uint32_t steps_to_subtract_minus_1 = diff / seconds_per_step;\n         fc::uint128_t stake_to_subtract( stake );\n         stake_to_subtract *= power_percents_to_subtract[steps_to_subtract_minus_1];\n         stake_to_subtract /= GRAPHENE_100_PERCENT;\n         return stake - static_cast<uint64_t>(stake_to_subtract);\n      }\n   };\n\n   const vote_recalc_options& vote_recalc_options::witness()\n   {\n      static const vote_recalc_options o( 360*86400, 8, 45*86400 );\n      return o;\n   }\n   const vote_recalc_options& vote_recalc_options::committee()\n   {\n      static const vote_recalc_options o( 360*86400, 8, 45*86400 );\n      return o;\n   }\n   const vote_recalc_options& vote_recalc_options::worker()\n   {\n      static const vote_recalc_options o( 360*86400, 8, 45*86400 );\n      return o;\n   }\n   const vote_recalc_options& vote_recalc_options::delegator()\n   {\n      static const vote_recalc_options o( 360*86400, 8, 45*86400 );\n      return o;\n   }\n}\n\nvoid database::perform_chain_maintenance( const signed_block& next_block )\n{\n   const auto& gpo = get_global_properties();\n   const auto& dgpo = get_dynamic_global_properties();\n   auto last_vote_tally_time = head_block_time();\n\n   distribute_fba_balances(*this);\n   create_buyback_orders(*this);\n\n   struct vote_tally_helper {\n      database& d;\n      const global_property_object& props;\n      const dynamic_global_property_object& dprops;\n      const time_point_sec now;\n      const bool hf2103_passed;\n      const bool hf2262_passed;\n      const bool pob_activated;\n      const size_t two = 2;\n      const size_t vid_committee = static_cast<size_t>( vote_id_type::committee ); // 0\n      const size_t vid_witness = static_cast<size_t>( vote_id_type::witness ); // 1\n      const size_t vid_worker = static_cast<size_t>( vote_id_type::worker ); // 2\n\n      optional<detail::vote_recalc_times> witness_recalc_times;\n      optional<detail::vote_recalc_times> committee_recalc_times;\n      optional<detail::vote_recalc_times> worker_recalc_times;\n      optional<detail::vote_recalc_times> delegator_recalc_times;\n\n      explicit vote_tally_helper( database& db )\n         : d(db), props( d.get_global_properties() ), dprops( d.get_dynamic_global_properties() ),\n           now( d.head_block_time() ), hf2103_passed( HARDFORK_CORE_2103_PASSED( now ) ),\n           hf2262_passed( HARDFORK_CORE_2262_PASSED( now ) ),\n           pob_activated( dprops.total_pob > 0 || dprops.total_inactive > 0 )\n      {\n         d._vote_tally_buffer.resize( props.next_available_vote_id, 0 );\n         d._witness_count_histogram_buffer.resize( (props.parameters.maximum_witness_count / two) + 1, 0 );\n         d._committee_count_histogram_buffer.resize( (props.parameters.maximum_committee_count / two) + 1, 0 );\n         d._total_voting_stake[vid_committee] = 0;\n         d._total_voting_stake[vid_witness] = 0;\n         if( hf2103_passed )\n         {\n            witness_recalc_times   = detail::vote_recalc_options::witness().get_vote_recalc_times( now );\n            committee_recalc_times = detail::vote_recalc_options::committee().get_vote_recalc_times( now );\n            worker_recalc_times    = detail::vote_recalc_options::worker().get_vote_recalc_times( now );\n            delegator_recalc_times = detail::vote_recalc_options::delegator().get_vote_recalc_times( now );\n         }\n      }\n\n      void operator()( const account_object& stake_account, const account_statistics_object& stats )\n      {\n         // PoB activation\n         if( pob_activated && stats.total_core_pob == 0 && stats.total_core_inactive == 0 )\n            return;\n\n         if( props.parameters.count_non_member_votes || stake_account.is_member( now ) )\n         {\n            // There may be a difference between the account whose stake is voting and the one specifying opinions.\n            // Usually they're the same, but if the stake account has specified a voting_account, that account is the\n            // one specifying the opinions.\n            bool directly_voting = ( stake_account.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT );\n            const account_object& opinion_account = ( directly_voting ? stake_account\n                                                      : d.get(stake_account.options.voting_account) );\n\n            std::array<uint64_t,3> voting_stake; // 0=committee, 1=witness, 2=worker, as in vote_id_type::vote_type\n            uint64_t num_committee_voting_stake; // number of committee members\n            voting_stake[vid_worker] = pob_activated ? 0 : stats.total_core_in_orders.value;\n            voting_stake[vid_worker] += ( !hf2262_passed && stake_account.cashback_vb.valid() ) ?\n                                             (*stake_account.cashback_vb)(d).balance.amount.value : 0;\n            voting_stake[vid_worker] += hf2262_passed ? 0 : stats.core_in_balance.value;\n\n            // voting power stats\n            uint64_t vp_all = 0;       ///<  all voting power.\n            ///  the voting power of the proxy, if there is no attenuation, it is equal to vp_all.\n            uint64_t vp_active = 0;\n            uint64_t vp_committee = 0; ///<  the final voting power for the committees.\n            uint64_t vp_witness = 0;   ///<  the final voting power for the witnesses.\n            uint64_t vp_worker = 0;    ///<  the final voting power for the workers.\n\n            //PoB\n            const uint64_t pol_amount = stats.total_core_pol.value;\n            const uint64_t pol_value = stats.total_pol_value.value;\n            const uint64_t pob_amount = stats.total_core_pob.value;\n            const uint64_t pob_value = stats.total_pob_value.value;\n            if( 0 == pob_amount )\n            {\n               voting_stake[vid_worker] += pol_value;\n            }\n            else if( 0 == pol_amount ) // and pob_amount > 0\n            {\n               if( pob_amount <= voting_stake[vid_worker] )\n               {\n                  voting_stake[vid_worker] += ( pob_value - pob_amount );\n               }\n               else\n               {\n                  auto base_value = ( static_cast<fc::uint128_t>( voting_stake[vid_worker] ) * pob_value )\n                                    / pob_amount;\n                  voting_stake[vid_worker] = static_cast<uint64_t>( base_value );\n               }\n            }\n            else if( pob_amount <= pol_amount ) // pob_amount > 0 && pol_amount > 0\n            {\n               auto base_value = ( static_cast<fc::uint128_t>( pob_value ) * pol_value ) / pol_amount;\n               auto diff_value = ( static_cast<fc::uint128_t>( pob_amount ) * pol_value ) / pol_amount;\n               base_value += ( pol_value - diff_value );\n               voting_stake[vid_worker] += static_cast<uint64_t>( base_value );\n            }\n            else // pob_amount > pol_amount > 0\n            {\n               auto base_value = ( static_cast<fc::uint128_t>( pol_value ) * pob_value ) / pob_amount;\n               fc::uint128_t diff_amount = pob_amount - pol_amount;\n               if( diff_amount <= voting_stake[vid_worker] )\n               {\n                  auto diff_value = ( static_cast<fc::uint128_t>( pol_amount ) * pob_value ) / pob_amount;\n                  base_value += ( pob_value - diff_value );\n                  voting_stake[vid_worker] += static_cast<uint64_t>( base_value - diff_amount );\n               }\n               else // diff_amount > voting_stake[vid_worker]\n               {\n                  base_value += ( static_cast<fc::uint128_t>( voting_stake[vid_worker] ) * pob_value ) / pob_amount;\n                  voting_stake[vid_worker] = static_cast<uint64_t>( base_value );\n               }\n            }\n\n            // Shortcut\n            if( 0 == voting_stake[vid_worker] )\n               return;\n\n            const auto& opinion_account_stats = ( directly_voting ? stats : opinion_account.statistics( d ) );\n\n            // Recalculate votes\n            if( !hf2103_passed )\n            {\n               voting_stake[vid_committee] = voting_stake[vid_worker];\n               voting_stake[vid_witness]   = voting_stake[vid_worker];\n               num_committee_voting_stake  = voting_stake[vid_worker];\n               vp_all       = voting_stake[vid_worker];\n               vp_active    = voting_stake[vid_worker];\n               vp_committee = voting_stake[vid_worker];\n               vp_witness   = voting_stake[vid_worker];\n               vp_worker    = voting_stake[vid_worker];\n            }\n            else\n            {\n               vp_all = voting_stake[vid_worker];\n               vp_active = voting_stake[vid_worker];\n               if( !directly_voting )\n               {\n                  voting_stake[vid_worker] = detail::vote_recalc_options::delegator().get_recalced_voting_stake(\n                     voting_stake[vid_worker], stats.last_vote_time, *delegator_recalc_times );\n                  vp_active = voting_stake[vid_worker];\n               }\n               voting_stake[vid_witness] = detail::vote_recalc_options::witness().get_recalced_voting_stake(\n                  voting_stake[vid_worker], opinion_account_stats.last_vote_time, *witness_recalc_times );\n               vp_witness = voting_stake[vid_witness];\n               voting_stake[vid_committee] = detail::vote_recalc_options::committee().get_recalced_voting_stake(\n                  voting_stake[vid_worker], opinion_account_stats.last_vote_time, *committee_recalc_times );\n               vp_committee = voting_stake[vid_committee];\n               num_committee_voting_stake = voting_stake[vid_committee];\n               if( opinion_account.num_committee_voted > 1 )\n                  voting_stake[vid_committee] /= opinion_account.num_committee_voted;\n               voting_stake[vid_worker] = detail::vote_recalc_options::worker().get_recalced_voting_stake(\n                  voting_stake[vid_worker], opinion_account_stats.last_vote_time, *worker_recalc_times );\n               vp_worker = voting_stake[vid_worker];\n            }\n\n            // update voting power\n            d.modify( opinion_account_stats, [vp_all,vp_active,vp_committee,vp_witness,vp_worker,this]\n                                             ( account_statistics_object& update_stats ) {\n               if (update_stats.vote_tally_time != now)\n               {\n                  update_stats.vp_all = vp_all;\n                  update_stats.vp_active = vp_active;\n                  update_stats.vp_committee = vp_committee;\n                  update_stats.vp_witness = vp_witness;\n                  update_stats.vp_worker = vp_worker;\n                  update_stats.vote_tally_time = now;\n               }\n               else\n               {\n                  update_stats.vp_all += vp_all;\n                  update_stats.vp_active += vp_active;\n                  update_stats.vp_committee += vp_committee;\n                  update_stats.vp_witness += vp_witness;\n                  update_stats.vp_worker += vp_worker;\n               }\n            });\n\n            for( vote_id_type id : opinion_account.options.votes )\n            {\n               uint32_t offset = id.instance();\n               uint32_t type = std::min( id.type(), vote_id_type::vote_type::worker ); // cap the data\n               // if they somehow managed to specify an illegal offset, ignore it.\n               if( offset < d._vote_tally_buffer.size() )\n                  d._vote_tally_buffer[offset] += voting_stake[type];\n            }\n\n            // votes for a number greater than maximum_witness_count are skipped here\n            if( voting_stake[vid_witness] > 0\n                  && opinion_account.options.num_witness <= props.parameters.maximum_witness_count )\n            {\n               uint16_t offset = opinion_account.options.num_witness / two;\n               d._witness_count_histogram_buffer[offset] += voting_stake[vid_witness];\n            }\n            // votes for a number greater than maximum_committee_count are skipped here\n            if( num_committee_voting_stake > 0\n                  && opinion_account.options.num_committee <= props.parameters.maximum_committee_count )\n            {\n               uint16_t offset = opinion_account.options.num_committee / two;\n               d._committee_count_histogram_buffer[offset] += num_committee_voting_stake;\n            }\n\n            d._total_voting_stake[vid_committee] += num_committee_voting_stake;\n            d._total_voting_stake[vid_witness] += voting_stake[vid_witness];\n         }\n      }\n   };\n\n   vote_tally_helper tally_helper(*this);\n\n   perform_account_maintenance( tally_helper );\n\n   struct clear_canary {\n      explicit clear_canary(vector<uint64_t>& target): target(target){}\n      clear_canary( const clear_canary& ) = delete;\n      ~clear_canary() { target.clear(); }\n   private:\n      vector<uint64_t>& target;\n   };\n   clear_canary a(_witness_count_histogram_buffer);\n   clear_canary b(_committee_count_histogram_buffer);\n   clear_canary c(_vote_tally_buffer);\n\n   update_top_n_authorities(*this);\n   update_active_witnesses();\n   update_active_committee_members();\n   update_worker_votes();\n\n   modify(gpo, [&dgpo](global_property_object& p) {\n      // Remove scaling of account registration fee\n      p.parameters.get_mutable_fees().get<account_create_operation>().basic_fee >>=\n            p.parameters.account_fee_scale_bitshifts *\n            (dgpo.accounts_registered_this_interval / p.parameters.accounts_per_fee_scale);\n\n      if( p.pending_parameters )\n      {\n         p.parameters = std::move(*p.pending_parameters);\n         p.pending_parameters.reset();\n      }\n   });\n\n   auto next_maintenance_time = dgpo.next_maintenance_time;\n   auto maintenance_interval = gpo.parameters.maintenance_interval;\n\n   if( next_maintenance_time <= next_block.timestamp )\n   {\n      if( 1 == next_block.block_num() )\n         next_maintenance_time = time_point_sec() +\n               (((next_block.timestamp.sec_since_epoch() / maintenance_interval) + 1) * maintenance_interval);\n      else\n      {\n         // We want to find the smallest k such that next_maintenance_time + k * maintenance_interval > head_block_time()\n         //  This implies k > ( head_block_time() - next_maintenance_time ) / maintenance_interval\n         //\n         // Let y be the right-hand side of this inequality, i.e.\n         // y = ( head_block_time() - next_maintenance_time ) / maintenance_interval\n         //\n         // and let the fractional part f be y-floor(y).  Clearly 0 <= f < 1.\n         // We can rewrite f = y-floor(y) as floor(y) = y-f.\n         //\n         // Clearly k = floor(y)+1 has k > y as desired.  Now we must\n         // show that this is the least such k, i.e. k-1 <= y.\n         //\n         // But k-1 = floor(y)+1-1 = floor(y) = y-f <= y.\n         // So this k suffices.\n         //\n         auto y = (head_block_time() - next_maintenance_time).to_seconds() / maintenance_interval;\n         next_maintenance_time += (uint32_t)( (y+1) * maintenance_interval );\n      }\n   }\n\n   if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) )\n      deprecate_annual_members(*this);\n\n   // To reset call_price of all call orders, then match by new rule, for hard fork core-343\n   bool to_process_hf_343 = false;\n   if( (dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME) && (next_maintenance_time > HARDFORK_CORE_343_TIME) )\n      to_process_hf_343 = true;\n\n   // Process inconsistent price feeds\n   if( (dgpo.next_maintenance_time <= HARDFORK_CORE_868_890_TIME)\n         && (next_maintenance_time > HARDFORK_CORE_868_890_TIME) )\n      process_hf_868_890( *this );\n\n   // To reset call_price of all call orders, then match by new rule, for hard fork core-1270\n   bool to_process_hf_1270 = false;\n   if( (dgpo.next_maintenance_time <= HARDFORK_CORE_1270_TIME) && (next_maintenance_time > HARDFORK_CORE_1270_TIME) )\n      to_process_hf_1270 = true;\n\n   // make sure current_supply is less than or equal to max_supply\n   if ( dgpo.next_maintenance_time <= HARDFORK_CORE_1465_TIME && next_maintenance_time > HARDFORK_CORE_1465_TIME )\n      process_hf_1465(*this);\n\n   // Fix supply issue\n   if ( dgpo.next_maintenance_time <= HARDFORK_CORE_2103_TIME && next_maintenance_time > HARDFORK_CORE_2103_TIME )\n      process_hf_2103(*this);\n\n   // Update tickets. Note: the new values will take effect only on the next maintenance interval\n   if ( dgpo.next_maintenance_time <= HARDFORK_CORE_2262_TIME && next_maintenance_time > HARDFORK_CORE_2262_TIME )\n      process_hf_2262(*this);\n\n   // Cancel all collateral bids on assets which disabled collateral bidding already\n   if ( dgpo.next_maintenance_time <= HARDFORK_CORE_2281_TIME && next_maintenance_time > HARDFORK_CORE_2281_TIME )\n      process_hf_2281(*this);\n\n   // To check call orders and potential match them with force settlements, for hard fork core-2481\n   bool match_call_orders_for_hf_2481 = false;\n   if( (dgpo.next_maintenance_time <= HARDFORK_CORE_2481_TIME) && (next_maintenance_time > HARDFORK_CORE_2481_TIME) )\n      match_call_orders_for_hf_2481 = true;\n\n   modify(dgpo, [last_vote_tally_time, next_maintenance_time](dynamic_global_property_object& d) {\n      d.next_maintenance_time = next_maintenance_time;\n      d.last_vote_tally_time = last_vote_tally_time;\n      d.accounts_registered_this_interval = 0;\n   });\n\n   // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-343\n   if( to_process_hf_343 )\n   {\n      update_call_orders_hf_343(*this);\n      match_call_orders(*this);\n   }\n\n   // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-1270.\n   if( to_process_hf_1270 )\n   {\n      update_call_orders_hf_1270(*this);\n      update_bitasset_current_feeds(*this);\n      match_call_orders(*this);\n   }\n\n   // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-2481\n   if( match_call_orders_for_hf_2481 )\n   {\n      match_call_orders(*this);\n   }\n\n   process_bitassets();\n   delete_expired_custom_auths(*this);\n\n   // process_budget needs to run at the bottom because\n   //   it needs to know the next_maintenance_time\n   process_budget();\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_management.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n#include <graphene/chain/special_authority_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/fstream.hpp>\n\n#include <fstream>\n#include <functional>\n#include <iostream>\n#include <queue>\n#include <tuple>\n\nnamespace graphene { namespace chain {\n\ndatabase::database()\n{\n   initialize_indexes();\n   initialize_evaluators();\n}\n\ndatabase::~database()\n{\n   clear_pending();\n}\n\nvoid database::reindex( fc::path data_dir )\n{ try {\n   auto last_block = _block_id_to_block.last();\n   if( !last_block ) {\n      elog( \"!no last block\" );\n      edump((last_block));\n      return;\n   }\n   if( last_block->block_num() <= head_block_num()) return;\n\n   ilog( \"reindexing blockchain\" );\n   auto start = fc::time_point::now();\n   const auto last_block_num = last_block->block_num();\n   uint32_t undo_point = last_block_num < GRAPHENE_MAX_UNDO_HISTORY ? 0 : (last_block_num - GRAPHENE_MAX_UNDO_HISTORY);\n\n   ilog( \"Replaying blocks, starting at ${next}...\", (\"next\",head_block_num() + 1) );\n   if( head_block_num() >= undo_point )\n   {\n      if( head_block_num() > 0 )\n         _fork_db.start_block( *fetch_block_by_number( head_block_num() ) );\n   }\n   else\n      _undo_db.disable();\n\n   uint32_t skip = node_properties().skip_flags;\n\n   size_t total_block_size = _block_id_to_block.total_block_size();\n   const auto& gpo = get_global_properties();\n   std::queue< std::tuple< size_t, signed_block, fc::future< void > > > blocks;\n   uint32_t next_block_num = head_block_num() + 1;\n   uint32_t i = next_block_num;\n   while( next_block_num <= last_block_num || !blocks.empty() )\n   {\n      if( next_block_num <= last_block_num && blocks.size() < 20 )\n      {\n         const size_t processed_block_size = _block_id_to_block.blocks_current_position();\n         fc::optional< signed_block > block = _block_id_to_block.fetch_by_number( next_block_num );\n         ++next_block_num;\n         if( block.valid() )\n         {\n            if( block->timestamp >= (last_block->timestamp - gpo.parameters.maximum_time_until_expiration) )\n               skip &= (uint32_t)(~skip_transaction_dupe_check);\n            blocks.emplace( processed_block_size, std::move(*block), fc::future<void>() );\n            std::get<2>(blocks.back()) = precompute_parallel( std::get<1>(blocks.back()), skip );\n         }\n         else\n         {\n            wlog( \"Reindexing terminated due to gap:  Block ${i} does not exist!\", (\"i\", i) );\n            uint32_t dropped_count = 0;\n            while( true )\n            {\n               fc::optional< block_id_type > last_id = _block_id_to_block.last_id();\n               // this can trigger if we attempt to e.g. read a file that has block #2 but no block #1\n               // OR\n               // we've caught up to the gap\n               if( !last_id.valid() || block_header::num_from_id( *last_id ) <= i )\n                  break;\n               _block_id_to_block.remove( *last_id );\n               ++dropped_count;\n            }\n            wlog( \"Dropped ${n} blocks from after the gap\", (\"n\", dropped_count) );\n            next_block_num = last_block_num + 1; // don't load more blocks\n         }\n      }\n      else\n      {\n         std::get<2>(blocks.front()).wait();\n         const signed_block& block = std::get<1>(blocks.front());\n\n         if( i % 10000 == 0 )\n         {\n            std::stringstream bysize;\n            std::stringstream bynum;\n            size_t current_pos = std::get<0>(blocks.front());\n            if( current_pos > total_block_size )\n               total_block_size = current_pos;\n            bysize << std::fixed << std::setprecision(5) << (100 * double(current_pos) / total_block_size);\n            bynum << std::fixed << std::setprecision(5) << (100 * double(i) / last_block_num);\n            ilog(\n               \"   [by size: ${size}%   ${processed} of ${total}]   [by num: ${num}%   ${i} of ${last}]\",\n               (\"size\", bysize.str())\n               (\"processed\", current_pos)\n               (\"total\", total_block_size)\n               (\"num\", bynum.str())\n               (\"i\", i)\n               (\"last\", last_block_num)\n            );\n         }\n         if( i == undo_point )\n         {\n            ilog( \"Writing object database to disk at block ${i}, please DO NOT kill the program\", (\"i\", i) );\n            flush();\n            ilog( \"Done writing object database to disk\" );\n         }\n         if( i < undo_point )\n            apply_block( block, skip );\n         else\n         {\n            _undo_db.enable();\n            push_block( block, skip );\n         }\n         blocks.pop();\n         ++i;\n      }\n   }\n   _undo_db.enable();\n   auto end = fc::time_point::now();\n   ilog( \"Done reindexing, elapsed time: ${t} sec\", (\"t\",double((end-start).count())/1000000.0 ) );\n} FC_CAPTURE_AND_RETHROW( (data_dir) ) }\n\nvoid database::wipe(const fc::path& data_dir, bool include_blocks)\n{\n   ilog(\"Wiping database\", (\"include_blocks\", include_blocks));\n   if (_opened) {\n     close();\n   }\n   object_database::wipe(data_dir);\n   if( include_blocks )\n      fc::remove_all( data_dir / \"database\" );\n}\n\nvoid database::open(\n   const fc::path& data_dir,\n   std::function<genesis_state_type()> genesis_loader,\n   const std::string& db_version)\n{\n   try\n   {\n      bool wipe_object_db = false;\n      if( !fc::exists( data_dir / \"db_version\" ) )\n         wipe_object_db = true;\n      else\n      {\n         std::string version_string;\n         fc::read_file_contents( data_dir / \"db_version\", version_string );\n         wipe_object_db = ( version_string != db_version );\n      }\n      if( wipe_object_db ) {\n          ilog(\"Wiping object_database due to missing or wrong version\");\n          object_database::wipe( data_dir );\n          std::ofstream version_file( (data_dir / \"db_version\").generic_string().c_str(),\n                                      std::ios::out | std::ios::binary | std::ios::trunc );\n          version_file.write( db_version.c_str(), db_version.size() );\n          version_file.close();\n      }\n\n      object_database::open(data_dir);\n\n      _block_id_to_block.open(data_dir / \"database\" / \"block_num_to_block\");\n\n      if( !find(global_property_id_type()) )\n         init_genesis(genesis_loader());\n      else\n      {\n         _p_core_asset_obj = &get( asset_id_type() );\n         _p_core_dynamic_data_obj = &get( asset_dynamic_data_id_type() );\n         _p_global_prop_obj = &get( global_property_id_type() );\n         _p_chain_property_obj = &get( chain_property_id_type() );\n         _p_dyn_global_prop_obj = &get( dynamic_global_property_id_type() );\n         _p_witness_schedule_obj = &get( witness_schedule_id_type() );\n      }\n\n      fc::optional<block_id_type> last_block = _block_id_to_block.last_id();\n      if( last_block.valid() )\n      {\n         FC_ASSERT( *last_block >= head_block_id(),\n                    \"last block ID does not match current chain state\",\n                    (\"last_block->id\", last_block)(\"head_block_id\",head_block_num()) );\n         reindex( data_dir );\n      }\n      _opened = true;\n   }\n   FC_CAPTURE_LOG_AND_RETHROW( (data_dir) )\n}\n\nvoid database::close(bool rewinding)\n{\n   if (!_opened)\n      return;\n   // TODO:  Save pending tx's on close()\n   clear_pending();\n\n   // pop all of the blocks that we can given our undo history, this should\n   // throw when there is no more undo history to pop\n   if( rewinding )\n   {\n      try\n      {\n         uint32_t cutoff = get_dynamic_global_properties().last_irreversible_block_num;\n\n         ilog( \"Rewinding from ${head} to ${cutoff}\", (\"head\",head_block_num())(\"cutoff\",cutoff) );\n         while( head_block_num() > cutoff )\n         {\n            block_id_type popped_block_id = head_block_id();\n            pop_block();\n            _fork_db.remove(popped_block_id); // doesn't throw on missing\n         }\n      }\n      catch ( const fc::exception& e )\n      {\n         wlog( \"Database close unexpected exception: ${e}\", (\"e\", e) );\n      }\n   }\n\n   // Since pop_block() will move tx's in the popped blocks into pending,\n   // we have to clear_pending() after we're done popping to get a clean\n   // DB state (issue #336).\n   clear_pending();\n\n   ilog( \"Writing object database to disk at block ${i}, please DO NOT kill the program\", (\"i\", head_block_num()) );\n   object_database::flush();\n   ilog( \"Done writing object database to disk\" );\n\n   object_database::close();\n\n   if( _block_id_to_block.is_open() )\n      _block_id_to_block.close();\n\n   _fork_db.reset();\n\n   _opened = false;\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_market.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace chain {\n\nnamespace detail {\n\n   share_type calculate_percent(const share_type& value, uint16_t percent)\n   {\n      fc::uint128_t a(value.value);\n      a *= percent;\n      a /= GRAPHENE_100_PERCENT;\n      FC_ASSERT( a <= GRAPHENE_MAX_SHARE_SUPPLY, \"overflow when calculating percent\" );\n      return static_cast<int64_t>(a);\n   }\n\n} //detail\n\nbool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan,\n                                    const asset_bitasset_data_object* bitasset_ptr )\n{\n    if( !mia.is_market_issued() ) return false;\n\n    const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this);\n    if( bitasset.is_globally_settled() ) return true; // already globally settled\n    auto settle_price = bitasset.current_feed.settlement_price;\n    if( settle_price.is_null() ) return false; // no feed\n\n    asset_id_type debt_asset_id = bitasset.asset_id;\n\n    auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n    bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue\n    bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls\n\n    // After core-2481 hard fork, if there are force-settlements, match call orders with them first\n    if( after_core_hardfork_2481 )\n    {\n      const auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();\n      auto lower_itr = settlement_index.lower_bound( debt_asset_id );\n      if( lower_itr != settlement_index.end() && lower_itr->balance.asset_id == debt_asset_id )\n         return false;\n    }\n\n    // Find the call order with the least collateral ratio\n    const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false );\n    if( !call_ptr ) // no call order\n       return false;\n\n    const limit_order_index& limit_index = get_index_type<limit_order_index>();\n    const auto& limit_price_index = limit_index.indices().get<by_price>();\n\n    // looking for limit orders selling the most USD for the least CORE\n    auto highest_possible_bid = price::max( debt_asset_id, bitasset.options.short_backing_asset );\n    // stop when limit orders are selling too little USD for too much CORE\n    auto lowest_possible_bid  = price::min( debt_asset_id, bitasset.options.short_backing_asset );\n\n    FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id );\n    // NOTE limit_price_index is sorted from greatest to least\n    auto limit_itr = limit_price_index.lower_bound( highest_possible_bid );\n    auto limit_end = limit_price_index.upper_bound( lowest_possible_bid );\n\n    price call_pays_price;\n    if( limit_itr != limit_end )\n    {\n       call_pays_price = limit_itr->sell_price;\n       if( after_core_hardfork_2481 )\n       {\n          // due to margin call fee, we check with MCPP (margin call pays price) here\n          call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio();\n       }\n    }\n\n    using bsrm_type = bitasset_options::black_swan_response_type;\n    const auto bsrm = bitasset.get_black_swan_response_method();\n\n    // when BSRM is individual settlement, we loop multiple times\n    bool settled_some = false;\n    while( true )\n    {\n       settle_price = bitasset.current_feed.settlement_price;\n       price highest = settle_price;\n       // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here\n       // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle.\n       // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement.\n       // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed.\n       if( bsrm_type::individual_settlement_to_fund == bsrm )\n          highest = bitasset.median_feed.max_short_squeeze_price();\n       else if( !before_core_hardfork_1270 )\n          highest = bitasset.current_feed.max_short_squeeze_price();\n       else if( maint_time > HARDFORK_CORE_338_TIME )\n          highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270();\n       // else do nothing\n\n       if( limit_itr != limit_end )\n       {\n          FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id );\n          if( bsrm_type::individual_settlement_to_fund != bsrm )\n             highest = std::max( call_pays_price, highest );\n          // for individual_settlement_to_fund, if call_pays_price < current_feed.max_short_squeeze_price(),\n          // we don't match the least collateralized short with the limit order\n          //    even if call_pays_price >= median_feed.max_short_squeeze_price()\n          else if( call_pays_price >= bitasset.current_feed.max_short_squeeze_price() )\n             highest = call_pays_price;\n          // else highest is median_feed.max_short_squeeze_price()\n       }\n\n       // The variable `highest` after hf_338:\n       // * if no limit order, it is expected to be the black swan price; if the call order with the least CR\n       //   has CR below or equal to the black swan price, we trigger GS,\n       // * if there exists at least one limit order and the price is higher, we use the limit order's price,\n       //   which means we will match the margin call orders with the limit order first.\n       //\n       // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered\n       // when calculating highest, which means some blackswans weren't got caught here.  Fortunately they got\n       // caught by an additional check in check_call_orders().\n       // This bug is fixed in hf_2481. Actually, after hf_2481,\n       // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(),\n       // * if there is no force settlement, we check here with margin call fee in consideration.\n\n       auto least_collateral = call_ptr->collateralization();\n       // Note: strictly speaking, even when the call order's collateralization is lower than ~highest,\n       //       if the matching limit order is smaller, due to rounding, it is still possible that the\n       //       call order's collateralization would increase and become higher than ~highest after matched.\n       //       However, for simplicity, we only compare the prices here.\n       bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest )\n                                                    : ( ~least_collateral >= highest );\n       if( !is_blackswan )\n          return settled_some;\n\n       wdump( (*call_ptr) );\n       elog( \"Black Swan detected on asset ${symbol} (${id}) at block ${b}: \\n\"\n             \"   Least collateralized call: ${lc}  ${~lc}\\n\"\n           //  \"   Highest Bid:               ${hb}  ${~hb}\\n\"\n             \"   Settle Price:              ${~sp}  ${sp}\\n\"\n             \"   Max:                       ${~h}  ${h}\\n\",\n            (\"id\",mia.id)(\"symbol\",mia.symbol)(\"b\",head_block_num())\n            (\"lc\",least_collateral.to_real())(\"~lc\",(~least_collateral).to_real())\n          //  (\"hb\",limit_itr->sell_price.to_real())(\"~hb\",(~limit_itr->sell_price).to_real())\n            (\"sp\",settle_price.to_real())(\"~sp\",(~settle_price).to_real())\n            (\"h\",highest.to_real())(\"~h\",(~highest).to_real()) );\n       edump((enable_black_swan));\n       FC_ASSERT( enable_black_swan,\n                  \"Black swan was detected during a margin update which is not allowed to trigger a blackswan\" );\n\n       if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm )\n       {\n          individually_settle( bitasset, *call_ptr );\n          call_ptr = find_least_collateralized_short( bitasset, true );\n          if( !call_ptr ) // no call order\n             return true;\n          settled_some = true;\n          continue;\n       }\n       // Global settlement or no settlement, but we should not be here if BSRM is no_settlement\n       else if( after_core_hardfork_2481 )\n       {\n          if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here\n             wlog( \"Internal error: BSRM is no_settlement but undercollateralization occurred\" );\n          // After hf_2481, when a global settlement occurs,\n          // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and\n          //   they are closed at the same price,\n          // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same\n          //   price too.\n          // * The GS price would close the position with the least CR with no collateral left for the owner,\n          //   but would close other positions with some collateral left (if any) for their owners.\n          // * Both the premium and the margin call fee paid by the margin calls go to the asset owner, none will go\n          //   to the global settlement fund, because\n          //   - if a part of the premium or fees goes to the global settlement fund, it means there would be a\n          //     difference in settlement prices, so traders are incentivized to create new debt in the last minute\n          //     then settle after GS to earn free money,\n          //   - if no premium or fees goes to the global settlement fund, it means debt asset holders would only\n          //     settle for less after GS, so they are incentivized to settle before GS which helps avoid GS.\n          globally_settle_asset(mia, ~least_collateral, true );\n       }\n       else if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price )\n          // global settle at feed price if possible\n          globally_settle_asset(mia, settle_price );\n       else\n          globally_settle_asset(mia, ~least_collateral );\n       return true;\n    }\n}\n\n/**\n * All margin positions are force closed at the swan price\n * Collateral received goes into a force-settlement fund\n * No new margin positions can be created for this asset\n * Force settlement happens without delay at the swan price, deducting from force-settlement fund\n*/\nvoid database::globally_settle_asset( const asset_object& mia, const price& settlement_price,\n                                      bool check_margin_calls )\n{\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_1669 = ( maint_time <= HARDFORK_CORE_1669_TIME ); // whether to use call_price\n\n   if( before_core_hardfork_1669 )\n   {\n      globally_settle_asset_impl( mia, settlement_price,\n                                  get_index_type<call_order_index>().indices().get<by_price>(),\n                                  check_margin_calls );\n   }\n   else\n   {\n      // Note: it is safe to iterate here even if there is no call order due to individual settlements\n      globally_settle_asset_impl( mia, settlement_price,\n                                  get_index_type<call_order_index>().indices().get<by_collateral>(),\n                                  check_margin_calls );\n   }\n}\n\ntemplate<typename IndexType>\nvoid database::globally_settle_asset_impl( const asset_object& mia,\n                                           const price& settlement_price,\n                                           const IndexType& call_index,\n                                           bool check_margin_calls )\n{ try {\n   const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);\n   // GCOVR_EXCL_START\n   // Defensive code, normally it should not fail\n   FC_ASSERT( !bitasset.is_globally_settled(), \"black swan already occurred, it should not happen again\" );\n   // GCOVR_EXCL_STOP\n\n   asset collateral_gathered( 0, bitasset.options.short_backing_asset );\n\n   const asset_dynamic_data_object& mia_dyn = mia.dynamic_asset_data_id(*this);\n   auto original_mia_supply = mia_dyn.current_supply;\n\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding\n\n   // cancel all call orders and accumulate it into collateral_gathered\n   auto call_itr = call_index.lower_bound( price::min( bitasset.options.short_backing_asset, bitasset.asset_id ) );\n   auto call_end = call_index.upper_bound( price::max( bitasset.options.short_backing_asset, bitasset.asset_id ) );\n\n   auto margin_end = call_end;\n   bool is_margin_call = false;\n   price call_pays_price = settlement_price;\n   price fund_receives_price = settlement_price;\n   if( check_margin_calls )\n   {\n      margin_end = call_index.upper_bound( bitasset.current_maintenance_collateralization );\n      // Note: settlement_price is in debt / collateral, here the fund gets less collateral\n      fund_receives_price = settlement_price * ratio_type( bitasset.current_feed.maximum_short_squeeze_ratio,\n                                                           GRAPHENE_COLLATERAL_RATIO_DENOM );\n      if( call_itr != margin_end )\n         is_margin_call = true;\n   }\n   asset margin_call_fee( 0, bitasset.options.short_backing_asset );\n\n   asset pays;\n   while( call_itr != call_end )\n   {\n      if( is_margin_call && call_itr == margin_end )\n      {\n         is_margin_call = false;\n         call_pays_price = fund_receives_price;\n      }\n\n      const call_order_object& order = *call_itr;\n      ++call_itr;\n\n      auto order_debt = order.get_debt();\n      if( before_core_hardfork_342 )\n         pays = order_debt * call_pays_price; // round down, in favor of call order\n      else\n         pays = order_debt.multiply_and_round_up( call_pays_price ); // round up in favor of global-settle fund\n\n      if( pays > order.get_collateral() )\n         pays = order.get_collateral();\n\n      if( is_margin_call )\n      {\n         auto fund_receives = order_debt.multiply_and_round_up( fund_receives_price );\n         if( fund_receives > pays )\n            fund_receives = pays;\n         margin_call_fee = pays - fund_receives;\n         collateral_gathered += fund_receives;\n      }\n      else\n      {\n         margin_call_fee.amount = 0;\n         collateral_gathered += pays;\n      }\n\n      // call order is maker\n      FC_ASSERT( fill_call_order( order, pays, order_debt, fund_receives_price, true, margin_call_fee, false ),\n                 \"Internal error: unable to close margin call ${o}\", (\"o\", order) );\n   }\n\n   // Remove the individual settlement order\n   const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id );\n   if( limit_ptr )\n      remove( *limit_ptr );\n\n   // Move individual settlement fund to the GS fund\n   collateral_gathered.amount += bitasset.individual_settlement_fund;\n\n   modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){\n      obj.options.extensions.value.black_swan_response_method.reset(); // Update BSRM to GS\n      obj.current_feed = obj.median_feed; // reset current feed price if was capped\n      obj.individual_settlement_debt = 0;\n      obj.individual_settlement_fund = 0;\n      obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered;\n      obj.settlement_fund  = collateral_gathered.amount;\n   });\n\n} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } // GCOVR_EXCL_LINE\n\nvoid database::individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& order )\n{\n   FC_ASSERT( bitasset.asset_id == order.debt_type(), \"Internal error: asset type mismatch\" );\n\n   using bsrm_type = bitasset_options::black_swan_response_type;\n   const auto bsrm = bitasset.get_black_swan_response_method();\n   FC_ASSERT( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm,\n              \"Internal error: Invalid BSRM\" );\n\n   auto order_debt = order.get_debt();\n   auto order_collateral = order.get_collateral();\n   auto fund_receives_price = (~order.collateralization()) / bitasset.get_margin_call_pays_ratio();\n   auto fund_receives = order_debt.multiply_and_round_up( fund_receives_price );\n   if( fund_receives.amount > order.collateral ) // should not happen, just be defensive\n      fund_receives.amount = order.collateral;\n\n   auto margin_call_fee = order_collateral - fund_receives;\n\n   modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){\n      obj.individual_settlement_debt += order.debt;\n      obj.individual_settlement_fund += fund_receives.amount;\n   });\n\n   if( bsrm_type::individual_settlement_to_order == bsrm ) // settle to order\n   {\n      const auto& head_time = head_block_time();\n      bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP)\n\n      const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id );\n      if( limit_ptr )\n      {\n         modify( *limit_ptr, [after_core_hardfork_2591,&bitasset]( limit_order_object& obj ) {\n            // TODO fix duplicate code\n            bool sell_all = true;\n            if( after_core_hardfork_2591 )\n            {\n               obj.sell_price = ~bitasset.get_margin_call_order_price();\n               asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() );\n               try\n               {\n                  obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount; // may overflow\n                  // Note: the \"=\" below is for the consistency of order matching logic\n                  if( obj.for_sale <= bitasset.individual_settlement_fund )\n                     sell_all = false;\n               }\n               catch( fc::exception& e ) // catch the overflow\n               {\n                  // do nothing\n                  dlog( e.to_detail_string() );\n               }\n            }\n            if( sell_all )\n            {\n               obj.for_sale = bitasset.individual_settlement_fund;\n               obj.sell_price = ~bitasset.get_individual_settlement_price();\n            }\n         } );\n      }\n      else\n      {\n         create< limit_order_object >( [&order_debt,&fund_receives]( limit_order_object& obj ) {\n            obj.expiration = time_point_sec::maximum();\n            obj.seller = GRAPHENE_NULL_ACCOUNT;\n            obj.for_sale = fund_receives.amount;\n            obj.sell_price = fund_receives / order_debt;\n            obj.is_settled_debt = true;\n         } );\n      }\n      // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders\n   }\n\n   // call order is maker\n   FC_ASSERT( fill_call_order( order, order_collateral, order_debt,\n                               fund_receives_price, true, margin_call_fee, false ),\n              \"Internal error: unable to close margin call ${o}\", (\"o\", order) );\n\n   // Update current feed if needed\n   if( bsrm_type::individual_settlement_to_fund == bsrm )\n      update_bitasset_current_feed( bitasset, true );\n\n}\n\nvoid database::revive_bitasset( const asset_object& bitasset, const asset_bitasset_data_object& bad )\n{ try {\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( bitasset.is_market_issued() );\n   FC_ASSERT( bitasset.id == bad.asset_id );\n   FC_ASSERT( bad.is_globally_settled() );\n   FC_ASSERT( !bad.is_prediction_market );\n   FC_ASSERT( !bad.current_feed.settlement_price.is_null() );\n   // GCOVR_EXCL_STOP\n\n   const asset_dynamic_data_object& bdd = bitasset.dynamic_asset_data_id(*this);\n   if( bdd.current_supply > 0 )\n   {\n      // Create + execute a \"bid\" with 0 additional collateral\n      const collateral_bid_object& pseudo_bid = create<collateral_bid_object>(\n               [&bitasset,&bad,&bdd](collateral_bid_object& bid) {\n         bid.bidder = bitasset.issuer;\n         bid.inv_swan_price = asset(0, bad.options.short_backing_asset)\n                              / asset(bdd.current_supply, bad.asset_id);\n      });\n      execute_bid( pseudo_bid, bdd.current_supply, bad.settlement_fund, bad.current_feed );\n   } else\n      FC_ASSERT( bad.settlement_fund == 0 );\n\n   _cancel_bids_and_revive_mpa( bitasset, bad );\n} FC_CAPTURE_AND_RETHROW( (bitasset) ) } // GCOVR_EXCL_LINE\n\nvoid database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad )\n{ try {\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( bitasset.is_market_issued() );\n   FC_ASSERT( bad.is_globally_settled() );\n   FC_ASSERT( !bad.is_prediction_market );\n   // GCOVR_EXCL_STOP\n\n   // cancel remaining bids\n   const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get<by_price>();\n   auto itr = bid_idx.lower_bound( bad.asset_id );\n   const auto end = bid_idx.upper_bound( bad.asset_id );\n   while( itr != end )\n   {\n      const collateral_bid_object& bid = *itr;\n      ++itr;\n      cancel_bid( bid );\n   }\n\n   // revive\n   modify( bad, []( asset_bitasset_data_object& obj ){\n              obj.settlement_price = price();\n              obj.settlement_fund = 0;\n           });\n} FC_CAPTURE_AND_RETHROW( (bitasset) ) } // GCOVR_EXCL_LINE\n\nvoid database::cancel_bid(const collateral_bid_object& bid, bool create_virtual_op)\n{\n   adjust_balance(bid.bidder, bid.inv_swan_price.base);\n\n   if( create_virtual_op )\n   {\n      bid_collateral_operation vop;\n      vop.bidder = bid.bidder;\n      vop.additional_collateral = bid.inv_swan_price.base;\n      vop.debt_covered = asset( 0, bid.inv_swan_price.quote.asset_id );\n      push_applied_operation( vop );\n   }\n   remove(bid);\n}\n\nvoid database::execute_bid( const collateral_bid_object& bid, share_type debt_covered,\n                            share_type collateral_from_fund, const price_feed& current_feed )\n{\n   const call_order_object& call_obj = create<call_order_object>(\n               [&bid, &debt_covered, &collateral_from_fund, &current_feed, this](call_order_object& call ){\n         call.borrower = bid.bidder;\n         call.collateral = bid.inv_swan_price.base.amount + collateral_from_fund;\n         call.debt = debt_covered;\n         // don't calculate call_price after core-1270 hard fork\n         if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME )\n            // bid.inv_swan_price is in collateral / debt\n            call.call_price = price( asset( 1, bid.inv_swan_price.base.asset_id ),\n                                     asset( 1, bid.inv_swan_price.quote.asset_id ) );\n         else\n            call.call_price = price::call_price( asset(debt_covered, bid.inv_swan_price.quote.asset_id),\n                                                 asset(call.collateral, bid.inv_swan_price.base.asset_id),\n                                                 current_feed.maintenance_collateral_ratio );\n      });\n\n   // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders\n   if( bid.inv_swan_price.base.asset_id == asset_id_type() )\n      modify( get_account_stats_by_owner(bid.bidder), [&call_obj](account_statistics_object& stats) {\n         stats.total_core_in_orders += call_obj.collateral;\n      });\n\n   push_applied_operation( execute_bid_operation( bid.bidder,\n                                                  asset( debt_covered, bid.inv_swan_price.quote.asset_id ),\n                                                  asset( call_obj.collateral, bid.inv_swan_price.base.asset_id ) ) );\n\n   remove(bid);\n}\n\nvoid database::cancel_settle_order( const force_settlement_object& order )\n{\n   adjust_balance(order.owner, order.balance);\n\n   push_applied_operation( asset_settle_cancel_operation( order.get_id(), order.owner, order.balance ) );\n\n   remove(order);\n}\n\nvoid database::cancel_limit_order( const limit_order_object& order, bool create_virtual_op, bool skip_cancel_fee )\n{\n   // if need to create a virtual op, try deduct a cancellation fee here.\n   // there are two scenarios when order is cancelled and need to create a virtual op:\n   // 1. due to expiration: always deduct a fee if there is any fee deferred\n   // 2. due to cull_small: deduct a fee after hard fork 604, but not before (will set skip_cancel_fee)\n   const account_statistics_object* seller_acc_stats = nullptr;\n   const asset_dynamic_data_object* deferred_fee_asset_dyn_data = nullptr;\n   limit_order_cancel_operation vop;\n   share_type deferred_fee = order.deferred_fee;\n   asset deferred_paid_fee = order.deferred_paid_fee;\n   if( create_virtual_op )\n   {\n      vop.order = order.id;\n      vop.fee_paying_account = order.seller;\n      // only deduct fee if not skipping fee, and there is any fee deferred\n      if( !skip_cancel_fee && deferred_fee > 0 )\n      {\n         asset core_cancel_fee = current_fee_schedule().calculate_fee( vop );\n         // cap the fee\n         if( core_cancel_fee.amount > deferred_fee )\n            core_cancel_fee.amount = deferred_fee;\n         // if there is any CORE fee to deduct, redirect it to referral program\n         if( core_cancel_fee.amount > 0 )\n         {\n            seller_acc_stats = &get_account_stats_by_owner( order.seller );\n            modify( *seller_acc_stats, [&core_cancel_fee, this]( account_statistics_object& obj ) {\n               obj.pay_fee( core_cancel_fee.amount, get_global_properties().parameters.cashback_vesting_threshold );\n            } );\n            deferred_fee -= core_cancel_fee.amount;\n            // handle originally paid fee if any:\n            //    to_deduct = round_up( paid_fee * core_cancel_fee / deferred_core_fee_before_deduct )\n            if( deferred_paid_fee.amount == 0 )\n            {\n               vop.fee = core_cancel_fee;\n            }\n            else\n            {\n               fc::uint128_t fee128( deferred_paid_fee.amount.value );\n               fee128 *= core_cancel_fee.amount.value;\n               // to round up\n               fee128 += order.deferred_fee.value;\n               fee128 -= 1;\n               fee128 /= order.deferred_fee.value;\n               share_type cancel_fee_amount = static_cast<int64_t>(fee128);\n               // cancel_fee should be positive, pay it to asset's accumulated_fees\n               deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this);\n               modify( *deferred_fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) {\n                  addo.accumulated_fees += cancel_fee_amount;\n               });\n               // cancel_fee should be no more than deferred_paid_fee\n               deferred_paid_fee.amount -= cancel_fee_amount;\n               vop.fee = asset( cancel_fee_amount, deferred_paid_fee.asset_id );\n            }\n         }\n      }\n   }\n\n   // refund funds in order\n   auto refunded = order.amount_for_sale();\n   if( refunded.asset_id == asset_id_type() )\n   {\n      if( !seller_acc_stats )\n         seller_acc_stats = &get_account_stats_by_owner( order.seller );\n      modify( *seller_acc_stats, [&refunded]( account_statistics_object& obj ) {\n         obj.total_core_in_orders -= refunded.amount;\n      });\n   }\n   adjust_balance(order.seller, refunded);\n\n   // refund fee\n   // could be virtual op or real op here\n   if( order.deferred_paid_fee.amount == 0 )\n   {\n      // be here, order.create_time <= HARDFORK_CORE_604_TIME, or fee paid in CORE, or no fee to refund.\n      // if order was created before hard fork 604 then cancelled no matter before or after hard fork 604,\n      //    see it as fee paid in CORE, deferred_fee should be refunded to order owner but not fee pool\n      adjust_balance( order.seller, deferred_fee );\n   }\n   else // need to refund fee in originally paid asset\n   {\n      adjust_balance(order.seller, deferred_paid_fee);\n      // be here, must have: fee_asset != CORE\n      if( !deferred_fee_asset_dyn_data )\n         deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this);\n      modify( *deferred_fee_asset_dyn_data, [&deferred_fee](asset_dynamic_data_object& addo) {\n         addo.fee_pool += deferred_fee;\n      });\n   }\n\n   if( create_virtual_op )\n   {\n      auto op_id = push_applied_operation( vop );\n      set_applied_operation_result( op_id, refunded );\n   }\n\n   cleanup_and_remove_limit_order( order );\n}\n\nvoid database::cleanup_and_remove_limit_order( const limit_order_object& order )\n{\n   // Unlink the linked take profit order if it exists\n   if( order.take_profit_order_id.valid() )\n   {\n      const auto& take_profit_order = (*order.take_profit_order_id)(*this);\n      modify( take_profit_order, []( limit_order_object& loo ) {\n         loo.take_profit_order_id.reset();\n      });\n   }\n\n   remove(order);\n}\n\nbool maybe_cull_small_order( database& db, const limit_order_object& order )\n{\n   /**\n    *  There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we\n    *  have hit the limit where the seller is asking for nothing in return.  When this\n    *  happens we must refund any balance back to the seller, it is too small to be\n    *  sold at the sale price.\n    *\n    *  If the order is a taker order (as opposed to a maker order), so the price is\n    *  set by the counterparty, this check is deferred until the order becomes unmatched\n    *  (see #555) -- however, detecting this condition is the responsibility of the caller.\n    */\n   if( order.amount_to_receive().amount == 0 )\n   {\n      if( order.deferred_fee > 0 && db.head_block_time() <= HARDFORK_CORE_604_TIME )\n      {\n         db.cancel_limit_order( order, true, true );\n      }\n      else\n         db.cancel_limit_order( order );\n      return true;\n   }\n   return false;\n}\n\n// Note: optimizations have been done in apply_order(...)\nbool database::apply_order_before_hardfork_625(const limit_order_object& new_order_object)\n{\n   auto order_id = new_order_object.id;\n   const asset_object& sell_asset = get(new_order_object.amount_for_sale().asset_id);\n   const asset_object& receive_asset = get(new_order_object.amount_to_receive().asset_id);\n\n   // Possible optimization: We only need to check calls if both are true:\n   //  - The new order is at the front of the book\n   //  - The new order is below the call limit price\n   bool called_some = check_call_orders(sell_asset, true, true); // the first time when checking, call order is maker\n   bool called_some_else = check_call_orders(receive_asset, true, true); // the other side, same as above\n   if( ( called_some || called_some_else ) && !find_object(order_id) ) // then we were filled by call order\n      return true;\n\n   const auto& limit_price_idx = get_index_type<limit_order_index>().indices().get<by_price>();\n\n   // it should be possible to simply check the NEXT/PREV iterator after new_order_object to\n   // determine whether or not this order has \"changed the book\" in a way that requires us to\n   // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs\n   // constant time check. Potential optimization.\n\n   auto max_price = ~new_order_object.sell_price;\n   auto limit_itr = limit_price_idx.lower_bound(max_price.max());\n   auto limit_end = limit_price_idx.upper_bound(max_price);\n\n   bool finished = false;\n   while( !finished && limit_itr != limit_end )\n   {\n      auto old_limit_itr = limit_itr;\n      ++limit_itr;\n      // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop.\n      finished = ( match(new_order_object, *old_limit_itr, old_limit_itr->sell_price)\n                   != match_result_type::only_maker_filled );\n   }\n\n   // Possible optimization: only check calls if the new order completely filled some old order.\n   // Do I need to check both assets?\n   check_call_orders(sell_asset); // after the new limit order filled some orders on the book,\n                                  // if a call order matches another order, the call order is taker\n   check_call_orders(receive_asset); // the other side, same as above\n\n   const limit_order_object* updated_order_object = find< limit_order_object >( order_id );\n   if( !updated_order_object )\n      return true;\n   if( head_block_time() <= HARDFORK_555_TIME )\n      return false;\n   // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order()\n   // being called by match() above\n   // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that\n   // was done too eagerly before, and\n   // this is the point it's deferred to.\n   return maybe_cull_small_order( *this, *updated_order_object );\n}\n\n/***\n * @brief apply a new limit_order_object to the market, matching with existing limit orders or\n *    margin call orders where possible, leaving remainder on the book if not fully matched.\n * @detail Called from limit_order_create_evaluator::do_apply() in market_evaluator.cpp in\n *    response to a limit_order_create operation.  If we're not at the front of the book, we\n *    return false early and do nothing else, since there's nothing we can match.  If we are at\n *    the front of the book, then we first look for matching limit orders that are more\n *    favorable than the margin call price, then we search through active margin calls, then\n *    finaly the remaining limit orders, until we either fully consume the order or can no\n *    longer match and must leave the remainder on the book.\n * @return Returns true if limit order is completely consumed by matching, else false if it\n *    remains on the book.\n * @param new_order_object the new limit order (read only ref, though the corresponding db\n *    object is modified as we match and deleted if filled completely)\n */\nbool database::apply_order(const limit_order_object& new_order_object)\n{\n   auto order_id = new_order_object.id;\n   asset_id_type sell_asset_id = new_order_object.sell_asset_id();\n   asset_id_type recv_asset_id = new_order_object.receive_asset_id();\n\n   // We only need to check if the new order will match with others if it is at the front of the book\n   const auto& limit_price_idx = get_index_type<limit_order_index>().indices().get<by_price>();\n   auto limit_itr = limit_price_idx.iterator_to( new_order_object );\n   if( limit_itr != limit_price_idx.begin() )\n   {\n      --limit_itr;\n      if( limit_itr->sell_asset_id() == sell_asset_id && limit_itr->receive_asset_id() == recv_asset_id )\n         return false;\n   }\n\n   // this is the opposite side (on the book)\n   auto max_price = ~new_order_object.sell_price;\n   limit_itr = limit_price_idx.lower_bound( max_price.max() );\n   auto limit_end = limit_price_idx.upper_bound( max_price );\n\n   // Order matching should be in favor of the taker.\n   // When a new limit order is created, e.g. an ask, need to check if it will match the highest bid.\n   // We were checking call orders first. However, due to MSSR (maximum_short_squeeze_ratio),\n   // effective price of call orders may be worse than limit orders, so we should also check limit orders here.\n\n   // Question: will a new limit order trigger a black swan event?\n   //\n   // 1. as of writing, it's possible due to the call-order-and-limit-order overlapping issue:\n   //       https://github.com/bitshares/bitshares-core/issues/606 .\n   //    when it happens, a call order can be very big but don't match with the opposite,\n   //    even when price feed is too far away, further than swan price,\n   //    if the new limit order is in the same direction with the call orders, it can eat up all the opposite,\n   //    then the call order will lose support and trigger a black swan event.\n   // 2. after issue 606 is fixed, there will be no limit order on the opposite side \"supporting\" the call order,\n   //    so a new order in the same direction with the call order won't trigger a black swan event.\n   // 3. calling is one direction. if the new limit order is on the opposite direction,\n   //    no matter if matches with the call, it won't trigger a black swan event.\n   //    (if a match at MSSP caused a black swan event, it means the call order is already undercollateralized,\n   //      which should trigger a black swan event earlier.)\n   //\n   // Since it won't trigger a black swan, no need to check here.\n\n   // currently we don't do cross-market (triangle) matching.\n   // the limit order will only match with a call order if meet all of these:\n   // 1. it's buying collateral, which means sell_asset is the MIA, receive_asset is the backing asset.\n   // 2. sell_asset is not a prediction market\n   // 3. sell_asset is not globally settled\n   // 4. sell_asset has a valid price feed\n   // 5. the call order's collateral ratio is below or equals to MCR\n   // 6. the limit order provided a good price\n\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue\n\n   bool to_check_call_orders = false;\n   const asset_object& sell_asset = sell_asset_id( *this );\n   const asset_bitasset_data_object* sell_abd = nullptr;\n   price call_match_price;  // Price at which margin calls sit on the books. Prior to BSIP-74 this price is\n                            // same as the MSSP. After, it is the MCOP, which may deviate from MSSP due to MCFR.\n   price call_pays_price;   // Price margin call actually relinquishes collateral at. Equals the MSSP and it may\n                            // differ from call_match_price if there is a Margin Call Fee.\n   if( sell_asset.is_market_issued() )\n   {\n      sell_abd = &sell_asset.bitasset_data( *this );\n      if( sell_abd->options.short_backing_asset == recv_asset_id\n          && !sell_abd->is_prediction_market\n          && !sell_abd->is_globally_settled()\n          && !sell_abd->current_feed.settlement_price.is_null() )\n      {\n         if( before_core_hardfork_1270 ) {\n            call_match_price = ~sell_abd->current_feed.max_short_squeeze_price_before_hf_1270();\n            call_pays_price = call_match_price;\n         } else {\n            call_match_price = ~sell_abd->get_margin_call_order_price();\n            call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price();\n         }\n         if( ~new_order_object.sell_price <= call_match_price ) // If new limit order price is good enough to\n            to_check_call_orders = true;                        // match a call, then check if there are calls.\n      }\n   }\n\n   bool finished = false; // whether the new order is gone\n   bool feed_price_updated = false; // whether current_feed.settlement_price has been updated\n   if( to_check_call_orders )\n   {\n      // check limit orders first, match the ones with better price in comparison to call orders\n      auto limit_itr_after_call = limit_price_idx.lower_bound( call_match_price );\n      while( !finished && limit_itr != limit_itr_after_call )\n      {\n         const limit_order_object& matching_limit_order = *limit_itr;\n         ++limit_itr;\n         // match returns 2 when only the old order was fully filled.\n         // In this case, we keep matching; otherwise, we stop.\n         finished = ( match( new_order_object, matching_limit_order, matching_limit_order.sell_price )\n                      != match_result_type::only_maker_filled );\n      }\n\n      auto call_min = price::min( recv_asset_id, sell_asset_id );\n      if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf\n      {\n         // check if there are margin calls\n         // Note: it is safe to iterate here even if there is no call order due to individual settlements\n         const auto& call_collateral_idx = get_index_type<call_order_index>().indices().get<by_collateral>();\n         // Note: when BSRM is no_settlement, current_feed can change after filled a call order,\n         //       so we recalculate inside the loop\n         using bsrm_type = bitasset_options::black_swan_response_type;\n         auto bsrm = sell_abd->get_black_swan_response_method();\n         bool update_call_price = ( bsrm_type::no_settlement == bsrm && sell_abd->is_current_feed_price_capped() );\n         auto old_current_feed_price = sell_abd->current_feed.settlement_price;\n         while( !finished )\n         {\n            // hard fork core-343 and core-625 took place at same time,\n            // always check call order with least collateral ratio\n            auto call_itr = call_collateral_idx.lower_bound( call_min );\n            if( call_itr == call_collateral_idx.end()\n                  || call_itr->debt_type() != sell_asset_id\n                  // feed protected https://github.com/cryptonomex/graphene/issues/436\n                  || call_itr->collateralization() > sell_abd->current_maintenance_collateralization )\n               break;\n            // hard fork core-338 and core-625 took place at same time, not checking HARDFORK_CORE_338_TIME here.\n            const auto match_result = match( new_order_object, *call_itr, call_match_price,\n                                             *sell_abd, call_pays_price );\n            // match returns 1 or 3 when the new order was fully filled.\n            // In this case, we stop matching; otherwise keep matching.\n            // since match can return 0 due to BSIP38 (hf core-834), we no longer only check if the result is 2.\n            if( match_result_type::only_taker_filled == match_result\n                  || match_result_type::both_filled == match_result )\n               finished = true;\n            else if( update_call_price )\n            {\n               call_match_price = ~sell_abd->get_margin_call_order_price();\n               call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price();\n               update_call_price = sell_abd->is_current_feed_price_capped();\n               // Since current feed price (in debt/collateral) can only decrease after updated, if there still\n               // exists a call order in margin call territory, it would be on the top of the order book,\n               // so no need to check if the current limit (buy) order would match another limit (sell) order atm.\n               // On the other hand, the current limit order is on the top of the other side of the order book.\n            }\n         } // while !finished\n         if( bsrm_type::no_settlement == bsrm && sell_abd->current_feed.settlement_price != old_current_feed_price )\n            feed_price_updated = true;\n      } // if after core-1270 hf\n      else if( !finished ) // and before core-1270 hard fork\n      {\n         // check if there are margin calls\n         const auto& call_price_idx = get_index_type<call_order_index>().indices().get<by_price>();\n         while( !finished )\n         {\n            // assume hard fork core-343 and core-625 will take place at same time,\n            // always check call order with least call_price\n            auto call_itr = call_price_idx.lower_bound( call_min );\n            if( call_itr == call_price_idx.end()\n                  || call_itr->debt_type() != sell_asset_id\n                  // feed protected https://github.com/cryptonomex/graphene/issues/436\n                  || call_itr->call_price > ~sell_abd->current_feed.settlement_price )\n               break;\n            // assume hard fork core-338 and core-625 will take place at same time,\n            // not checking HARDFORK_CORE_338_TIME here.\n            const auto match_result = match( new_order_object, *call_itr, call_match_price, *sell_abd );\n            // match returns 1 or 3 when the new order was fully filled.\n            // In this case, we stop matching; otherwise keep matching.\n            // since match can return 0 due to BSIP38 (hard fork core-834),\n            // we no longer only check if the result is 2.\n            if( match_result_type::only_taker_filled == match_result\n                  || match_result_type::both_filled == match_result )\n               finished = true;\n         } // while !finished\n      } // if before core-1270 hf\n   } // if to check call\n\n   // still need to check limit orders\n   while( !finished && limit_itr != limit_end )\n   {\n      const limit_order_object& matching_limit_order = *limit_itr;\n      ++limit_itr;\n      // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop.\n      finished = ( match( new_order_object, matching_limit_order, matching_limit_order.sell_price )\n                   != match_result_type::only_maker_filled );\n   }\n\n   bool limit_order_is_gone = true;\n   const limit_order_object* updated_order_object = find< limit_order_object >( order_id );\n   if( updated_order_object )\n      // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order()\n      // being called by match() above\n      // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that\n      // was done too eagerly before, and\n      // this is the point it's deferred to.\n      limit_order_is_gone = maybe_cull_small_order( *this, *updated_order_object );\n\n   if( limit_order_is_gone && feed_price_updated )\n   {\n      // If current_feed got updated, and the new limit order is gone,\n      // it is possible that other limit orders are able to get filled,\n      // so we need to call check_call_orders()\n      check_call_orders( sell_asset, true, false, sell_abd );\n   }\n\n   return limit_order_is_gone;\n}\n\nvoid database::apply_force_settlement( const force_settlement_object& new_settlement,\n                                       const asset_bitasset_data_object& bitasset,\n                                       const asset_object& asset_obj )\n{\n   // Defensive checks\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( HARDFORK_CORE_2481_PASSED( maint_time ), \"Internal error: hard fork core-2481 not passed\" );\n   FC_ASSERT( new_settlement.balance.asset_id == bitasset.asset_id, \"Internal error: asset type mismatch\" );\n   FC_ASSERT( !bitasset.is_prediction_market, \"Internal error: asset is a prediction market\" );\n   FC_ASSERT( !bitasset.is_globally_settled(), \"Internal error: asset is globally settled already\" );\n   FC_ASSERT( !bitasset.current_feed.settlement_price.is_null(), \"Internal error: no sufficient price feeds\" );\n   // GCOVR_EXCL_STOP\n\n   auto head_time = head_block_time();\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n   auto new_obj_id = new_settlement.id;\n\n   // Price at which margin calls sit on the books.\n   // It is the MCOP, which may deviate from MSSP due to MCFR.\n   price call_match_price = bitasset.get_margin_call_order_price();\n   // Price margin call actually relinquishes collateral at. Equals the MSSP and it may\n   // differ from call_match_price if there is a Margin Call Fee.\n   price call_pays_price = bitasset.current_feed.max_short_squeeze_price();\n\n   // Note: when BSRM is no_settlement, current_feed can change after filled a call order,\n   //       so we recalculate inside the loop\n   using bsrm_type = bitasset_options::black_swan_response_type;\n   auto bsrm = bitasset.get_black_swan_response_method();\n   bool update_call_price = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() );\n\n   bool finished = false; // whether the new order is gone\n\n   // check if there are margin calls\n   // Note: it is safe to iterate here even if there is no call order due to individual settlements\n   const auto& call_collateral_idx = get_index_type<call_order_index>().indices().get<by_collateral>();\n   auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id );\n   while( !finished )\n   {\n      // always check call order with the least collateral ratio\n      auto call_itr = call_collateral_idx.lower_bound( call_min );\n      // Note: we don't precalculate an iterator with upper_bound() before entering the loop,\n      //       because the upper bound can change after a call order got filled\n      if( call_itr == call_collateral_idx.end()\n            || call_itr->debt_type() != new_settlement.balance.asset_id\n            // feed protected https://github.com/cryptonomex/graphene/issues/436\n            || call_itr->collateralization() > bitasset.current_maintenance_collateralization )\n         break;\n      // TCR applies here\n      auto settle_price = after_core_hardfork_2582 ? bitasset.median_feed.settlement_price\n                                                   : bitasset.current_feed.settlement_price;\n      asset max_debt_to_cover( call_itr->get_max_debt_to_cover( call_pays_price,\n                                                       settle_price,\n                                                       bitasset.current_feed.maintenance_collateral_ratio,\n                                                       bitasset.current_maintenance_collateralization ),\n                               new_settlement.balance.asset_id );\n\n      match( new_settlement, *call_itr, call_pays_price, bitasset, max_debt_to_cover, call_match_price, true );\n\n      // Check whether the new order is gone\n      finished = ( nullptr == find_object( new_obj_id ) );\n\n      if( update_call_price )\n      {\n         // when current_feed is updated, it is possible that there are limit orders able to get filled,\n         // so we need to call check_call_orders(), but skip matching call orders with force settlements\n         check_call_orders( asset_obj, true, false, &bitasset, false, true );\n         if( !finished )\n         {\n            call_match_price = bitasset.get_margin_call_order_price();\n            call_pays_price = bitasset.current_feed.max_short_squeeze_price();\n            update_call_price = bitasset.is_current_feed_price_capped();\n         }\n      }\n   }\n\n}\n\n/// Helper function\nstatic database::match_result_type get_match_result( bool taker_filled, bool maker_filled )\n{\n   int8_t result = 0;\n   if( maker_filled )\n      result += static_cast<int8_t>( database::match_result_type::only_maker_filled );\n   if( taker_filled )\n      result += static_cast<int8_t>( database::match_result_type::only_taker_filled );\n   return static_cast<database::match_result_type>( result );\n}\n\n/**\n *  Matches the two orders, the first parameter is taker, the second is maker.\n *\n *  @return which orders were filled (and thus removed)\n */\ndatabase::match_result_type database::match( const limit_order_object& taker, const limit_order_object& maker,\n                                             const price& match_price )\n{\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( taker.sell_price.quote.asset_id == maker.sell_price.base.asset_id );\n   FC_ASSERT( taker.sell_price.base.asset_id  == maker.sell_price.quote.asset_id );\n   FC_ASSERT( taker.for_sale > 0 && maker.for_sale > 0 );\n   // GCOVR_EXCL_STOP\n\n   return maker.is_settled_debt ? match_limit_settled_debt( taker, maker, match_price )\n                                : match_limit_normal_limit( taker, maker, match_price );\n}\n\n/// Match a normal limit order with another normal limit order\ndatabase::match_result_type database::match_limit_normal_limit( const limit_order_object& taker,\n                               const limit_order_object& maker, const price& match_price )\n{\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( !maker.is_settled_debt, \"Internal error: maker is settled debt\" );\n   FC_ASSERT( !taker.is_settled_debt, \"Internal error: taker is settled debt\" );\n   // GCOVR_EXCL_STOP\n\n   auto taker_for_sale = taker.amount_for_sale();\n   auto maker_for_sale = maker.amount_for_sale();\n\n   asset taker_pays;\n   asset taker_receives;\n   asset maker_pays;\n   asset maker_receives;\n\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding\n\n   bool cull_taker = false;\n   if( taker_for_sale <= ( maker_for_sale * match_price ) ) // rounding down here should be fine\n   {\n      taker_receives  = taker_for_sale * match_price; // round down, in favor of bigger order\n\n      // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop.\n      // In this case, we see it as filled and cancel it later\n      if( taker_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME )\n         return match_result_type::only_taker_filled;\n\n      if( before_core_hardfork_342 )\n         maker_receives = taker_for_sale;\n      else\n      {\n         // The remaining amount in order `taker` would be too small,\n         //   so we should cull the order in fill_limit_order() below.\n         // The order would receive 0 even at `match_price`, so it would receive 0 at its own price,\n         //   so calling maybe_cull_small() will always cull it.\n         maker_receives = taker_receives.multiply_and_round_up( match_price );\n         cull_taker = true;\n      }\n   }\n   else\n   {\n      //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); // check\n      //This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp\n      //Although taker_for_sale is greater than maker_for_sale * match_price,\n      //         maker_for_sale == taker_for_sale * match_price\n      //Removing the assert seems to be safe -- apparently no asset is created or destroyed.\n\n      // The maker won't be paying something for nothing, since if it would, it would have been cancelled already.\n      maker_receives = maker_for_sale * match_price; // round down, in favor of bigger order\n      if( before_core_hardfork_342 )\n         taker_receives = maker_for_sale;\n      else\n         // The remaining amount in order `maker` would be too small,\n         //   so the order will be culled in fill_limit_order() below\n         taker_receives = maker_receives.multiply_and_round_up( match_price );\n   }\n\n   maker_pays = taker_receives;\n   taker_pays  = maker_receives;\n\n   if( before_core_hardfork_342 )\n      FC_ASSERT( taker_pays == taker.amount_for_sale() ||\n                 maker_pays == maker.amount_for_sale() );\n\n   // the first param of match() is taker\n   bool taker_filled = fill_limit_order( taker, taker_pays, taker_receives, cull_taker, match_price, false );\n   // the second param of match() is maker\n   bool maker_filled = fill_limit_order( maker, maker_pays, maker_receives, true, match_price, true );\n\n   match_result_type result = get_match_result( taker_filled, maker_filled );\n   FC_ASSERT( result != match_result_type::none_filled );\n   return result;\n}\n\n/// When matching a limit order against settled debt, the maker actually behaviors like a call order\ndatabase::match_result_type database::match_limit_settled_debt( const limit_order_object& taker,\n                               const limit_order_object& maker, const price& match_price )\n{\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( maker.is_settled_debt, \"Internal error: maker is not settled debt\" );\n   FC_ASSERT( !taker.is_settled_debt, \"Internal error: taker is settled debt\" );\n   // GCOVR_EXCL_STOP\n\n   bool cull_taker = false;\n   bool maker_filled = false;\n\n   const auto& mia = maker.receive_asset_id()(*this);\n   const auto& bitasset = mia.bitasset_data(*this);\n\n   auto usd_for_sale = taker.amount_for_sale();\n   auto usd_to_buy = asset( bitasset.individual_settlement_debt, maker.receive_asset_id() );\n\n   asset call_receives;\n   asset order_receives;\n   if( usd_to_buy > usd_for_sale )\n   {  // fill taker limit order\n      order_receives  = usd_for_sale * match_price; // round down here, in favor of \"call order\"\n\n      // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop.\n      // In this case, we see it as filled and cancel it later\n      if( order_receives.amount == 0 )\n         return match_result_type::only_taker_filled;\n\n      // The remaining amount in the limit order could be too small,\n      //   so we should cull the order in fill_limit_order() below.\n      // If the order would receive 0 even at `match_price`, it would receive 0 at its own price,\n      //   so calling maybe_cull_small() will always cull it.\n      call_receives = order_receives.multiply_and_round_up( match_price );\n      cull_taker = true;\n   }\n   else\n   {  // fill maker \"call order\"\n      call_receives  = usd_to_buy;\n      order_receives = maker.amount_for_sale();\n      maker_filled = true;\n   }\n\n   // seller, pays, receives, ...\n   bool taker_filled = fill_limit_order( taker, call_receives, order_receives, cull_taker, match_price, false );\n\n   const auto& head_time = head_block_time();\n   bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP)\n\n   asset call_pays = order_receives;\n   if( maker_filled ) // Regardless of hf core-2591\n      call_pays.amount = bitasset.individual_settlement_fund;\n   else if( maker.for_sale != bitasset.individual_settlement_fund ) // implies hf core-2591\n      call_pays = call_receives * bitasset.get_individual_settlement_price(); // round down, in favor of \"call order\"\n   if( call_pays < order_receives ) // be defensive, maybe unnecessary\n   { // GCOVR_EXCL_START\n      wlog( \"Unexpected scene: call_pays < order_receives\" );\n      call_pays = order_receives;\n   } // GCOVR_EXCL_STOP\n   asset collateral_fee = call_pays - order_receives;\n\n   // Reduce current supply, and accumulate collateral fees\n   const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this);\n   modify( mia_ddo, [&call_receives,&collateral_fee]( asset_dynamic_data_object& ao ){\n      ao.current_supply -= call_receives.amount;\n      ao.accumulated_collateral_fees += collateral_fee.amount;\n   });\n\n   // Push fill_order vitual operation\n   // id, seller, pays, receives, ...\n   push_applied_operation( fill_order_operation( maker.id, maker.seller, call_pays, call_receives,\n                                                 collateral_fee, match_price, true ) );\n\n   // Update bitasset data\n   modify( bitasset, [&call_receives,&call_pays]( asset_bitasset_data_object& obj ){\n      obj.individual_settlement_debt -= call_receives.amount;\n      obj.individual_settlement_fund -= call_pays.amount;\n   });\n\n   // Update the maker order\n   // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders\n   if( maker_filled )\n      remove( maker );\n   else\n   {\n      modify( maker, [after_core_hardfork_2591,&bitasset]( limit_order_object& obj ) {\n         if( after_core_hardfork_2591 )\n         {\n            // Note: for simplicity, only update price when necessary\n            asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() );\n            obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount;\n            if( obj.for_sale > bitasset.individual_settlement_fund ) // be defensive, maybe unnecessary\n            { // GCOVR_EXCL_START\n               wlog( \"Unexpected scene: obj.for_sale > bitasset.individual_settlement_fund\" );\n               obj.for_sale = bitasset.individual_settlement_fund;\n               obj.sell_price = ~bitasset.get_individual_settlement_price();\n            } // GCOVR_EXCL_STOP\n         }\n         else\n         {\n            obj.for_sale = bitasset.individual_settlement_fund;\n            obj.sell_price = ~bitasset.get_individual_settlement_price();\n         }\n         // Note: filled_amount is not updated, but it should be fine\n      });\n      // Note:\n      // After the price is updated, it is possible that the order can be matched with another order on the order\n      // book, which may then be matched with more other orders. For simplicity, we don't do more matching here.\n   }\n\n   match_result_type result = get_match_result( taker_filled, maker_filled );\n   return result;\n}\n\n/// When matching a settled debt order against a limit order, the taker actually behaviors like a call order\n// TODO fix duplicate code\ndatabase::match_result_type database::match_settled_debt_limit( const limit_order_object& taker,\n                               const limit_order_object& maker, const price& match_price )\n{\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( !maker.is_settled_debt, \"Internal error: maker is settled debt\" );\n   FC_ASSERT( taker.is_settled_debt, \"Internal error: taker is not settled debt\" );\n   // GCOVR_EXCL_STOP\n\n   bool taker_filled = false;\n\n   const auto& mia = taker.receive_asset_id()(*this);\n   const auto& bitasset = mia.bitasset_data(*this);\n\n   auto usd_for_sale = maker.amount_for_sale();\n   auto usd_to_buy = asset( bitasset.individual_settlement_debt, taker.receive_asset_id() );\n\n   asset call_receives;\n   asset order_receives;\n   if( usd_to_buy > usd_for_sale )\n   {  // fill maker limit order\n      order_receives = usd_for_sale * match_price; // round down here, in favor of call order\n\n      // Be here, the limit order won't be paying something for nothing, since if it would, it would have\n      //   been cancelled elsewhere already (a maker limit order won't be paying something for nothing).\n\n      call_receives = order_receives.multiply_and_round_up( match_price );\n   }\n   else\n   {  // fill taker \"call order\"\n      call_receives = usd_to_buy;\n      order_receives = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of limit order\n      taker_filled = true;\n   }\n\n   asset call_pays = order_receives;\n   if( taker_filled )\n      call_pays.amount = bitasset.individual_settlement_fund;\n   else if( taker.for_sale != bitasset.individual_settlement_fund )\n      call_pays = call_receives * bitasset.get_individual_settlement_price(); // round down, in favor of \"call order\"\n   if( call_pays < order_receives ) // be defensive, maybe unnecessary\n   { // GCOVR_EXCL_START\n      wlog( \"Unexpected scene: call_pays < order_receives\" );\n      call_pays = order_receives;\n   } // GCOVR_EXCL_STOP\n   asset collateral_fee = call_pays - order_receives;\n\n   // Reduce current supply, and accumulate collateral fees\n   const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this);\n   modify( mia_ddo, [&call_receives,&collateral_fee]( asset_dynamic_data_object& ao ){\n      ao.current_supply -= call_receives.amount;\n      ao.accumulated_collateral_fees += collateral_fee.amount;\n   });\n\n   // Push fill_order vitual operation\n   // id, seller, pays, receives, ...\n   push_applied_operation( fill_order_operation( taker.id, taker.seller, call_pays, call_receives,\n                                                 collateral_fee, match_price, false ) );\n\n   // Update bitasset data\n   modify( bitasset, [&call_receives,&call_pays]( asset_bitasset_data_object& obj ){\n      obj.individual_settlement_debt -= call_receives.amount;\n      obj.individual_settlement_fund -= call_pays.amount;\n   });\n\n   // Update the taker order\n   // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders\n   if( taker_filled )\n      remove( taker );\n   else\n   {\n      modify( taker, [&bitasset]( limit_order_object& obj ) {\n         // Note: for simplicity, only update price when necessary\n         asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() );\n         obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount;\n         if( obj.for_sale > bitasset.individual_settlement_fund ) // be defensive, maybe unnecessary\n         { // GCOVR_EXCL_START\n            wlog( \"Unexpected scene: obj.for_sale > bitasset.individual_settlement_fund\" );\n            obj.for_sale = bitasset.individual_settlement_fund;\n            obj.sell_price = ~bitasset.get_individual_settlement_price();\n         } // GCOVR_EXCL_STOP\n         // Note: filled_amount is not updated, but it should be fine\n      });\n   }\n\n   // seller, pays, receives, ...\n   bool maker_filled = fill_limit_order( maker, call_receives, order_receives, true, match_price, true );\n\n   match_result_type result = get_match_result( taker_filled, maker_filled );\n   return result;\n}\n\n\ndatabase::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask,\n                     const price& match_price,\n                     const asset_bitasset_data_object& bitasset,\n                     const price& call_pays_price )\n{\n   FC_ASSERT( bid.sell_asset_id() == ask.debt_type() );\n   FC_ASSERT( bid.receive_asset_id() == ask.collateral_type() );\n   FC_ASSERT( bid.for_sale > 0 && ask.debt > 0 && ask.collateral > 0 );\n\n   bool cull_taker = false;\n\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue\n   bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls\n\n   auto head_time = head_block_time();\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n   const auto& feed_price = after_core_hardfork_2582 ? bitasset.median_feed.settlement_price\n                                                     : bitasset.current_feed.settlement_price;\n   const auto& maintenance_collateral_ratio = bitasset.current_feed.maintenance_collateral_ratio;\n   optional<price> maintenance_collateralization;\n   if( !before_core_hardfork_1270 )\n      maintenance_collateralization = bitasset.current_maintenance_collateralization;\n\n   asset usd_for_sale = bid.amount_for_sale();\n   asset usd_to_buy( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio,\n                                                maintenance_collateralization ),\n                     ask.debt_type() );\n\n   asset call_pays;\n   asset call_receives;\n   asset order_pays;\n   asset order_receives;\n   if( usd_to_buy > usd_for_sale )\n   {  // fill limit order\n      order_receives  = usd_for_sale * match_price; // round down here, in favor of call order\n\n      // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop.\n      // In this case, we see it as filled and cancel it later\n      if( order_receives.amount == 0 )\n         return match_result_type::only_taker_filled;\n\n      call_receives = order_receives.multiply_and_round_up( match_price );\n      if( after_core_hardfork_2481 )\n         call_pays = call_receives * call_pays_price; // calculate with updated call_receives\n      else\n         // TODO add tests about CR change\n         call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74)\n\n      // The remaining amount (if any) in the limit order would be too small,\n      //   so we should cull the order in fill_limit_order() below.\n      // The order would receive 0 even at `match_price`, so it would receive 0 at its own price,\n      //   so calling maybe_cull_small() will always cull it.\n      cull_taker = true;\n   }\n   else\n   {  // fill call order\n      call_receives  = usd_to_buy;\n      order_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up here, in favor of limit order\n      call_pays      = usd_to_buy.multiply_and_round_up( call_pays_price );\n      // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more\n      //       debt asset, it means the call order could be receiving a bit too much less than its value.\n      //       It is a sad thing for the call order, but it is the rule -- when a call order is margin called,\n      //       it does not get more than it borrowed.\n      //       On the other hand, if the call order is not being closed (due to TCR),\n      //       it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it.\n   }\n   order_pays = call_receives;\n\n   // Compute margin call fee (BSIP74). Difference between what the call order pays and the limit order\n   // receives is the margin call fee that is paid by the call order owner to the asset issuer.\n   // Margin call fee should equal = X*MCFR/settle_price, to within rounding error.\n   FC_ASSERT(call_pays >= order_receives);\n   const asset margin_call_fee = call_pays - order_receives;\n\n   bool taker_filled = fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false );\n   bool maker_filled = fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee );\n\n   // Update current_feed after filled call order if needed\n   if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() )\n      update_bitasset_current_feed( bitasset, true );\n\n   // Note: result can be none_filled when call order has target_collateral_ratio option set.\n   match_result_type result = get_match_result( taker_filled, maker_filled );\n   return result;\n}\n\n\nasset database::match( const force_settlement_object& settle,\n                       const call_order_object& call,\n                       const price& match_price,\n                       const asset_bitasset_data_object& bitasset,\n                       const asset& max_settlement,\n                       const price& fill_price,\n                       bool is_margin_call )\n{\n   return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true );\n}\n\nasset database::match( const call_order_object& call,\n                       const force_settlement_object& settle,\n                       const price& match_price,\n                       const asset_bitasset_data_object& bitasset,\n                       const asset& max_settlement,\n                       const price& fill_price )\n{\n   return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false );\n}\n\nasset database::match_impl( const force_settlement_object& settle,\n                            const call_order_object& call,\n                            const price& p_match_price,\n                            const asset_bitasset_data_object& bitasset,\n                            const asset& max_settlement,\n                            const price& p_fill_price,\n                            bool is_margin_call,\n                            bool settle_is_taker )\n{ try {\n   FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id );\n   FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0);\n\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding\n\n   auto settle_for_sale = std::min(settle.balance, max_settlement);\n   auto call_debt = call.get_debt();\n   auto call_collateral = call.get_collateral();\n\n   price match_price = p_match_price;\n   price fill_price = p_fill_price;\n\n   asset call_receives   = std::min(settle_for_sale, call_debt);\n   asset call_pays       = call_receives * match_price; // round down here, in favor of call order, for first check\n                                                        // TODO possible optimization: check need to round up\n                                                        //      or down first\n\n   // Note: when is_margin_call == true, the call order is being margin called,\n   //       match_price is the price that the call order pays,\n   //       fill_price is the price that the settle order receives,\n   //       the difference is the margin-call fee\n\n   asset settle_receives = call_pays;\n   asset settle_pays     = call_receives;\n\n   // Be here, the call order may be paying nothing.\n   bool cull_settle_order = false; // whether need to cancel dust settle order\n   if( maint_time > HARDFORK_CORE_184_TIME && call_pays.amount == 0 )\n   {\n      if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order\n      {\n         call_pays.amount = 1;\n         settle_receives.amount = 1; // Note: no margin-call fee in this case even if is_margin_call\n      }\n      else if( call_receives == settle.balance ) // the settle order is smaller\n      {\n         cancel_settle_order( settle );\n         // If the settle order is canceled, we just return, since nothing else can be done\n         return asset( 0, call_debt.asset_id );\n      }\n      // be here, neither order will be completely filled, perhaps due to max_settlement too small\n      else if( !is_margin_call )\n      {\n         // If the call order is not being margin called, we simply return and continue outside\n         return asset( 0, call_debt.asset_id );\n      }\n      else\n      {\n         // Be here, the call order is being margin called, and it is not being fully covered due to TCR,\n         // and the settle order is big enough.\n         // So the call order is considered as the smaller one, and we should round up call_pays.\n         // We have ( call_receives == max_settlement == call_order.get_max_debt_to_cover() ).\n         // It is guaranteed by call_order.get_max_debt_to_cover() that rounding up call_pays\n         // would not reduce CR of the call order, but would push it to be above MCR.\n         call_pays.amount = 1;\n         settle_receives.amount = 1; // Note: no margin-call fee in this case\n      }\n   } // end : if after the core-184 hf and call_pays.amount == 0\n   else if( !before_core_hardfork_342 && call_pays.amount != 0 )\n   {\n      auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio();\n      // be here, the call order is not paying nothing,\n      // but it is still possible that the settle order is paying more than minimum required due to rounding\n      if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order\n      {\n         call_pays = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of settle order\n         if( is_margin_call ) // implies hf core-2481\n         {\n            if( call_pays.amount > call.collateral ) // CR too low\n            {\n               call_pays.amount = call.collateral;\n               match_price = call_debt / call_collateral;\n               fill_price = match_price / margin_call_pays_ratio;\n            }\n            settle_receives = call_receives.multiply_and_round_up( fill_price );\n         }\n         else // be here, we should have: call_pays <= call_collateral\n         {\n            settle_receives = call_pays; // Note: fill_price is not used in calculation when is_margin_call is false\n         }\n      }\n      else // the call order is not completely filled, due to max_settlement too small or settle order too small\n      {\n         // be here, call_pays has been rounded down\n         if( !is_margin_call )\n         {\n            // it was correct to round down call_pays.\n            // round up here to mitigate rounding issues (hf core-342).\n            // It is important to understand the math that the newly rounded-up call_receives won't be greater than\n            // the old call_receives. And rounding up here would NOT make CR lower.\n            call_receives = call_pays.multiply_and_round_up( match_price );\n         }\n         // the call order is a margin call, implies hf core-2481\n         else if( settle_pays == max_settlement ) // the settle order is larger, but the call order has TCR\n         {\n            // Note: here settle_pays == call_receives\n            call_pays = call_receives.multiply_and_round_up( match_price ); // round up, in favor of settle order\n            settle_receives = call_receives.multiply_and_round_up( fill_price ); // round up\n            // Note: here we do NOT stabilize call_receives since it is done in get_max_debt_to_cover(),\n            //       and it is already the maximum value\n         }\n         else // the call order is a margin call, and the settle order is smaller\n         {\n            // It was correct to round down call_pays. However, it is not the final result.\n            // For margin calls, due to margin call fee, it is fairer to calculate with fill_price first\n            const auto& calculate = [&settle_receives,&settle_pays,&fill_price,&call_receives,&call_pays,&match_price]\n            {\n               settle_receives  = settle_pays * fill_price; // round down here, in favor of call order\n               if( settle_receives.amount != 0 )\n               {\n                  // round up to mitigate rounding issues (hf core-342)\n                  call_receives = settle_receives.multiply_and_round_up( fill_price );\n                  // round down\n                  call_pays = call_receives * match_price;\n               }\n            };\n\n            calculate();\n            if( settle_receives.amount == 0 )\n            {\n               cancel_settle_order( settle );\n               // If the settle order is canceled, we just return, since nothing else can be done\n               return asset( 0, call_debt.asset_id );\n            }\n\n            // check whether the call order can be filled at match_price\n            bool cap_price = false;\n            if( call_pays.amount >= call.collateral ) // CR too low, normally won't be true, just be defensive here\n               cap_price = true;\n            else\n            {\n               auto new_collateral = call_collateral - call_pays;\n               auto new_debt = call_debt - call_receives; // the result is positive due to math\n               if( ( new_collateral / new_debt ) < call.collateralization() ) // if CR would decrease\n                  cap_price = true;\n            }\n\n            if( cap_price ) // match_price is not good, update match price and fill price, then calculate again\n            {\n               match_price = call_debt / call_collateral;\n               fill_price = match_price / margin_call_pays_ratio;\n               calculate();\n               if( settle_receives.amount == 0 )\n               {\n                  // Note: when it is a margin call, max_settlement is max_debt_to_cover.\n                  //       if need to cap price here, max_debt_to_cover should be equal to call_debt.\n                  //       if call pays 0, it means the settle order is really small.\n                  cancel_settle_order( settle );\n                  // If the settle order is canceled, we just return, since nothing else can be done\n                  return asset( 0, call_debt.asset_id );\n               }\n            }\n         } // end : if is_margin_call, else ...\n\n         // be here, we should have: call_pays <= call_collateral\n\n         // if the settle order is too small, mark it to be culled\n         if( settle_pays == settle.balance && call_receives != settle.balance )\n            cull_settle_order = true;\n         // else do nothing, since we can't cull the settle order, or it is already fully filled\n\n         settle_pays = call_receives;\n      }\n   } // end : if after the core-342 hf and call_pays.amount != 0\n   // else : before the core-184 hf or the core-342 hf, do nothing\n\n   /**\n    *  If the least collateralized call position lacks sufficient\n    *  collateral to cover at the match price then this indicates a black\n    *  swan event according to the price feed, but only the market\n    *  can trigger a black swan.  So now we must cancel the forced settlement\n    *  object.\n    */\n   if( before_core_hardfork_342 )\n   {\n      GRAPHENE_ASSERT( call_pays < call_collateral, black_swan_exception, \"\" );\n\n      assert( settle_pays == settle_for_sale || call_receives == call.get_debt() );\n   }\n   // else do nothing, since black swan event won't happen, and the assertion is no longer true\n\n   asset margin_call_fee = call_pays - settle_receives;\n\n   fill_call_order( call, call_pays, call_receives, fill_price, settle_is_taker, margin_call_fee );\n   // do not pay force-settlement fee if the call is being margin called\n   fill_settle_order( settle, settle_pays, settle_receives, fill_price, !settle_is_taker, !is_margin_call );\n\n   // Update current_feed after filled call order if needed\n   if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() )\n      update_bitasset_current_feed( bitasset, true );\n\n   if( cull_settle_order )\n      cancel_settle_order( settle );\n\n   return call_receives;\n} FC_CAPTURE_AND_RETHROW( (p_match_price)(max_settlement)(p_fill_price) // GCOVR_EXCL_LINE\n                          (is_margin_call)(settle_is_taker) ) } // GCOVR_EXCL_LINE\n\noptional<limit_order_id_type> database::process_limit_order_on_fill( const limit_order_object& order,\n                                                                     const asset& order_receives )\n{\n   optional<limit_order_id_type> result;\n   if( order.on_fill.empty() )\n      return result;\n\n   const auto& take_profit_action = order.get_take_profit_action();\n\n   fc::uint128_t amount128( order_receives.amount.value );\n   amount128 *= take_profit_action.size_percent;\n   amount128 += (GRAPHENE_100_PERCENT - 1); // Round up\n   amount128 /= GRAPHENE_100_PERCENT;\n   // GCOVR_EXCL_START\n   // Defensive code, should not happen\n   if( amount128 <= 0 )\n      return result;\n   // GCOVR_EXCL_STOP\n\n   asset for_sale( static_cast<int64_t>( amount128 ), order_receives.asset_id );\n\n   if( order.take_profit_order_id.valid() ) // Update existing take profit order\n   {\n      limit_order_update_operation op;\n      op.seller = order.seller;\n      op.order = *order.take_profit_order_id;\n      op.delta_amount_to_sell = for_sale;\n\n      if( ( time_point_sec::maximum() - take_profit_action.expiration_seconds ) > head_block_time() )\n         op.new_expiration = head_block_time() + take_profit_action.expiration_seconds;\n      else\n         op.new_expiration = time_point_sec::maximum();\n\n      try\n      {\n         if( take_profit_action.fee_asset_id == asset_id_type() )\n            op.fee = current_fee_schedule().calculate_fee( op );\n         else\n            op.fee = current_fee_schedule().calculate_fee( op,\n                        take_profit_action.fee_asset_id(*this).options.core_exchange_rate ); // This may throw\n\n         if( *order.take_profit_order_id > order.get_id() ) //The linked take profit order was generated by this order\n         {\n            // Update order price\n            const auto& take_profit_order = (*order.take_profit_order_id)(*this);\n            for_sale.amount += take_profit_order.for_sale;\n            auto sell_price = (~order.sell_price) * ratio_type( GRAPHENE_100_PERCENT,\n                                 int32_t(GRAPHENE_100_PERCENT) + take_profit_action.spread_percent );\n            auto new_min_to_receive = for_sale.multiply_and_round_up( sell_price ); // This may throw\n            op.new_price = for_sale / new_min_to_receive;\n         }\n         // else do not update order price\n\n         // GCOVR_EXCL_START\n         // Defensive code, should not fail\n         FC_ASSERT( !op.new_price || ( ~(*op.new_price) > order.sell_price ),\n                    \"Internal error: the take profit order should not match the current order\" );\n         // GCOVR_EXCL_STOP\n\n         transaction_evaluation_state eval_state(this);\n         eval_state.skip_limit_order_price_check = true;\n\n         try_push_virtual_operation( eval_state, op );\n      }\n      catch( const fc::exception& e )\n      {\n         // We can in fact get here\n         // e.g. if the selling or receiving asset issuer blacklisted the account,\n         //      or no sufficient balance to pay fees, or undo sessions nested too deeply\n         wlog( \"At block ${n}, failed to process on_fill for limit order ${order}, \"\n               \"automatic action (maybe incomplete) was ${op}, exception was ${e}\",\n               (\"op\", operation(op))(\"order\", order)\n               (\"n\", head_block_num())(\"e\", e.to_detail_string()) );\n      }\n   }\n   else // Create a new take profit order\n   {\n      limit_order_create_operation op;\n      op.seller = order.seller;\n      op.amount_to_sell = for_sale;\n      if( ( time_point_sec::maximum() - take_profit_action.expiration_seconds ) > head_block_time() )\n         op.expiration = head_block_time() + take_profit_action.expiration_seconds;\n      else\n         op.expiration = time_point_sec::maximum();\n      if( take_profit_action.repeat )\n         op.extensions.value.on_fill = order.on_fill;\n\n      try\n      {\n         if( take_profit_action.fee_asset_id == asset_id_type() )\n            op.fee = current_fee_schedule().calculate_fee( op );\n         else\n            op.fee = current_fee_schedule().calculate_fee( op,\n                        take_profit_action.fee_asset_id(*this).options.core_exchange_rate ); // This may throw\n\n         auto sell_price = (~order.sell_price) * ratio_type( GRAPHENE_100_PERCENT,\n                              int32_t(GRAPHENE_100_PERCENT) + take_profit_action.spread_percent );\n         op.min_to_receive = for_sale.multiply_and_round_up( sell_price ); // This may throw\n\n         // GCOVR_EXCL_START\n         // Defensive code, should not fail\n         FC_ASSERT( ~op.get_price() > order.sell_price,\n                    \"Internal error: the take profit order should not match the current order\" );\n         // GCOVR_EXCL_STOP\n\n         transaction_evaluation_state eval_state(this);\n\n         auto op_result = try_push_virtual_operation( eval_state, op );\n         result = limit_order_id_type( op_result.get<object_id_type>() );\n      }\n      catch( const fc::exception& e )\n      {\n         // We can in fact get here\n         // e.g. if the selling or receiving asset issuer blacklisted the account,\n         //      or no sufficient balance to pay fees, or undo sessions nested too deeply\n         wlog( \"At block ${n}, failed to process on_fill for limit order ${order}, \"\n               \"automatic action (maybe incomplete) was ${op}, exception was ${e}\",\n               (\"op\", operation(op))(\"order\", order)\n               (\"n\", head_block_num())(\"e\", e.to_detail_string()) );\n      }\n   }\n\n   return result;\n}\n\nbool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives,\n                                 bool cull_if_small, const price& fill_price, const bool is_maker)\n{ try {\n   if( head_block_time() < HARDFORK_555_TIME )\n      cull_if_small = true;\n\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id );\n   FC_ASSERT( pays.asset_id != receives.asset_id );\n   // GCOVR_EXCL_STOP\n\n   const account_object& seller = order.seller(*this);\n\n   const auto issuer_fees = pay_market_fees(&seller, receives.asset_id(*this), receives, is_maker);\n\n   auto order_receives = receives - issuer_fees;\n   pay_order( seller, order_receives, pays );\n\n   push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives,\n                                                 issuer_fees, fill_price, is_maker ) );\n\n   // BSIP85: Maker order creation fee discount, https://github.com/bitshares/bsips/blob/master/bsip-0085.md\n   //   if the order creation fee was paid in BTS,\n   //     return round_down(deferred_fee * maker_fee_discount_percent) to the owner,\n   //     then process the remaining deferred fee as before;\n   //   if the order creation fee was paid in another asset,\n   //     return round_down(deferred_paid_fee * maker_fee_discount_percent) to the owner,\n   //     return round_down(deferred_fee * maker_fee_discount_percent) to the fee pool of the asset,\n   //     then process the remaining deferred fee and deferred paid fee as before.\n   const uint16_t maker_discount_percent = get_global_properties().parameters.get_maker_fee_discount_percent();\n\n   // Save local copies for calculation\n   share_type deferred_fee = order.deferred_fee;\n   share_type deferred_paid_fee = order.deferred_paid_fee.amount;\n\n   // conditional because cheap integer comparison may allow us to avoid two expensive modify() and object lookups\n   if( order.deferred_paid_fee.amount > 0 ) // implies head_block_time() > HARDFORK_CORE_604_TIME\n   {\n      share_type fee_pool_refund = 0;\n      if( is_maker && maker_discount_percent > 0 )\n      {\n         share_type refund = detail::calculate_percent( deferred_paid_fee, maker_discount_percent );\n         // Note: it's possible that the deferred_paid_fee is very small,\n         //       which can result in a zero refund due to rounding issue,\n         //       in this case, no refund to the fee pool\n         if( refund > 0 )\n         {\n            FC_ASSERT( refund <= deferred_paid_fee, \"Internal error\" );\n            adjust_balance( order.seller, asset(refund, order.deferred_paid_fee.asset_id) );\n            deferred_paid_fee -= refund;\n\n            // deferred_fee might be positive too\n            FC_ASSERT( deferred_fee > 0, \"Internal error\" );\n            fee_pool_refund = detail::calculate_percent( deferred_fee, maker_discount_percent );\n            FC_ASSERT( fee_pool_refund <= deferred_fee, \"Internal error\" );\n            deferred_fee -= fee_pool_refund;\n         }\n      }\n\n      const auto& fee_asset_dyn_data = order.deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this);\n      modify( fee_asset_dyn_data, [deferred_paid_fee,fee_pool_refund](asset_dynamic_data_object& addo) {\n         addo.accumulated_fees += deferred_paid_fee;\n         addo.fee_pool += fee_pool_refund;\n      });\n   }\n\n   if( order.deferred_fee > 0 )\n   {\n      if( order.deferred_paid_fee.amount <= 0 // paid in CORE, or before HF 604\n            && is_maker && maker_discount_percent > 0 )\n      {\n         share_type refund = detail::calculate_percent( deferred_fee, maker_discount_percent );\n         if( refund > 0 )\n         {\n            FC_ASSERT( refund <= deferred_fee, \"Internal error\" );\n            adjust_balance( order.seller, asset(refund, asset_id_type()) );\n            deferred_fee -= refund;\n         }\n      }\n      // else do nothing here, because we have already processed it above, or no need to process\n\n      if( deferred_fee > 0 )\n      {\n         modify( seller.statistics(*this), [deferred_fee,this]( account_statistics_object& statistics )\n         {\n            statistics.pay_fee( deferred_fee, get_global_properties().parameters.cashback_vesting_threshold );\n         } );\n      }\n   }\n\n   // Process on_fill for order_receives\n   optional<limit_order_id_type> new_take_profit_order_id = process_limit_order_on_fill( order, order_receives );\n\n   // If this order is fully filled\n   if( pays == order.amount_for_sale() )\n   {\n      cleanup_and_remove_limit_order( order );\n      return true;\n   }\n\n   // This order is partially filled\n   if( new_take_profit_order_id.valid() ) // A new take profit order is created, link this order to it\n   {\n      modify( (*new_take_profit_order_id)(*this), [&order]( limit_order_object& loo ) {\n         loo.take_profit_order_id = order.get_id();\n      });\n   }\n   modify( order, [&pays,&new_take_profit_order_id]( limit_order_object& b ) {\n      b.for_sale -= pays.amount;\n      b.filled_amount += pays.amount.value;\n      b.deferred_fee = 0;\n      b.deferred_paid_fee.amount = 0;\n      if( new_take_profit_order_id.valid() ) // A new take profit order is created, link it to this order\n         b.take_profit_order_id = *new_take_profit_order_id;\n   });\n   if( cull_if_small )\n      return maybe_cull_small_order( *this, order );\n   return false;\n} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } // GCOVR_EXCL_LINE\n\n/***\n * @brief fill a call order in the specified amounts\n * @param order the call order\n * @param pays What the call order will give to the other party (collateral)\n * @param receives what the call order will receive from the other party (debt)\n * @param fill_price the price at which the call order will execute\n * @param is_maker TRUE if the call order is the maker, FALSE if it is the taker\n * @param margin_call_fee Margin call fees paid in collateral asset\n * @returns TRUE if the call order was completely filled\n */\nbool database::fill_call_order( const call_order_object& order, const asset& pays, const asset& receives,\n      const price& fill_price, const bool is_maker, const asset& margin_call_fee, bool reduce_current_supply )\n{ try {\n   FC_ASSERT( order.debt_type() == receives.asset_id );\n   FC_ASSERT( order.collateral_type() == pays.asset_id );\n   FC_ASSERT( order.collateral >= pays.amount );\n\n   // TODO pass in mia and bitasset_data for better performance\n   const asset_object& mia = receives.asset_id(*this);\n   FC_ASSERT( mia.is_market_issued() );\n   const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);\n\n   optional<asset> collateral_freed;\n   // adjust the order\n   modify( order, [&]( call_order_object& o ) {\n         o.debt       -= receives.amount;\n         o.collateral -= pays.amount;\n         if( o.debt == 0 ) // is the whole debt paid?\n         {\n            collateral_freed = o.get_collateral();\n            o.collateral = 0;\n         }\n         else // the debt was not completely paid\n         {\n            auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n            // update call_price after core-343 hard fork,\n            // but don't update call_price after core-1270 hard fork\n            if( maint_time <= HARDFORK_CORE_1270_TIME && maint_time > HARDFORK_CORE_343_TIME )\n            {\n               o.call_price = price::call_price( o.get_debt(), o.get_collateral(),\n                     bitasset.current_feed.maintenance_collateral_ratio );\n            }\n         }\n      });\n\n   // update current supply\n   if( reduce_current_supply )\n   {\n      const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this);\n      modify( mia_ddo, [&receives]( asset_dynamic_data_object& ao ){\n         ao.current_supply -= receives.amount;\n      });\n   }\n\n   // If the whole debt is paid, adjust borrower's collateral balance\n   if( collateral_freed.valid() )\n      adjust_balance( order.borrower, *collateral_freed );\n\n   // Update account statistics. We know that order.collateral_type() == pays.asset_id\n   if( pays.asset_id == asset_id_type() )\n   {\n      modify( get_account_stats_by_owner(order.borrower), [&collateral_freed,&pays]( account_statistics_object& b ){\n         b.total_core_in_orders -= pays.amount;\n         if( collateral_freed.valid() )\n            b.total_core_in_orders -= collateral_freed->amount;\n      });\n   }\n\n   // BSIP74: Accumulate the collateral-denominated fee\n   if (margin_call_fee.amount.value != 0)\n      mia.accumulate_fee(*this, margin_call_fee);\n\n   // virtual operation for account history\n   push_applied_operation( fill_order_operation( order.id, order.borrower, pays, receives,\n         margin_call_fee, fill_price, is_maker ) );\n\n   // Call order completely filled, remove it\n   if( collateral_freed.valid() )\n      remove( order );\n\n   return collateral_freed.valid();\n} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } // GCOVR_EXCL_LINE\n\n/***\n * @brief fullfill a settle order in the specified amounts\n *\n * @details Called from database::match(), this coordinates exchange of debt asset X held in the\n *    settle order for collateral asset Y held in a call order, and routes fees.  Note that we\n *    don't touch the call order directly, as match() handles this via a separate call to\n *    fill_call_order().  We are told exactly how much X and Y to exchange, based on details of\n *    order matching determined higher up the call chain. Thus it is possible that the settle\n *    order is not completely satisfied at the conclusion of this function.\n *\n * @param settle the force_settlement object\n * @param pays the quantity of market-issued debt asset X which the settler will yield in this\n *    round (may be less than the full amount indicated in settle object)\n * @param receives the quantity of collateral asset Y which the settler will receive in\n *    exchange for X\n * @param fill_price the price at which the settle order will execute (not used - passed through\n *    to virtual operation)\n * @param is_maker TRUE if the settle order is the maker, FALSE if it is the taker (passed\n *    through to virtual operation)\n * @returns TRUE if the settle order was completely filled, FALSE if only partially filled\n */\nbool database::fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives,\n                                  const price& fill_price, bool is_maker, bool pay_force_settle_fee )\n{ try {\n   bool filled = false;\n\n   const account_object* settle_owner_ptr = nullptr;\n   // The owner of the settle order pays market fees to the issuer of the collateral asset.\n   // After HF core-1780, these fees are shared to the referral program, which is flagged to\n   // pay_market_fees by setting settle_owner_ptr non-null.\n   //\n   // TODO Check whether the HF check can be removed after the HF.\n   //      Note: even if logically it can be removed, perhaps the removal will lead to a small performance\n   //            loss. Needs testing.\n   if( head_block_time() >= HARDFORK_CORE_1780_TIME )\n      settle_owner_ptr = &settle.owner(*this);\n   // Compute and pay the market fees:\n   asset market_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives, is_maker );\n\n   // Issuer of the settled smartcoin asset lays claim to a force-settlement fee (BSIP87), but\n   // note that fee is denominated in collateral asset, not the debt asset.  Asset object of\n   // debt asset is passed to the pay function so it knows where to put the fee. Note that\n   // amount of collateral asset upon which fee is assessed is reduced by market_fees already\n   // paid to prevent the total fee exceeding total collateral.\n   asset force_settle_fees = pay_force_settle_fee\n                             ? pay_force_settle_fees( get(pays.asset_id), receives - market_fees )\n                             : asset( 0, receives.asset_id );\n\n   auto total_collateral_denominated_fees = market_fees + force_settle_fees;\n\n   // If we don't consume entire settle order:\n   if( pays < settle.balance )\n   {\n      modify(settle, [&pays](force_settlement_object& s) {\n         s.balance -= pays;\n      });\n   } else {\n      filled = true;\n   }\n   // Give released collateral not already taken as fees to settle order owner:\n   adjust_balance(settle.owner, receives - total_collateral_denominated_fees);\n\n   assert( pays.asset_id != receives.asset_id );\n   push_applied_operation( fill_order_operation( settle.id, settle.owner, pays, receives,\n                                                 total_collateral_denominated_fees, fill_price, is_maker ) );\n\n   if (filled)\n      remove(settle);\n\n   return filled;\n\n} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } // GCOVR_EXCL_LINE\n\n/**\n *  Starting with the least collateralized orders, fill them if their\n *  call price is above the max(lowest bid,call_limit).\n *\n *  This method will return true if it filled a short or limit\n *\n *  @param mia - the market issued asset that should be called.\n *  @param enable_black_swan - when adjusting collateral, triggering a black swan is invalid and will throw\n *                             if enable_black_swan is not set to true.\n *  @param for_new_limit_order - true if this function is called when matching call orders with a new\n *     limit order. (Only relevent before hardfork 625. apply_order_before_hardfork_625() is only\n *     function that calls this with for_new_limit_order true.)\n *  @param bitasset_ptr - an optional pointer to the bitasset_data object of the asset\n *  @param mute_exceptions - whether to mute exceptions in a special case\n *  @param skip_matching_settle_orders - whether to skip matching call orders with force settlements\n *\n *  @return true if a margin call was executed.\n */\nbool database::check_call_orders( const asset_object& mia, bool enable_black_swan, bool for_new_limit_order,\n                                  const asset_bitasset_data_object* bitasset_ptr,\n                                  bool mute_exceptions, bool skip_matching_settle_orders )\n{ try {\n    const auto& dyn_prop = get_dynamic_global_properties();\n    auto maint_time = dyn_prop.next_maintenance_time;\n    if( for_new_limit_order )\n       FC_ASSERT( maint_time <= HARDFORK_CORE_625_TIME ); // `for_new_limit_order` is only true before HF 338 / 625\n\n    if( !mia.is_market_issued() ) return false;\n\n    const asset_bitasset_data_object& bitasset = ( bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this) );\n\n    // price feeds can cause black swans in prediction markets\n    // The hardfork check may be able to be removed after the hardfork date\n    // if check_for_blackswan never triggered a black swan on a prediction market.\n    // NOTE: check_for_blackswan returning true does not always mean a black\n    // swan was triggered.\n    if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market )\n       return false;\n\n    using bsrm_type = bitasset_options::black_swan_response_type;\n    const auto bsrm = bitasset.get_black_swan_response_method();\n\n    // Only check for black swan here if BSRM is not individual settlement\n    if( bsrm_type::individual_settlement_to_fund != bsrm\n          && bsrm_type::individual_settlement_to_order != bsrm\n          && check_for_blackswan( mia, enable_black_swan, &bitasset ) )\n       return false;\n\n    if( bitasset.is_prediction_market ) return false;\n    if( bitasset.current_feed.settlement_price.is_null() ) return false;\n\n    const limit_order_index& limit_index = get_index_type<limit_order_index>();\n    const auto& limit_price_index = limit_index.indices().get<by_price>();\n\n    bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue\n    bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls\n\n    // Looking for limit orders selling the most USD for the least CORE.\n    auto max_price = price::max( bitasset.asset_id, bitasset.options.short_backing_asset );\n    // Stop when limit orders are selling too little USD for too much CORE.\n    // Note that since BSIP74, margin calls offer somewhat less CORE per USD\n    // if the issuer claims a Margin Call Fee.\n    auto min_price = before_core_hardfork_1270 ?\n                         bitasset.current_feed.max_short_squeeze_price_before_hf_1270()\n                       : bitasset.get_margin_call_order_price();\n\n    // NOTE limit_price_index is sorted from greatest to least\n    auto limit_itr = limit_price_index.lower_bound( max_price );\n    auto limit_end = limit_price_index.upper_bound( min_price );\n\n    // Before the core-2481 hf, only check limit orders\n    if( !after_core_hardfork_2481 && limit_itr == limit_end )\n       return false;\n\n    const call_order_index& call_index = get_index_type<call_order_index>();\n    const auto& call_price_index = call_index.indices().get<by_price>();\n    // Note: it is safe to iterate here even if there is no call order due to individual settlements\n    const auto& call_collateral_index = call_index.indices().get<by_collateral>();\n\n    auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id );\n    auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id );\n\n    auto call_price_itr = call_price_index.begin();\n    auto call_price_end = call_price_itr;\n    auto call_collateral_itr = call_collateral_index.begin();\n    auto call_collateral_end = call_collateral_itr;\n\n    if( before_core_hardfork_1270 )\n    {\n       call_price_itr = call_price_index.lower_bound( call_min );\n       call_price_end = call_price_index.upper_bound( call_max );\n    }\n    else\n    {\n       call_collateral_itr = call_collateral_index.lower_bound( call_min );\n       call_collateral_end = call_collateral_index.upper_bound( call_max );\n    }\n\n    bool filled_limit = false;\n    bool margin_called = false;         // toggles true once/if we actually execute a margin call\n\n    auto head_time = head_block_time();\n    auto head_num = head_block_num();\n\n    bool before_hardfork_615 = ( head_time < HARDFORK_615_TIME );\n    bool after_hardfork_436 = ( head_time > HARDFORK_436_TIME );\n\n    bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding\n    bool before_core_hardfork_343 = ( maint_time <= HARDFORK_CORE_343_TIME ); // update call_price on partial fill\n    bool before_core_hardfork_453 = ( maint_time <= HARDFORK_CORE_453_TIME ); // multiple matching issue\n    bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call\n    bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option\n\n    bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n    auto has_call_order = [ before_core_hardfork_1270,\n                            &call_collateral_itr,&call_collateral_end,\n                            &call_price_itr,&call_price_end ]()\n    {\n       return before_core_hardfork_1270 ? ( call_price_itr != call_price_end )\n                                        : ( call_collateral_itr != call_collateral_end );\n    };\n\n    bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() );\n\n    const auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();\n\n    while( has_call_order() )\n    {\n      // check for blackswan first // TODO perhaps improve performance by passing in iterators\n      bool settled_some = check_for_blackswan( mia, enable_black_swan, &bitasset );\n      if( bitasset.is_globally_settled() )\n         return margin_called;\n\n      if( settled_some ) // which implies that BSRM is individual settlement to fund or to order\n      {\n         call_collateral_itr = call_collateral_index.lower_bound( call_min );\n         if( call_collateral_itr == call_collateral_end ) // no call order left\n         {\n            check_settled_debt_order( bitasset );\n            return true;\n         }\n         margin_called = true;\n         if( bsrm_type::individual_settlement_to_fund == bsrm )\n            limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() );\n      }\n\n      // be here, there exists at least one call order\n      const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr );\n\n      // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436\n      bool feed_protected = before_core_hardfork_1270 ?\n                            ( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price )\n                          : ( bitasset.current_maintenance_collateralization < call_order.collateralization() );\n      if( feed_protected )\n      {\n         check_settled_debt_order( bitasset );\n         return margin_called;\n      }\n\n      // match call orders with limit orders\n      if( limit_itr != limit_end )\n      {\n         const limit_order_object& limit_order = *limit_itr;\n\n         price match_price  = limit_order.sell_price;\n         // There was a check `match_price.validate();` here, which is removed now because it always passes\n\n         // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606\n         if( before_core_hardfork_606 && match_price > ~call_order.call_price )\n            return margin_called;\n\n         margin_called = true;\n\n         price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio();\n         // Since BSIP74, the call \"pays\" a bit more collateral per debt than the match price, with the\n         // excess being kept by the asset issuer as a margin call fee. In what follows, we use\n         // call_pays_price for the black swan check, and for the TCR, but we still use the match_price,\n         // of course, to determine what the limit order receives.  Note margin_call_pays_ratio() returns\n         // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit.\n\n         // Although we checked for black swan above, we do one more check to ensure the call order can\n         // pay the amount of collateral which we intend to take from it (including margin call fee).\n         // TODO refactor code for better performance and readability, perhaps extract the new logic to a new\n         //      function and call it after hf_1270, hf_bsip74 or hf_2481.\n         auto usd_to_buy = call_order.get_debt();\n         if( !after_core_hardfork_2481 && ( usd_to_buy * call_pays_price ) > call_order.get_collateral() )\n         {\n            // Trigger black swan\n            elog( \"black swan detected on asset ${symbol} (${id}) at block ${b}\",\n                  (\"id\",bitasset.asset_id)(\"symbol\",mia.symbol)(\"b\",head_num) );\n            edump((enable_black_swan));\n            FC_ASSERT( enable_black_swan );\n            globally_settle_asset(mia, bitasset.current_feed.settlement_price );\n            return true;\n         }\n\n         if( !before_core_hardfork_1270 )\n         {\n            auto settle_price = after_core_hardfork_2582 ? bitasset.median_feed.settlement_price\n                                                         : bitasset.current_feed.settlement_price;\n            usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price,\n                                                                settle_price,\n                                                                bitasset.current_feed.maintenance_collateral_ratio,\n                                                                bitasset.current_maintenance_collateralization );\n         }\n         else if( !before_core_hardfork_834 )\n         {\n            usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price,\n                                                                bitasset.current_feed.settlement_price,\n                                                                bitasset.current_feed.maintenance_collateral_ratio );\n         }\n\n         asset usd_for_sale = limit_order.amount_for_sale();\n         asset call_pays, call_receives, limit_pays, limit_receives;\n\n         struct UndercollateralizationException {};\n         try { // throws UndercollateralizationException if the call order is undercollateralized\n\n            bool filled_call = false;\n\n            if( usd_to_buy > usd_for_sale )\n            {  // fill order\n               limit_receives  = usd_for_sale * match_price; // round down, in favor of call order\n\n               // Be here, the limit order won't be paying something for nothing, since if it would, it would have\n               //   been cancelled elsewhere already (a maker limit order won't be paying something for nothing):\n               // * after hard fork core-625, the limit order will be always a maker if entered this function;\n               // * before hard fork core-625,\n               //   * when the limit order is a taker, it could be paying something for nothing only when\n               //     the call order is smaller and is too small\n               //   * when the limit order is a maker, it won't be paying something for nothing\n\n               if( before_core_hardfork_342 )\n                  call_receives = usd_for_sale;\n               else\n                  // The remaining amount in the limit order would be too small,\n                  //   so we should cull the order in fill_limit_order() below.\n                  // The order would receive 0 even at `match_price`, so it would receive 0 at its own price,\n                  //   so calling maybe_cull_small() will always cull it.\n                  call_receives = limit_receives.multiply_and_round_up( match_price );\n\n               if( !after_core_hardfork_2481 )\n                  // TODO add tests about CR change\n                  call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74)\n               else\n               {\n                  call_pays = call_receives * call_pays_price; // calculate with updated call_receives\n                  if( call_pays.amount >= call_order.collateral )\n                     throw UndercollateralizationException();\n                  auto new_collateral = call_order.get_collateral() - call_pays;\n                  auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math\n                  if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease\n                     throw UndercollateralizationException();\n               }\n\n               filled_limit = true;\n\n            } else { // fill call, could be partial fill due to TCR\n               call_receives  = usd_to_buy;\n\n               if( before_core_hardfork_342 )\n               {\n                  limit_receives = usd_to_buy * match_price; // round down, in favor of call order\n                  call_pays = limit_receives;\n               } else {\n                  call_pays      = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee.\n                  // Note: Due to different rounding, this could potentialy be\n                  //       one satoshi more than the blackswan check above\n                  if( call_pays.amount > call_order.collateral )\n                  {\n                     if( after_core_hardfork_2481 )\n                        throw UndercollateralizationException();\n                     if( mute_exceptions )\n                        call_pays.amount = call_order.collateral;\n                  }\n                  // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher\n                  //       than the old CR, so no additional check for potential blackswan here\n\n                  limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, favors limit order\n                  if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481\n                     limit_receives.amount = call_order.collateral;\n                  // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more\n                  //       debt asset, it means the call order could be receiving a bit too much less than its value.\n                  //       It is a sad thing for the call order, but it is the rule\n                  //       -- when a call order is margin called, it does not get more than it borrowed.\n                  //       On the other hand, if the call order is not being closed (due to TCR),\n                  //       it means get_max_debt_to_cover() did not return a perfect result, maybe we can improve it.\n               }\n\n               filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343)\n\n               if( usd_to_buy == usd_for_sale )\n                  filled_limit = true;\n               else if( filled_limit && before_hardfork_615 )\n                  //NOTE: Multiple limit match problem (see issue 453, yes this happened)\n                  _issue_453_affected_assets.insert( bitasset.asset_id );\n            }\n            limit_pays = call_receives;\n\n            // BSIP74: Margin call fee\n            FC_ASSERT(call_pays >= limit_receives);\n            const asset margin_call_fee = call_pays - limit_receives;\n\n            if( filled_call && before_core_hardfork_343 )\n               ++call_price_itr;\n\n            // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker\n            fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee);\n\n            // Update current_feed after filled call order if needed\n            if( update_current_feed )\n            {\n               update_bitasset_current_feed( bitasset, true );\n               limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() );\n               update_current_feed = bitasset.is_current_feed_price_capped();\n            }\n\n            if( !before_core_hardfork_1270 )\n               call_collateral_itr = call_collateral_index.lower_bound( call_min );\n            else if( !before_core_hardfork_343 )\n               call_price_itr = call_price_index.lower_bound( call_min );\n\n            auto next_limit_itr = std::next( limit_itr );\n            // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker\n            bool really_filled = fill_limit_order( limit_order, limit_pays, limit_receives, true,\n                                                   match_price, !for_new_limit_order );\n            if( really_filled || ( filled_limit && before_core_hardfork_453 ) )\n               limit_itr = next_limit_itr;\n\n            continue; // check for blackswan again\n\n         } catch( const UndercollateralizationException& ) {\n            // Nothing to do here\n         }\n      } // if there is a matching limit order\n\n      // be here, it is unable to fill a limit order due to undercollateralization (and there is a force settlement),\n      //          or there is no matching limit order due to MSSR, or no limit order at all\n\n      // If no need to process force settlements, we return\n      // Note: before core-2481/2467 hf, or BSRM is no_settlement and processing a new force settlement\n      if( skip_matching_settle_orders || !after_core_hardfork_2481 )\n         return margin_called;\n\n      // If no force settlements, we return\n      // Note: there is no matching limit order due to MSSR, or no limit order at all,\n      //       in either case, the settled debt order can't be matched\n      auto settle_itr = settlement_index.lower_bound( bitasset.asset_id );\n      if( settle_itr == settlement_index.end() || settle_itr->balance.asset_id != bitasset.asset_id )\n         return margin_called;\n\n      // Check margin calls against force settlements\n      // Note: we always need to recheck limit orders after processed call-settle match,\n      //       in case when the least collateralized short was undercollateralized.\n      if( match_force_settlements( bitasset ) )\n      {\n         margin_called = true;\n         call_collateral_itr = call_collateral_index.lower_bound( call_min );\n         if( update_current_feed )\n         {\n            // Note: we do not call update_bitasset_current_feed() here,\n            //       because it's called in match_impl() in match() in match_force_settlements()\n            limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() );\n            update_current_feed = bitasset.is_current_feed_price_capped();\n         }\n      }\n      // else : no more force settlements, or feed protected, both will be handled in the next loop\n   } // while there exists a call order\n   check_settled_debt_order( bitasset );\n   return margin_called;\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nbool database::match_force_settlements( const asset_bitasset_data_object& bitasset )\n{\n   // Defensive checks\n   auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   // GCOVR_EXCL_START\n   // Defensive code, normally none of these should fail\n   FC_ASSERT( HARDFORK_CORE_2481_PASSED( maint_time ), \"Internal error: hard fork core-2481 not passed\" );\n   FC_ASSERT( !bitasset.is_prediction_market, \"Internal error: asset is a prediction market\" );\n   FC_ASSERT( !bitasset.is_globally_settled(), \"Internal error: asset is globally settled already\" );\n   FC_ASSERT( !bitasset.current_feed.settlement_price.is_null(), \"Internal error: no sufficient price feeds\" );\n   // GCOVR_EXCL_STOP\n\n   auto head_time = head_block_time();\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n   const auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();\n   auto settle_itr = settlement_index.lower_bound( bitasset.asset_id );\n   auto settle_end = settlement_index.upper_bound( bitasset.asset_id );\n\n   // Note: it is safe to iterate here even if there is no call order due to individual settlements\n   const auto& call_collateral_index = get_index_type<call_order_index>().indices().get<by_collateral>();\n   auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id );\n   auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id );\n   auto call_itr = call_collateral_index.lower_bound( call_min );\n   auto call_end = call_collateral_index.upper_bound( call_max );\n\n   // Price at which margin calls sit on the books.\n   // It is the MCOP, which may deviate from MSSP due to MCFR.\n   // It is in debt/collateral .\n   price call_match_price = bitasset.get_margin_call_order_price();\n   // Price margin call actually relinquishes collateral at. Equals the MSSP and it may\n   // differ from call_match_price if there is a Margin Call Fee.\n   // It is in debt/collateral .\n   price call_pays_price = bitasset.current_feed.max_short_squeeze_price();\n\n   while( settle_itr != settle_end && call_itr != call_end )\n   {\n      const force_settlement_object& settle_order = *settle_itr;\n      const call_order_object& call_order = *call_itr;\n\n      // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436\n      if( bitasset.current_maintenance_collateralization < call_order.collateralization() )\n         return false;\n\n      // TCR applies here\n      auto settle_price = after_core_hardfork_2582 ? bitasset.median_feed.settlement_price\n                                                   : bitasset.current_feed.settlement_price;\n      asset max_debt_to_cover( call_order.get_max_debt_to_cover( call_pays_price,\n                                                       settle_price,\n                                                       bitasset.current_feed.maintenance_collateral_ratio,\n                                                       bitasset.current_maintenance_collateralization ),\n                               bitasset.asset_id );\n\n      // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price.\n      //       In this case, the call order pays at its CR, the settle order may receive less due to margin call fee.\n      //       It is processed inside the function.\n      auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price );\n\n      // if result.amount > 0, it means the call order got updated or removed\n      // in this case, we need to check limit orders first, so we return\n      if( result.amount > 0 )\n         return true;\n      // else : result.amount == 0, it means the settle order got canceled directly and the call order did not change\n\n      settle_itr = settlement_index.lower_bound( bitasset.asset_id );\n      call_itr = call_collateral_index.lower_bound( call_min );\n   }\n   return false;\n}\n\nvoid database::check_settled_debt_order( const asset_bitasset_data_object& bitasset )\n{\n   const auto& head_time = head_block_time();\n   bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP)\n   if( !after_core_hardfork_2591 )\n      return;\n\n   using bsrm_type = bitasset_options::black_swan_response_type;\n   const auto bsrm = bitasset.get_black_swan_response_method();\n   if( bsrm_type::individual_settlement_to_order != bsrm )\n      return;\n\n   const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id );\n   if( !limit_ptr )\n      return;\n\n   const limit_order_index& limit_index = get_index_type<limit_order_index>();\n   const auto& limit_price_index = limit_index.indices().get<by_price>();\n\n   // Looking for limit orders selling the most USD for the least CORE.\n   auto max_price = price::max( bitasset.asset_id, bitasset.options.short_backing_asset );\n   // Stop when limit orders are selling too little USD for too much CORE.\n   auto min_price = ~limit_ptr->sell_price;\n\n   // NOTE limit_price_index is sorted from greatest to least\n   auto limit_itr = limit_price_index.lower_bound( max_price );\n   auto limit_end = limit_price_index.upper_bound( min_price );\n\n   bool finished = false; // whether the settled debt order is gone\n   while( !finished && limit_itr != limit_end )\n   {\n      const limit_order_object& matching_limit_order = *limit_itr;\n      ++limit_itr;\n      price old_price = limit_ptr->sell_price;\n      finished = ( match_settled_debt_limit( *limit_ptr, matching_limit_order, matching_limit_order.sell_price )\n                   != match_result_type::only_maker_filled );\n      if( !finished && old_price != limit_ptr->sell_price )\n         limit_end = limit_price_index.upper_bound( ~limit_ptr->sell_price );\n   }\n}\n\nvoid database::pay_order( const account_object& receiver, const asset& receives, const asset& pays )\n{\n   if( pays.asset_id == asset_id_type() )\n   {\n      const auto& stats = receiver.statistics(*this);\n      modify( stats, [&pays]( account_statistics_object& b ){\n         b.total_core_in_orders -= pays.amount;\n      });\n   }\n   adjust_balance(receiver.get_id(), receives);\n}\n\nasset database::calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount,\n                                      const bool& is_maker )const\n{\n   assert( trade_asset.id == trade_amount.asset_id );\n\n   if( !trade_asset.charges_market_fees() )\n      return trade_asset.amount(0);\n   // Optimization: The fee is zero if the order is a maker, and the maker fee percent is 0%\n   if( is_maker && trade_asset.options.market_fee_percent == 0 )\n      return trade_asset.amount(0);\n\n   // Optimization: The fee is zero if the order is a taker, and the taker fee percent is 0%\n   const optional<uint16_t>& taker_fee_percent = trade_asset.options.extensions.value.taker_fee_percent;\n   if(!is_maker && taker_fee_percent.valid() && *taker_fee_percent == 0)\n      return trade_asset.amount(0);\n\n   uint16_t fee_percent;\n   if (is_maker) {\n      // Maker orders are charged the maker fee percent\n      fee_percent = trade_asset.options.market_fee_percent;\n   } else {\n      // Taker orders are charged the taker fee percent if they are valid.  Otherwise, the maker fee percent.\n      fee_percent = taker_fee_percent.valid() ? *taker_fee_percent : trade_asset.options.market_fee_percent;\n   }\n\n   auto value = detail::calculate_percent(trade_amount.amount, fee_percent);\n   asset percent_fee = trade_asset.amount(value);\n\n   if( percent_fee.amount > trade_asset.options.max_market_fee )\n      percent_fee.amount = trade_asset.options.max_market_fee;\n\n   return percent_fee;\n}\n\n\nasset database::pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives,\n                                const bool& is_maker, const optional<asset>& calculated_market_fees )\n{\n   const auto market_fees = ( calculated_market_fees.valid() ? *calculated_market_fees\n                                    : calculate_market_fee( recv_asset, receives, is_maker ) );\n   auto issuer_fees = market_fees;\n   FC_ASSERT( issuer_fees <= receives, \"Market fee shouldn't be greater than receives\");\n   //Don't dirty undo state if not actually collecting any fees\n   if ( issuer_fees.amount > 0 )\n   {\n      // Share market fees to the network\n      const uint16_t network_percent = get_global_properties().parameters.get_market_fee_network_percent();\n      if( network_percent > 0 )\n      {\n         const auto network_fees_amt = detail::calculate_percent( issuer_fees.amount, network_percent );\n         FC_ASSERT( network_fees_amt <= issuer_fees.amount,\n                    \"Fee shared to the network shouldn't be greater than total market fee\" );\n         if( network_fees_amt > 0 )\n         {\n            const asset network_fees = recv_asset.amount( network_fees_amt );\n            deposit_market_fee_vesting_balance( GRAPHENE_COMMITTEE_ACCOUNT, network_fees );\n            issuer_fees -= network_fees;\n         }\n      }\n   }\n\n   // Process the remaining fees\n   if ( issuer_fees.amount > 0 )\n   {\n      // calculate and pay rewards\n      asset reward = recv_asset.amount(0);\n\n      auto is_rewards_allowed = [&recv_asset, seller]() {\n         if ( !seller )\n            return false;\n         const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing;\n         return ( !white_list || (*white_list).empty()\n               || ( (*white_list).find(seller->registrar) != (*white_list).end() ) );\n      };\n\n      if ( is_rewards_allowed() )\n      {\n         const auto reward_percent = recv_asset.options.extensions.value.reward_percent;\n         if ( reward_percent.valid() && (*reward_percent) > 0 )\n         {\n            const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent);\n            if ( reward_value > 0 && is_authorized_asset(*this, seller->registrar(*this), recv_asset) )\n            {\n               reward = recv_asset.amount(reward_value);\n               // TODO after hf_1774, remove the `if` check, keep the code in `else`\n               if( head_block_time() < HARDFORK_1774_TIME ){\n                  FC_ASSERT( reward < issuer_fees, \"Market reward should be less than issuer fees\");\n               }\n               else{\n                  FC_ASSERT( reward <= issuer_fees, \"Market reward should not be greater than issuer fees\");\n               }\n               // cut referrer percent from reward\n               auto registrar_reward = reward;\n\n               auto registrar = seller->registrar;\n               auto referrer = seller->referrer;\n\n               // After HF core-1800, for funds going to temp-account, redirect to committee-account\n               if( head_block_time() >= HARDFORK_CORE_1800_TIME )\n               {\n                  if( registrar == GRAPHENE_TEMP_ACCOUNT )\n                     registrar = GRAPHENE_COMMITTEE_ACCOUNT;\n                  if( referrer == GRAPHENE_TEMP_ACCOUNT )\n                     referrer = GRAPHENE_COMMITTEE_ACCOUNT;\n               }\n\n               if( referrer != registrar )\n               {\n                  const auto referrer_rewards_value = detail::calculate_percent( reward.amount,\n                                                                 seller->referrer_rewards_percentage );\n\n                  if ( referrer_rewards_value > 0 && is_authorized_asset(*this, referrer(*this), recv_asset) )\n                  {\n                     FC_ASSERT ( referrer_rewards_value <= reward.amount.value,\n                                 \"Referrer reward shouldn't be greater than total reward\" );\n                     const asset referrer_reward = recv_asset.amount(referrer_rewards_value);\n                     registrar_reward -= referrer_reward;\n                     deposit_market_fee_vesting_balance(referrer, referrer_reward);\n                  }\n               }\n               if( registrar_reward.amount > 0 )\n                  deposit_market_fee_vesting_balance(registrar, registrar_reward);\n            }\n         }\n      }\n\n      if( issuer_fees.amount > reward.amount )\n      {\n         const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this);\n         modify( recv_dyn_data, [&issuer_fees, &reward]( asset_dynamic_data_object& obj ){\n            obj.accumulated_fees += issuer_fees.amount - reward.amount;\n         });\n      }\n   }\n\n   return market_fees;\n}\n\n/***\n * @brief Calculate force-settlement fee and give it to issuer of the settled asset\n * @param collecting_asset the smart asset object which should receive the fee\n * @param collat_receives the amount of collateral the settler would expect to receive absent this fee\n *     (fee is computed as a percentage of this amount)\n * @return asset denoting the amount of fee collected\n */\nasset database::pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives)\n{\n   FC_ASSERT( collecting_asset.get_id() != collat_receives.asset_id );\n\n   const bitasset_options& collecting_bitasset_opts = collecting_asset.bitasset_data(*this).options;\n\n   if( !collecting_bitasset_opts.extensions.value.force_settle_fee_percent.valid()\n         || *collecting_bitasset_opts.extensions.value.force_settle_fee_percent == 0 )\n      return asset{ 0, collat_receives.asset_id };\n\n   auto value = detail::calculate_percent(collat_receives.amount,\n                                          *collecting_bitasset_opts.extensions.value.force_settle_fee_percent);\n   asset settle_fee( value, collat_receives.asset_id );\n\n   // Deposit fee in asset's dynamic data object:\n   if( value > 0) {\n      collecting_asset.accumulate_fee(*this, settle_fee);\n   }\n   return settle_fee;\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_notify.cpp",
    "content": "#include <fc/container/flat.hpp>\n\n#include <graphene/protocol/authority.hpp>\n#include <graphene/protocol/operations.hpp>\n#include <graphene/protocol/transaction.hpp>\n\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/confidential_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/transaction_history_object.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/impacted.hpp>\n#include <graphene/chain/hardfork.hpp>\n\nusing namespace fc;\nnamespace graphene { namespace chain { namespace detail {\n\n// TODO:  Review all of these, especially no-ops\nstruct get_impacted_account_visitor\n{\n   flat_set<account_id_type>& _impacted;\n   bool _ignore_custom_op_reqd_auths;\n\n   get_impacted_account_visitor( flat_set<account_id_type>& impact, bool ignore_custom_op_required_auths )\n      : _impacted( impact ), _ignore_custom_op_reqd_auths( ignore_custom_op_required_auths )\n   {}\n\n   using result_type = void;\n\n   void operator()( const transfer_operation& op )\n   {\n      _impacted.insert( op.to );\n      _impacted.insert( op.fee_payer() ); // from\n   }\n   void operator()( const asset_claim_fees_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const asset_claim_pool_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const limit_order_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // seller\n   }\n   void operator()(const limit_order_update_operation& op)\n   {\n      _impacted.insert(op.fee_payer()); // seller\n   }\n   void operator()( const limit_order_cancel_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // fee_paying_account\n   }\n   void operator()( const call_order_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // funding_account\n   }\n   void operator()( const bid_collateral_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // bidder\n   }\n   void operator()( const fill_order_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account_id\n   }\n   void operator()( const execute_bid_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // bidder\n   }\n   void operator()( const account_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // registrar\n      _impacted.insert( op.referrer );\n      add_authority_accounts( _impacted, op.owner );\n      add_authority_accounts( _impacted, op.active );\n   }\n   void operator()( const account_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n      if( op.owner )\n         add_authority_accounts( _impacted, *(op.owner) );\n      if( op.active )\n         add_authority_accounts( _impacted, *(op.active) );\n   }\n   void operator()( const account_whitelist_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // authorizing_account\n      _impacted.insert( op.account_to_list );\n   }\n   void operator()( const account_upgrade_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account_to_upgrade\n   }\n   void operator()( const account_transfer_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account_id\n   }\n   void operator()( const asset_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const asset_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n      if( op.new_issuer )\n         _impacted.insert( *(op.new_issuer) );\n   }\n   void operator()( const asset_update_issuer_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n      _impacted.insert( op.new_issuer );\n   }\n   void operator()( const asset_update_bitasset_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const asset_update_feed_producers_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const asset_issue_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n      _impacted.insert( op.issue_to_account );\n   }\n   void operator()( const asset_reserve_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // payer\n   }\n   void operator()( const asset_fund_fee_pool_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // from_account\n   }\n   void operator()( const asset_settle_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const asset_global_settle_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const asset_publish_feed_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // publisher\n   }\n   void operator()( const witness_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // witness_account\n   }\n   void operator()( const witness_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // witness_account\n   }\n   void operator()( const proposal_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // fee_paying_account\n      vector<authority> other;\n      for( const auto& proposed_op : op.proposed_ops )\n         operation_get_required_authorities( proposed_op.op, _impacted, _impacted, other,\n                                             _ignore_custom_op_reqd_auths );\n      for( const auto& o : other )\n         add_authority_accounts( _impacted, o );\n   }\n   void operator()( const proposal_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // fee_paying_account\n   }\n   void operator()( const proposal_delete_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // fee_paying_account\n   }\n   void operator()( const withdraw_permission_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // withdraw_from_account\n      _impacted.insert( op.authorized_account );\n   }\n   void operator()( const withdraw_permission_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // withdraw_from_account\n      _impacted.insert( op.authorized_account );\n   }\n   void operator()( const withdraw_permission_claim_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // withdraw_to_account\n      _impacted.insert( op.withdraw_from_account );\n   }\n   void operator()( const withdraw_permission_delete_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // withdraw_from_account\n      _impacted.insert( op.authorized_account );\n   }\n   void operator()( const committee_member_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // committee_member_account\n   }\n   void operator()( const committee_member_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // committee_member_account\n   }\n   void operator()( const committee_member_update_global_parameters_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account_id_type()\n   }\n   void operator()( const vesting_balance_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // creator\n      _impacted.insert( op.owner );\n   }\n   void operator()( const vesting_balance_withdraw_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner\n   }\n   void operator()( const worker_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner\n   }\n   void operator()( const custom_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // payer\n      if( !_ignore_custom_op_reqd_auths )\n         _impacted.insert( op.required_auths.begin(), op.required_auths.end() );\n   }\n   void operator()( const assert_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // fee_paying_account\n   }\n   void operator()( const balance_claim_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // deposit_to_account\n   }\n   void operator()( const override_transfer_operation& op )\n   {\n      _impacted.insert( op.to );\n      _impacted.insert( op.from );\n      _impacted.insert( op.fee_payer() ); // issuer\n   }\n   void operator()( const transfer_to_blind_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // from\n      for( const auto& out : op.outputs )\n         add_authority_accounts( _impacted, out.owner );\n   }\n   void operator()( const blind_transfer_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // GRAPHENE_TEMP_ACCOUNT\n      for( const auto& in : op.inputs )\n         add_authority_accounts( _impacted, in.owner );\n      for( const auto& out : op.outputs )\n         add_authority_accounts( _impacted, out.owner );\n   }\n   void operator()( const transfer_from_blind_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // GRAPHENE_TEMP_ACCOUNT\n      _impacted.insert( op.to );\n      for( const auto& in : op.inputs )\n         add_authority_accounts( _impacted, in.owner );\n   }\n   void operator()( const asset_settle_cancel_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const fba_distribute_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account_id\n   }\n   void operator()( const htlc_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() );\n      _impacted.insert( op.to );\n   }\n   void operator()( const htlc_redeem_operation& op )\n   {\n      _impacted.insert( op.fee_payer() );\n   }\n   void operator()( const htlc_redeemed_operation& op )\n   {\n      _impacted.insert( op.from );\n      if ( op.to != op.redeemer )\n         _impacted.insert( op.to );\n   }\n   void operator()( const htlc_extend_operation& op )\n   {\n      _impacted.insert( op.fee_payer() );\n   }\n   void operator()( const htlc_refund_operation& op )\n   {\n      _impacted.insert( op.fee_payer() );\n   }\n   void operator()( const custom_authority_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n      add_authority_accounts( _impacted, op.auth );\n   }\n   void operator()( const custom_authority_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n      if ( op.new_auth )\n         add_authority_accounts(_impacted, *op.new_auth);\n   }\n   void operator()( const custom_authority_delete_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const ticket_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const ticket_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const liquidity_pool_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const liquidity_pool_delete_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const liquidity_pool_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const liquidity_pool_deposit_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const liquidity_pool_withdraw_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const liquidity_pool_exchange_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const samet_fund_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner_account\n   }\n   void operator()( const samet_fund_delete_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner_account\n   }\n   void operator()( const samet_fund_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner_account\n   }\n   void operator()( const samet_fund_borrow_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // borrower\n   }\n   void operator()( const samet_fund_repay_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const credit_offer_create_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner_account\n   }\n   void operator()( const credit_offer_delete_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner_account\n   }\n   void operator()( const credit_offer_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // owner_account\n   }\n   void operator()( const credit_offer_accept_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // borrower\n   }\n   void operator()( const credit_deal_repay_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n   void operator()( const credit_deal_expired_operation& op )\n   {\n      _impacted.insert( op.offer_owner );\n      _impacted.insert( op.borrower );\n   }\n   void operator()( const credit_deal_update_operation& op )\n   {\n      _impacted.insert( op.fee_payer() ); // account\n   }\n};\n\n} // namespace detail\n\n// Declared in impacted.hpp\nvoid operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result,\n      bool ignore_custom_op_required_auths )\n{\n  detail::get_impacted_account_visitor vtor( result, ignore_custom_op_required_auths );\n  op.visit( vtor );\n}\n\n// Declared in impacted.hpp, although only used in this file\nvoid transaction_get_impacted_accs( const transaction& tx, flat_set<account_id_type>& result,\n      bool ignore_custom_op_required_auths )\n{\n  for( const auto& op : tx.operations )\n    operation_get_impacted_accounts( op, result, ignore_custom_op_required_auths );\n}\n\nstatic void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accounts,\n                            bool ignore_custom_op_required_auths ) {\n   FC_ASSERT( obj != nullptr, \"Internal error: get_relevant_accounts called with nullptr\" ); // This should not happen\n   if( obj->id.space() == protocol_ids )\n   {\n      switch( (object_type)obj->id.type() )\n      {\n        case null_object_type:\n        case base_object_type:\n           return;\n        case account_object_type:\n           accounts.insert( account_id_type(obj->id) );\n           break;\n        case asset_object_type:{\n           const auto* aobj = dynamic_cast<const asset_object*>(obj);\n           accounts.insert( aobj->issuer );\n           break;\n        } case force_settlement_object_type:{\n           const auto* aobj = dynamic_cast<const force_settlement_object*>(obj);\n           accounts.insert( aobj->owner );\n           break;\n        } case committee_member_object_type:{\n           const auto* aobj = dynamic_cast<const committee_member_object*>(obj);\n           accounts.insert( aobj->committee_member_account );\n           break;\n        } case witness_object_type:{\n           const auto* aobj = dynamic_cast<const witness_object*>(obj);\n           accounts.insert( aobj->witness_account );\n           break;\n        } case limit_order_object_type:{\n           const auto* aobj = dynamic_cast<const limit_order_object*>(obj);\n           accounts.insert( aobj->seller );\n           break;\n        } case call_order_object_type:{\n           const auto* aobj = dynamic_cast<const call_order_object*>(obj);\n           accounts.insert( aobj->borrower );\n           break;\n        } case custom_object_type:\n          break;\n        case proposal_object_type:{\n           const auto* aobj = dynamic_cast<const proposal_object*>(obj);\n           transaction_get_impacted_accs( aobj->proposed_transaction, accounts,\n                                          ignore_custom_op_required_auths );\n           break;\n        } case operation_history_object_type:{\n           const auto* aobj = dynamic_cast<const operation_history_object*>(obj);\n           operation_get_impacted_accounts( aobj->op, accounts,\n                                            ignore_custom_op_required_auths );\n           break;\n        } case withdraw_permission_object_type:{\n           const auto* aobj = dynamic_cast<const withdraw_permission_object*>(obj);\n           accounts.insert( aobj->withdraw_from_account );\n           accounts.insert( aobj->authorized_account );\n           break;\n        } case vesting_balance_object_type:{\n           const auto* aobj = dynamic_cast<const vesting_balance_object*>(obj);\n           accounts.insert( aobj->owner );\n           break;\n        } case worker_object_type:{\n           const auto* aobj = dynamic_cast<const worker_object*>(obj);\n           accounts.insert( aobj->worker_account );\n           break;\n        } case balance_object_type:\n           /** these are free from any accounts */\n           break;\n        case htlc_object_type:{\n              const auto* htlc_obj = dynamic_cast<const htlc_object*>(obj);\n              accounts.insert( htlc_obj->transfer.from );\n              accounts.insert( htlc_obj->transfer.to );\n              break;\n        } case custom_authority_object_type:{\n           const auto* cust_auth_obj = dynamic_cast<const custom_authority_object*>( obj );\n           accounts.insert( cust_auth_obj->account );\n           add_authority_accounts( accounts, cust_auth_obj->auth );\n           break;\n        } case ticket_object_type:{\n           const auto* aobj = dynamic_cast<const ticket_object*>( obj );\n           accounts.insert( aobj->account );\n           break;\n        } case liquidity_pool_object_type:\n           // no account info in the object although it does have an owner\n           break;\n        case samet_fund_object_type:{\n           const auto* aobj = dynamic_cast<const samet_fund_object*>( obj );\n           accounts.insert( aobj->owner_account );\n           break;\n        } case credit_offer_object_type:{\n           const auto* aobj = dynamic_cast<const credit_offer_object*>( obj );\n           accounts.insert( aobj->owner_account );\n           break;\n        } case credit_deal_object_type:{\n           const auto* aobj = dynamic_cast<const credit_deal_object*>( obj );\n           accounts.insert( aobj->offer_owner );\n           accounts.insert( aobj->borrower );\n           break;\n        }\n        // Do not have a default fallback so that there will be a compiler warning when a new type is added\n      }\n   }\n   else if( obj->id.space() == implementation_ids )\n   {\n      switch( (impl_object_type)obj->id.type() )\n      {\n             case impl_global_property_object_type:\n              break;\n             case impl_dynamic_global_property_object_type:\n              break;\n             case impl_reserved0_object_type:\n              break;\n             case impl_asset_dynamic_data_object_type:\n              break;\n             case impl_asset_bitasset_data_object_type:\n              break;\n             case impl_account_balance_object_type:{\n              const auto* aobj = dynamic_cast<const account_balance_object*>(obj);\n              accounts.insert( aobj->owner );\n              break;\n           } case impl_account_statistics_object_type:{\n              const auto* aobj = dynamic_cast<const account_statistics_object*>(obj);\n              accounts.insert( aobj->owner );\n              break;\n           } case impl_transaction_history_object_type:{\n              const auto* aobj = dynamic_cast<const transaction_history_object*>(obj);\n              transaction_get_impacted_accs( aobj->trx, accounts,\n                                             ignore_custom_op_required_auths );\n              break;\n           } case impl_blinded_balance_object_type:{\n              const auto* aobj = dynamic_cast<const blinded_balance_object*>(obj);\n              for( const auto& a : aobj->owner.account_auths )\n                accounts.insert( a.first );\n              break;\n           } case impl_block_summary_object_type:\n              break;\n             case impl_account_history_object_type: {\n              const auto* aobj = dynamic_cast<const account_history_object*>(obj);\n              accounts.insert( aobj->account );\n              break;\n           } case impl_chain_property_object_type:\n              break;\n             case impl_witness_schedule_object_type:\n              break;\n             case impl_budget_record_object_type:\n              break;\n             case impl_special_authority_object_type:\n              break;\n             case impl_buyback_object_type:\n              break;\n             case impl_fba_accumulator_object_type:\n              break;\n             case impl_collateral_bid_object_type:{\n              const auto* aobj = dynamic_cast<const collateral_bid_object*>(obj);\n              accounts.insert( aobj->bidder );\n              break;\n           } case impl_credit_deal_summary_object_type:{\n              const auto* aobj = dynamic_cast<const credit_deal_summary_object*>(obj);\n              accounts.insert( aobj->offer_owner );\n              accounts.insert( aobj->borrower );\n              break;\n           }\n           // Do not have a default fallback so that there will be a compiler warning when a new type is added\n      }\n   }\n} // end get_relevant_accounts( const object* obj, flat_set<account_id_type>& accounts )\n\nvoid database::notify_applied_block( const signed_block& block )\n{\n   GRAPHENE_TRY_NOTIFY( applied_block, block )\n}\n\nvoid database::notify_on_pending_transaction( const signed_transaction& tx )\n{\n   GRAPHENE_TRY_NOTIFY( on_pending_transaction, tx )\n}\n\nvoid database::notify_changed_objects()\n{ try {\n   if( _undo_db.enabled() )\n   {\n      const auto& head_undo = _undo_db.head();\n      auto chain_time = head_block_time();\n\n      // New\n      if( !new_objects.empty() )\n      {\n        vector<object_id_type> new_ids;\n        new_ids.reserve(head_undo.new_ids.size());\n        flat_set<account_id_type> new_accounts_impacted;\n        for( const auto& item : head_undo.new_ids )\n        {\n          new_ids.push_back(item);\n          auto* obj = find_object(item);\n          if(obj != nullptr)\n            get_relevant_accounts(obj, new_accounts_impacted,\n                                  MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time));\n        }\n\n        if( !new_ids.empty() )\n           GRAPHENE_TRY_NOTIFY( new_objects, new_ids, new_accounts_impacted)\n      }\n\n      // Changed\n      if( !changed_objects.empty() )\n      {\n        vector<object_id_type> changed_ids;\n        changed_ids.reserve(head_undo.old_values.size());\n        flat_set<account_id_type> changed_accounts_impacted;\n        for( const auto& item : head_undo.old_values )\n        {\n          changed_ids.push_back(item.first);\n          get_relevant_accounts(item.second.get(), changed_accounts_impacted,\n                                MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time));\n        }\n\n        if( !changed_ids.empty() )\n           GRAPHENE_TRY_NOTIFY( changed_objects, changed_ids, changed_accounts_impacted)\n      }\n\n      // Removed\n      if( !removed_objects.empty() )\n      {\n        vector<object_id_type> removed_ids;\n        removed_ids.reserve( head_undo.removed.size() );\n        vector<const object*> removed;\n        removed.reserve( head_undo.removed.size() );\n        flat_set<account_id_type> removed_accounts_impacted;\n        for( const auto& item : head_undo.removed )\n        {\n          removed_ids.emplace_back( item.first );\n          auto* obj = item.second.get();\n          removed.emplace_back( obj );\n          get_relevant_accounts(obj, removed_accounts_impacted,\n                                MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time));\n        }\n\n        if( !removed_ids.empty() )\n           GRAPHENE_TRY_NOTIFY( removed_objects, removed_ids, removed, removed_accounts_impacted )\n      }\n   }\n} catch( const graphene::chain::plugin_exception& e ) {\n   elog( \"Caught plugin exception: ${e}\", (\"e\", e.to_detail_string() ) );\n   throw;\n} FC_CAPTURE_AND_LOG( (0) ) } // GCOVR_EXCL_LINE\n\n} } // namespace graphene::chain\n"
  },
  {
    "path": "libraries/chain/db_update.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/db_with.hpp>\n\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/transaction_history_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <graphene/protocol/fee_schedule.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid database::update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks )\n{\n   const dynamic_global_property_object& _dgp = get_dynamic_global_properties();\n\n   // dynamic global properties updating\n   modify( _dgp, [&b,this,missed_blocks]( dynamic_global_property_object& dgp ){\n      const uint32_t block_num = b.block_num();\n      if( BOOST_UNLIKELY( block_num == 1 ) )\n         dgp.recently_missed_count = 0;\n      else if( !_checkpoints.empty() && _checkpoints.rbegin()->first >= block_num )\n         dgp.recently_missed_count = 0;\n      else if( missed_blocks )\n         dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks;\n      else if( dgp.recently_missed_count > GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT )\n         dgp.recently_missed_count -= GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT;\n      else if( dgp.recently_missed_count > 0 )\n         dgp.recently_missed_count--;\n\n      dgp.head_block_number = block_num;\n      dgp.head_block_id = b.id();\n      dgp.time = b.timestamp;\n      dgp.current_witness = b.witness;\n      dgp.recent_slots_filled = (\n           (dgp.recent_slots_filled << 1)\n           + 1) << missed_blocks;\n      dgp.current_aslot += missed_blocks+1;\n   });\n\n   if( 0 == (get_node_properties().skip_flags & skip_undo_history_check) )\n   {\n      GRAPHENE_ASSERT( _dgp.head_block_number - _dgp.last_irreversible_block_num  < GRAPHENE_MAX_UNDO_HISTORY, undo_database_exception,\n                 \"The database does not have enough undo history to support a blockchain with so many missed blocks. \"\n                 \"Please add a checkpoint if you would like to continue applying blocks beyond this point.\",\n                 (\"last_irreversible_block_num\",_dgp.last_irreversible_block_num)(\"head\", _dgp.head_block_number)\n                 (\"recently_missed\",_dgp.recently_missed_count)(\"max_undo\",GRAPHENE_MAX_UNDO_HISTORY) );\n   }\n\n   _undo_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + 1 );\n   _fork_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + 1 );\n}\n\nvoid database::update_signing_witness(const witness_object& signing_witness, const signed_block& new_block)\n{\n   const global_property_object& gpo = get_global_properties();\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n   uint64_t new_block_aslot = dpo.current_aslot + get_slot_at_time( new_block.timestamp );\n\n   share_type witness_pay = std::min( gpo.parameters.witness_pay_per_block, dpo.witness_budget );\n\n   modify( dpo, [&]( dynamic_global_property_object& _dpo )\n   {\n      _dpo.witness_budget -= witness_pay;\n   } );\n\n   deposit_witness_pay( signing_witness, witness_pay );\n\n   modify( signing_witness, [&]( witness_object& _wit )\n   {\n      _wit.last_aslot = new_block_aslot;\n      _wit.last_confirmed_block_num = new_block.block_num();\n   } );\n}\n\nvoid database::update_last_irreversible_block()\n{\n   const global_property_object& gpo = get_global_properties();\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n\n   // TODO for better performance, move this to db_maint, because only need to do it once per maintenance interval\n   vector< const witness_object* > wit_objs;\n   wit_objs.reserve( gpo.active_witnesses.size() );\n   for( const witness_id_type& wid : gpo.active_witnesses )\n      wit_objs.push_back( &(wid(*this)) );\n\n   static_assert( GRAPHENE_IRREVERSIBLE_THRESHOLD > 0, \"irreversible threshold must be nonzero\" );\n\n   // 1 1 1 2 2 2 2 2 2 2 -> 2     .3*10 = 3\n   // 1 1 1 1 1 1 1 2 2 2 -> 1\n   // 3 3 3 3 3 3 3 3 3 3 -> 3\n   // 3 3 3 4 4 4 4 4 4 4 -> 4\n\n   size_t offset = ((GRAPHENE_100_PERCENT - GRAPHENE_IRREVERSIBLE_THRESHOLD) * wit_objs.size() / GRAPHENE_100_PERCENT);\n\n   std::nth_element( wit_objs.begin(), wit_objs.begin() + offset, wit_objs.end(),\n      []( const witness_object* a, const witness_object* b )\n      {\n         return a->last_confirmed_block_num < b->last_confirmed_block_num;\n      } );\n\n   uint32_t new_last_irreversible_block_num = wit_objs[offset]->last_confirmed_block_num;\n\n   if( new_last_irreversible_block_num > dpo.last_irreversible_block_num )\n   {\n      modify( dpo, [&]( dynamic_global_property_object& _dpo )\n      {\n         _dpo.last_irreversible_block_num = new_last_irreversible_block_num;\n      } );\n   }\n}\n\nvoid database::clear_expired_transactions()\n{ try {\n   //Look for expired transactions in the deduplication list, and remove them.\n   //Transactions must have expired by at least two forking windows in order to be removed.\n   auto& transaction_idx = static_cast<transaction_index&>(get_mutable_index(implementation_ids,\n                                                                             impl_transaction_history_object_type));\n   const auto& dedupe_index = transaction_idx.indices().get<by_expiration>();\n   while( (!dedupe_index.empty()) && (head_block_time() > dedupe_index.begin()->trx.expiration) )\n      transaction_idx.remove(*dedupe_index.begin());\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid database::clear_expired_proposals()\n{\n   const auto& proposal_expiration_index = get_index_type<proposal_index>().indices().get<by_expiration>();\n   while( !proposal_expiration_index.empty() && proposal_expiration_index.begin()->expiration_time <= head_block_time() )\n   {\n      const proposal_object& proposal = *proposal_expiration_index.begin();\n      processed_transaction result;\n      try {\n         if( proposal.is_authorized_to_execute(*this) )\n         {\n            result = push_proposal(proposal);\n            //TODO: Do something with result so plugins can process it.\n            continue;\n         }\n      } catch( const fc::exception& e ) {\n         elog(\"Failed to apply proposed transaction on its expiration. Deleting it.\\n${proposal}\\n${error}\",\n              (\"proposal\", proposal)(\"error\", e.to_detail_string()));\n      }\n      remove(proposal);\n   }\n}\n\n// Helper function to check whether we need to udpate current_feed.settlement_price.\nstatic optional<price> get_derived_current_feed_price( const database& db,\n                                                       const asset_bitasset_data_object& bitasset )\n{\n   optional<price> result;\n   // check for null first\n   if( bitasset.median_feed.settlement_price.is_null() )\n   {\n      if( bitasset.current_feed.settlement_price.is_null() )\n         return result;\n      else\n         return bitasset.median_feed.settlement_price;\n   }\n\n   using bsrm_type = bitasset_options::black_swan_response_type;\n   const auto bsrm = bitasset.get_black_swan_response_method();\n   if( bsrm_type::no_settlement == bsrm )\n   {\n      // Find the call order with the least collateral ratio\n      const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true );\n      if( call_ptr )\n      {\n         // GS if : call_ptr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() )\n         auto least_collateral = call_ptr->collateralization();\n         auto lowest_callable_feed_price = (~least_collateral) / ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM,\n                                              bitasset.current_feed.maximum_short_squeeze_ratio );\n         result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price );\n      }\n      else // there is no call order of this bitasset\n         result = bitasset.median_feed.settlement_price;\n   }\n   else if( bsrm_type::individual_settlement_to_fund == bsrm && bitasset.individual_settlement_debt > 0 )\n   {\n      // Check whether to cap\n      price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id )\n                       / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset );\n      auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio();\n      result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price );\n   }\n   else // should not cap\n      result = bitasset.median_feed.settlement_price;\n\n   // Check whether it's necessary to update\n   if( result.valid() && (*result) == bitasset.current_feed.settlement_price )\n      result.reset();\n   return result;\n}\n\n// Helper function to update the limit order which is the individual settlement fund of the specified asset\nstatic void update_settled_debt_order( database& db, const asset_bitasset_data_object& bitasset )\n{\n   // To avoid unexpected price fluctuations, do not update the order if no sufficient price feeds\n   if( bitasset.current_feed.settlement_price.is_null() )\n      return;\n\n   const limit_order_object* limit_ptr = db.find_settled_debt_order( bitasset.asset_id );\n   if( !limit_ptr )\n      return;\n\n   bool sell_all = true;\n   share_type for_sale;\n\n   // Note: bitasset.get_margin_call_order_price() is in debt/collateral\n   price sell_price = ~bitasset.get_margin_call_order_price();\n   asset settled_debt( bitasset.individual_settlement_debt, limit_ptr->receive_asset_id() );\n   try\n   {\n      for_sale = settled_debt.multiply_and_round_up( sell_price ).amount; // may overflow\n      if( for_sale <= bitasset.individual_settlement_fund ) // \"=\" is for the consistency of order matching logic\n         sell_all = false;\n   }\n   catch( const fc::exception& e ) // catch the overflow\n   {\n      // do nothing\n      dlog( e.to_detail_string() );\n   }\n\n   // TODO Potential optimization: to avoid unnecessary database update, check before update\n   db.modify( *limit_ptr, [sell_all, &sell_price, &for_sale, &bitasset]( limit_order_object& obj )\n   {\n      if( sell_all )\n      {\n         obj.for_sale = bitasset.individual_settlement_fund;\n         obj.sell_price = ~bitasset.get_individual_settlement_price();\n      }\n      else\n      {\n         obj.for_sale = for_sale;\n         obj.sell_price = sell_price;\n      }\n   } );\n}\n\nvoid database::update_bitasset_current_feed( const asset_bitasset_data_object& bitasset, bool skip_median_update )\n{\n   // For better performance, if nothing to update, we return\n   optional<price> new_current_feed_price;\n   using bsrm_type = bitasset_options::black_swan_response_type;\n   const auto bsrm = bitasset.get_black_swan_response_method();\n   if( skip_median_update )\n   {\n      if( bsrm_type::no_settlement != bsrm && bsrm_type::individual_settlement_to_fund != bsrm )\n      {\n         // it's possible that current_feed was capped thus we still need to update it\n         if( bitasset.current_feed.settlement_price == bitasset.median_feed.settlement_price )\n            return;\n         new_current_feed_price = bitasset.median_feed.settlement_price;\n      }\n      else\n      {\n         new_current_feed_price = get_derived_current_feed_price( *this, bitasset );\n         if( !new_current_feed_price.valid() )\n            return;\n      }\n   }\n\n   const auto& head_time = head_block_time();\n\n   // We need to update the database\n   modify( bitasset, [this, skip_median_update, &head_time, &new_current_feed_price, &bsrm]\n                     ( asset_bitasset_data_object& abdo )\n   {\n      if( !skip_median_update )\n      {\n         const auto& maint_time = get_dynamic_global_properties().next_maintenance_time;\n         abdo.update_median_feeds( head_time, maint_time );\n         abdo.current_feed = abdo.median_feed;\n         if( bsrm_type::no_settlement == bsrm || bsrm_type::individual_settlement_to_fund == bsrm )\n            new_current_feed_price = get_derived_current_feed_price( *this, abdo );\n      }\n      if( new_current_feed_price.valid() )\n         abdo.current_feed.settlement_price = *new_current_feed_price;\n   } );\n\n   // Update individual settlement order price\n   if( !skip_median_update\n         && bsrm_type::individual_settlement_to_order == bsrm\n         && HARDFORK_CORE_2591_PASSED( head_time ) ) // Tighter peg (fill individual settlement order at MCOP)\n   {\n      update_settled_debt_order( *this, bitasset );\n   }\n}\n\nvoid database::clear_expired_orders()\n{ try {\n         //Cancel expired limit orders\n         auto head_time = head_block_time();\n         auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n\n         bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call\n\n         auto& limit_index = get_index_type<limit_order_index>().indices().get<by_expiration>();\n         while( !limit_index.empty() && limit_index.begin()->expiration <= head_time )\n         {\n            const limit_order_object& order = *limit_index.begin();\n            auto base_asset = order.sell_price.base.asset_id;\n            auto quote_asset = order.sell_price.quote.asset_id;\n            cancel_limit_order( order );\n            if( before_core_hardfork_606 )\n            {\n               // check call orders\n               // Comments below are copied from limit_order_cancel_evaluator::do_apply(...)\n               // Possible optimization: order can be called by cancelling a limit order\n               //   if the canceled order was at the top of the book.\n               // Do I need to check calls in both assets?\n               check_call_orders( base_asset( *this ) );\n               check_call_orders( quote_asset( *this ) );\n            }\n         }\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid database::clear_expired_force_settlements()\n{ try {\n   // Process expired force settlement orders\n\n   // TODO Possible performance optimization. Looping through all assets is not ideal.\n   //      - One idea is to check time first, if any expired settlement found, check asset.\n   //        However, due to max_settlement_volume, this does not work, i.e. time meets but have to\n   //        skip due to volume limit.\n   //      - Instead, maintain some data e.g. (whether_force_settle_volome_meets, first_settle_time)\n   //        in bitasset_data object and index by them, then we could process here faster.\n   //        Note: due to rounding, even when settled < max_volume, it is still possible that we have to skip\n   const auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();\n   if( settlement_index.empty() )\n      return;\n\n   const auto& head_time = head_block_time();\n   const auto& maint_time = get_dynamic_global_properties().next_maintenance_time;\n\n   const bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing\n   const bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding\n\n   asset_id_type current_asset = settlement_index.begin()->settlement_asset_id();\n   const asset_object* mia_object_ptr = &get(current_asset);\n   const asset_bitasset_data_object* mia_ptr = &mia_object_ptr->bitasset_data(*this);\n\n   asset max_settlement_volume;\n   price settlement_fill_price;\n   price settlement_price;\n   bool current_asset_finished = false;\n\n   auto next_asset = [&current_asset, &mia_object_ptr, &mia_ptr, &current_asset_finished, &settlement_index, this] {\n      const auto bound = settlement_index.upper_bound(current_asset);\n      if( bound == settlement_index.end() )\n         return false;\n      current_asset = bound->settlement_asset_id();\n      mia_object_ptr = &get(current_asset);\n      mia_ptr = &mia_object_ptr->bitasset_data(*this);\n      current_asset_finished = false;\n      return true;\n   };\n\n   // At each iteration, we either consume the current order and remove it, or we move to the next asset\n   for( auto itr = settlement_index.lower_bound(current_asset);\n        itr != settlement_index.end();\n        itr = settlement_index.lower_bound(current_asset) )\n   {\n      const force_settlement_object& settle_order = *itr;\n      auto settle_order_id = settle_order.id;\n\n      if( current_asset != settle_order.settlement_asset_id() )\n      {\n         current_asset = settle_order.settlement_asset_id();\n         mia_object_ptr = &get(current_asset);\n         mia_ptr = &mia_object_ptr->bitasset_data(*this);\n         // Note: we did not reset current_asset_finished to false here, it is OK,\n         //       because current_asset should not have changed if current_asset_finished is true\n      }\n      const asset_object& mia_object = *mia_object_ptr;\n      const asset_bitasset_data_object& mia = *mia_ptr;\n\n      if( mia.is_globally_settled() )\n      {\n         ilog( \"Canceling a force settlement because of black swan\" );\n         cancel_settle_order( settle_order );\n         continue;\n      }\n\n      // Has this order not reached its settlement date?\n      if( settle_order.settlement_date > head_time )\n      {\n         if( next_asset() )\n            continue;\n         break;\n      }\n      // Can we still settle in this asset?\n      if( mia.current_feed.settlement_price.is_null() )\n      {\n         ilog(\"Canceling a force settlement in ${asset} because settlement price is null\",\n              (\"asset\", mia_object.symbol));\n         cancel_settle_order(settle_order);\n         continue;\n      }\n      if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing\n      {\n         ilog( \"Canceling a force settlement in ${asset} because settlement offset is 100%\",\n               (\"asset\", mia_object.symbol));\n         cancel_settle_order(settle_order);\n         continue;\n      }\n      // Note: although current supply would decrease during filling the settle orders,\n      //       we always calculate with the initial value\n      if( max_settlement_volume.asset_id != current_asset )\n         max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume(\n                                                           mia_object.dynamic_data(*this).current_supply ) );\n      // When current_asset_finished is true, this would be the 2nd time processing the same order.\n      // In this case, we move to the next asset.\n      if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished )\n      {\n         if( next_asset() )\n            continue;\n         break;\n      }\n\n      if( settlement_fill_price.base.asset_id != current_asset ) // only calculate once per asset\n         settlement_fill_price = mia.current_feed.settlement_price\n                                 / ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent,\n                                               GRAPHENE_100_PERCENT );\n\n      if( before_core_hardfork_342 )\n      {\n         auto& pays = settle_order.balance;\n         auto receives = (settle_order.balance * mia.current_feed.settlement_price);\n         receives.amount = static_cast<uint64_t>( ( fc::uint128_t(receives.amount.value) *\n                             (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) ) /\n                             GRAPHENE_100_PERCENT );\n         assert(receives <= settle_order.balance * mia.current_feed.settlement_price);\n         settlement_price = pays / receives;\n      }\n      else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset\n         settlement_price = settlement_fill_price;\n\n      asset settled = mia_object.amount(mia.force_settled_volume);\n      // Match against the least collateralized short until the settlement is finished or we reach max settlements\n      while( settled < max_settlement_volume && find_object(settle_order_id) )\n      {\n         if( 0 == settle_order.balance.amount )\n         {\n            wlog( \"0 settlement detected\" );\n            cancel_settle_order( settle_order );\n            break;\n         }\n\n         const call_order_object* call_ptr = find_least_collateralized_short( mia, true );\n         // Note: there can be no debt position due to individual settlements\n         if( !call_ptr ) // no debt position\n         {\n            wlog( \"No debt position found when processing force settlement ${o}\", (\"o\",settle_order) );\n            cancel_settle_order( settle_order );\n            break;\n         }\n\n         try {\n            asset max_settlement = max_settlement_volume - settled;\n\n            asset new_settled = match( settle_order, *call_ptr, settlement_price, mia,\n                                       max_settlement, settlement_fill_price );\n            if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order\n            {\n               // current asset is finished when the settle order hasn't been cancelled\n               current_asset_finished = ( nullptr != find_object( settle_order_id ) );\n               break;\n            }\n            settled += new_settled;\n            // before hard fork core-342, `new_settled > 0` is always true, we'll have:\n            // * call order is completely filled (thus call_ptr will change in next loop), or\n            // * settle order is completely filled (thus find_object(settle_order_id) will be false so will\n            //   break out), or\n            // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out).\n            //\n            // after hard fork core-342, if new_settled > 0, we'll have:\n            // * call order is completely filled (thus call_ptr will change in next loop), or\n            // * settle order is completely filled (thus find_object(settle_order_id) will be false so will\n            //   break out), or\n            // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement,\n            //   in this case, new_settled will be zero in next iteration of the loop, so no need to check here.\n         }\n         catch ( const black_swan_exception& e ) {\n            wlog( \"Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}\",\n                  (\"o\", settle_order)(\"e\", e.to_detail_string()) );\n            cancel_settle_order( settle_order );\n            break;\n         }\n      }\n      if( mia.force_settled_volume != settled.amount )\n      {\n         modify(mia, [&settled](asset_bitasset_data_object& b) {\n            b.force_settled_volume = settled.amount;\n         });\n      }\n   }\n} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\nvoid database::update_expired_feeds()\n{\n   const auto head_time = head_block_time();\n   bool after_hardfork_615 = ( head_time >= HARDFORK_615_TIME );\n   bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues\n\n   const auto& idx = get_index_type<asset_bitasset_data_index>().indices().get<by_feed_expiration>();\n   auto itr = idx.begin();\n   while( itr != idx.end() && itr->feed_is_expired( head_time ) )\n   {\n      const asset_bitasset_data_object& b = *itr;\n      ++itr; // not always process begin() because old code skipped updating some assets before hf 615\n\n      // update feeds, check margin calls\n      if( !( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) )\n         continue;\n\n      auto old_current_feed = b.current_feed;\n      auto old_median_feed = b.median_feed;\n      const asset_object& asset_obj = b.asset_id( *this );\n      update_bitasset_current_feed( b );\n      // Note: we don't try to revive the bitasset here if it was GSed // TODO probably we should do it\n\n      if( !b.current_feed.settlement_price.is_null()\n            && !b.current_feed.margin_call_params_equal( old_current_feed ) )\n      {\n         check_call_orders( asset_obj, true, false, &b, true );\n      }\n      else if( after_core_hardfork_2582 && !b.median_feed.settlement_price.is_null()\n            && !b.median_feed.margin_call_params_equal( old_median_feed ) )\n      {\n         check_call_orders( asset_obj, true, false, &b, true );\n      }\n      // update CER\n      if( b.need_to_update_cer() )\n      {\n         modify( b, []( asset_bitasset_data_object& abdo )\n         {\n            abdo.asset_cer_updated = false;\n            abdo.feed_cer_updated = false;\n         });\n         if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate )\n         {\n            modify( asset_obj, [&b]( asset_object& ao )\n            {\n               ao.options.core_exchange_rate = b.current_feed.core_exchange_rate;\n            });\n         }\n      }\n   } // for each asset whose feed is expired\n\n   // process assets affected by bitshares-core issue 453 before hard fork 615\n   if( !after_hardfork_615 )\n   {\n      for( asset_id_type a : _issue_453_affected_assets )\n      {\n         check_call_orders( a(*this) );\n      }\n   }\n}\n\nvoid database::update_core_exchange_rates()\n{\n   const auto& idx = get_index_type<asset_bitasset_data_index>().indices().get<by_cer_update>();\n   if( idx.begin() != idx.end() )\n   {\n      for( auto itr = idx.rbegin(); itr->need_to_update_cer(); itr = idx.rbegin() )\n      {\n         const asset_bitasset_data_object& b = *itr;\n         const asset_object& a = b.asset_id( *this );\n         if( a.options.core_exchange_rate != b.current_feed.core_exchange_rate )\n         {\n            modify( a, [&b]( asset_object& ao )\n            {\n               ao.options.core_exchange_rate = b.current_feed.core_exchange_rate;\n            });\n         }\n         modify( b, []( asset_bitasset_data_object& abdo )\n         {\n            abdo.asset_cer_updated = false;\n            abdo.feed_cer_updated = false;\n         });\n      }\n   }\n}\n\nvoid database::update_maintenance_flag( bool new_maintenance_flag )\n{\n   modify( get_dynamic_global_properties(), [&]( dynamic_global_property_object& dpo )\n   {\n      auto maintenance_flag = dynamic_global_property_object::maintenance_flag;\n      dpo.dynamic_flags =\n           (dpo.dynamic_flags & (uint32_t)(~maintenance_flag))\n         | (new_maintenance_flag ? (uint32_t)maintenance_flag : 0U);\n   } );\n   return;\n}\n\nvoid database::update_withdraw_permissions()\n{\n   auto& permit_index = get_index_type<withdraw_permission_index>().indices().get<by_expiration>();\n   while( !permit_index.empty() && permit_index.begin()->expiration <= head_block_time() )\n      remove(*permit_index.begin());\n}\n\nvoid database::clear_expired_htlcs()\n{\n   const auto& htlc_idx = get_index_type<htlc_index>().indices().get<by_expiration>();\n   while ( htlc_idx.begin() != htlc_idx.end()\n         && htlc_idx.begin()->conditions.time_lock.expiration <= head_block_time() )\n   {\n      const htlc_object& obj = *htlc_idx.begin();\n      const auto amount = asset(obj.transfer.amount, obj.transfer.asset_id);\n      adjust_balance( obj.transfer.from, amount );\n      // notify related parties\n      htlc_refund_operation vop( obj.get_id(), obj.transfer.from, obj.transfer.to, amount,\n         obj.conditions.hash_lock.preimage_hash, obj.conditions.hash_lock.preimage_size );\n      push_applied_operation( vop );\n      remove( obj );\n   }\n}\n\ngeneric_operation_result database::process_tickets()\n{\n   const auto maint_time = get_dynamic_global_properties().next_maintenance_time;\n   ticket_version version = ( HARDFORK_CORE_2262_PASSED(maint_time) ? ticket_v2 : ticket_v1 );\n\n   generic_operation_result result;\n   share_type total_delta_pob;\n   share_type total_delta_inactive;\n   auto& idx = get_index_type<ticket_index>().indices().get<by_next_update>();\n   while( !idx.empty() && idx.begin()->next_auto_update_time <= head_block_time() )\n   {\n      const ticket_object& ticket = *idx.begin();\n      const auto& stat = get_account_stats_by_owner( ticket.account );\n      if( ticket.status == withdrawing && ticket.current_type == liquid )\n      {\n         adjust_balance( ticket.account, ticket.amount );\n         // Note: amount.asset_id is checked when creating the ticket, so no check here\n         modify( stat, [&ticket](account_statistics_object& aso) {\n            aso.total_core_pol -= ticket.amount.amount;\n            aso.total_pol_value -= ticket.value;\n         });\n         result.removed_objects.insert( ticket.id );\n         remove( ticket );\n      }\n      else\n      {\n         ticket_type old_type = ticket.current_type;\n         share_type old_value = ticket.value;\n         modify( ticket, [version]( ticket_object& o ) {\n            o.auto_update( version );\n         });\n         result.updated_objects.insert( ticket.id );\n\n         share_type delta_inactive_amount;\n         share_type delta_forever_amount;\n         share_type delta_forever_value;\n         share_type delta_other_amount;\n         share_type delta_other_value;\n\n         if( old_type == lock_forever ) // It implies that the new type is lock_forever too\n         {\n            if( ticket.value == 0 )\n            {\n               total_delta_pob -= ticket.amount.amount;\n               total_delta_inactive += ticket.amount.amount;\n               delta_inactive_amount = ticket.amount.amount;\n               delta_forever_amount = -ticket.amount.amount;\n            }\n            delta_forever_value = ticket.value - old_value;\n         }\n         else // old_type != lock_forever\n         {\n            if( ticket.current_type == lock_forever )\n            {\n               total_delta_pob += ticket.amount.amount;\n               delta_forever_amount = ticket.amount.amount;\n               delta_forever_value = ticket.value;\n               delta_other_amount = -ticket.amount.amount;\n               delta_other_value = -old_value;\n            }\n            else // ticket.current_type != lock_forever\n            {\n               delta_other_value = ticket.value - old_value;\n            }\n         }\n\n         // Note: amount.asset_id is checked when creating the ticket, so no check here\n         modify( stat, [delta_inactive_amount,delta_forever_amount,delta_forever_value,\n                        delta_other_amount,delta_other_value](account_statistics_object& aso) {\n            aso.total_core_inactive += delta_inactive_amount;\n            aso.total_core_pob += delta_forever_amount;\n            aso.total_core_pol += delta_other_amount;\n            aso.total_pob_value += delta_forever_value;\n            aso.total_pol_value += delta_other_value;\n         });\n\n      }\n      // TODO if a lock_forever ticket lost all the value, remove it\n   }\n\n   // TODO merge stable tickets with the same account and the same type\n\n   // Update global data\n   if( total_delta_pob != 0 || total_delta_inactive != 0 )\n   {\n      modify( get_dynamic_global_properties(),\n              [total_delta_pob,total_delta_inactive]( dynamic_global_property_object& dgp ) {\n         dgp.total_pob += total_delta_pob;\n         dgp.total_inactive += total_delta_inactive;\n      });\n   }\n\n   return result;\n}\n\nvoid database::update_credit_offers_and_deals()\n{\n   const auto head_time = head_block_time();\n\n   // Auto-disable offers\n   const auto& offer_idx = get_index_type<credit_offer_index>().indices().get<by_auto_disable_time>();\n   auto offer_itr = offer_idx.lower_bound( true );\n   auto offer_itr_end = offer_idx.upper_bound( boost::make_tuple( true, head_time ) );\n   while( offer_itr != offer_itr_end )\n   {\n      const credit_offer_object& offer = *offer_itr;\n      ++offer_itr;\n      modify( offer, []( credit_offer_object& obj ) {\n         obj.enabled = false;\n      });\n   }\n\n   // Auto-process deals\n   const auto& deal_idx = get_index_type<credit_deal_index>().indices().get<by_latest_repay_time>();\n   const auto& deal_summary_idx = get_index_type<credit_deal_summary_index>().indices().get<by_offer_borrower>();\n   auto deal_itr_end = deal_idx.upper_bound( head_time );\n   for( auto deal_itr = deal_idx.begin(); deal_itr != deal_itr_end; deal_itr = deal_idx.begin() )\n   {\n      const credit_deal_object& deal = *deal_itr;\n\n      // Process automatic repayment\n      // Note: an automatic repayment may fail, in which case we consider the credit deal past due without repayment\n      using repay_type = credit_deal_auto_repayment_type;\n      if( static_cast<uint8_t>(repay_type::no_auto_repayment) != deal.auto_repay )\n      {\n         credit_deal_repay_operation op;\n         op.account = deal.borrower;\n         op.deal_id = deal.get_id();\n         // Amounts\n         // Note: the result can be larger than 64 bit\n         auto required_fee = ( ( ( fc::uint128_t( deal.debt_amount.value ) * deal.fee_rate )\n                                 + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up\n         fc::uint128_t total_required = required_fee + deal.debt_amount.value;\n         auto balance = get_balance( deal.borrower, deal.debt_asset );\n         if( static_cast<uint8_t>(repay_type::only_full_repayment) == deal.auto_repay\n               || fc::uint128_t( balance.amount.value ) >= total_required )\n         { // if only full repayment or account balance is sufficient\n            op.repay_amount = asset( deal.debt_amount, deal.debt_asset );\n            op.credit_fee = asset( static_cast<int64_t>( required_fee ), deal.debt_asset );\n         }\n         else // Allow partial repayment\n         {\n            fc::uint128_t debt_to_repay = ( fc::uint128_t( balance.amount.value ) * GRAPHENE_FEE_RATE_DENOM )\n                                          / ( GRAPHENE_FEE_RATE_DENOM + deal.fee_rate ); // Round down\n            fc::uint128_t collateral_to_release = ( debt_to_repay * deal.collateral_amount.value )\n                                                  / deal.debt_amount.value; // Round down\n            debt_to_repay = ( ( ( collateral_to_release * deal.debt_amount.value ) + deal.collateral_amount.value )\n                              - 1 ) / deal.collateral_amount.value; // Round up\n            fc::uint128_t fee_to_pay = ( ( ( debt_to_repay * deal.fee_rate )\n                                           + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up\n            op.repay_amount = asset( static_cast<int64_t>( debt_to_repay ), deal.debt_asset );\n            op.credit_fee = asset( static_cast<int64_t>( fee_to_pay ), deal.debt_asset );\n         }\n\n         auto deal_copy = deal; // Make a copy for logging\n\n         transaction_evaluation_state eval_state(this);\n         eval_state.skip_fee_schedule_check = true;\n\n         try\n         {\n            try_push_virtual_operation( eval_state, op );\n         }\n         catch( const fc::exception& e )\n         {\n            // We can in fact get here,\n            // e.g. if the debt asset issuer blacklisted the account, or account balance is insufficient\n            wlog( \"Automatic repayment ${op} for credit deal ${credit_deal} failed at block ${n}; \"\n                  \"account balance was ${balance}; exception was ${e}\",\n                  (\"op\", op)(\"credit_deal\", deal_copy)\n                  (\"n\", head_block_num())(\"balance\", balance)(\"e\", e.to_detail_string()) );\n         }\n\n         if( !find( op.deal_id ) ) // The credit deal is fully repaid\n            continue;\n      }\n\n      // Update offer\n      // Note: offer balance can be zero after updated. TODO remove zero-balance offers after a period\n      const credit_offer_object& offer = deal.offer_id(*this);\n      modify( offer, [&deal]( credit_offer_object& obj ){\n         obj.total_balance -= deal.debt_amount;\n      });\n\n      // Process deal summary\n      auto summ_itr = deal_summary_idx.find( boost::make_tuple( deal.offer_id, deal.borrower ) );\n      if( summ_itr == deal_summary_idx.end() ) // This should not happen, just be defensive here\n      {\n         // We do not do FC_ASSERT or FC_THROW here to avoid halting the chain\n         elog( \"Error: unable to find the credit deal summary object for credit deal ${d}\",\n               (\"d\", deal) );\n      }\n      else\n      {\n         const credit_deal_summary_object& summ_obj = *summ_itr;\n         if( summ_obj.total_debt_amount == deal.debt_amount )\n         {\n            remove( summ_obj );\n         }\n         else\n         {\n            modify( summ_obj, [&deal]( credit_deal_summary_object& obj ){\n               obj.total_debt_amount -= deal.debt_amount;\n            });\n         }\n      }\n\n      // Adjust balance\n      adjust_balance( deal.offer_owner, asset( deal.collateral_amount, deal.collateral_asset ) );\n\n      // Notify related parties\n      push_applied_operation( credit_deal_expired_operation (\n                                    deal.get_id(), deal.offer_id, deal.offer_owner, deal.borrower,\n                                    asset( deal.debt_amount, deal.debt_asset ),\n                                    asset( deal.collateral_amount, deal.collateral_asset ),\n                                    deal.fee_rate ) );\n\n      // Remove the deal\n      remove( deal );\n   }\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/db_witness_schedule.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n\n#include <fc/popcount.hpp>\n\nnamespace graphene { namespace chain {\n\nusing boost::container::flat_set;\n\nwitness_id_type database::get_scheduled_witness( uint32_t slot_num )const\n{\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n   const witness_schedule_object& wso = get_witness_schedule_object();\n   uint64_t current_aslot = dpo.current_aslot + slot_num;\n   return wso.current_shuffled_witnesses[ current_aslot % wso.current_shuffled_witnesses.size() ];\n}\n\nfc::time_point_sec database::get_slot_time(uint32_t slot_num)const\n{\n   if( slot_num == 0 )\n      return fc::time_point_sec();\n\n   auto interval = block_interval();\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n\n   if( head_block_num() == 0 )\n   {\n      // n.b. first block is at genesis_time plus one block interval\n      fc::time_point_sec genesis_time = dpo.time;\n      return genesis_time + slot_num * interval;\n   }\n\n   int64_t head_block_abs_slot = head_block_time().sec_since_epoch() / interval;\n   fc::time_point_sec head_slot_time(head_block_abs_slot * interval);\n\n   const global_property_object& gpo = get_global_properties();\n\n   if( dpo.dynamic_flags & dynamic_global_property_object::maintenance_flag )\n      slot_num += gpo.parameters.maintenance_skip_slots;\n\n   // \"slot 0\" is head_slot_time\n   // \"slot 1\" is head_slot_time,\n   //   plus maint interval if head block is a maint block\n   //   plus block interval if head block is not a maint block\n   return head_slot_time + (slot_num * interval);\n}\n\nuint32_t database::get_slot_at_time(fc::time_point_sec when)const\n{\n   fc::time_point_sec first_slot_time = get_slot_time( 1 );\n   if( when < first_slot_time )\n      return 0;\n   return (when - first_slot_time).to_seconds() / block_interval() + 1;\n}\n\nuint32_t database::update_witness_missed_blocks( const signed_block& b )\n{\n   uint32_t missed_blocks = get_slot_at_time( b.timestamp );\n   FC_ASSERT( missed_blocks != 0, \"Trying to push double-produced block onto current block?!\" );\n   missed_blocks--;\n   const auto& witnesses = witness_schedule_id_type()(*this).current_shuffled_witnesses;\n   if( missed_blocks < witnesses.size() )\n      for( uint32_t i = 0; i < missed_blocks; ++i ) {\n         const auto& witness_missed = get_scheduled_witness( i+1 )(*this);\n         modify( witness_missed, []( witness_object& w ) {\n            w.total_missed++;\n         });\n      }\n   return missed_blocks;\n}\n\nuint32_t database::witness_participation_rate()const\n{\n   const dynamic_global_property_object& dpo = get_dynamic_global_properties();\n   return uint64_t(GRAPHENE_100_PERCENT) * fc::popcount(dpo.recent_slots_filled) / 128;\n}\n\nvoid database::update_witness_schedule()\n{\n   const witness_schedule_object& wso = get_witness_schedule_object();\n   const global_property_object& gpo = get_global_properties();\n\n   if( head_block_num() % gpo.active_witnesses.size() == 0 )\n   {\n      modify( wso, [&]( witness_schedule_object& _wso )\n      {\n         _wso.current_shuffled_witnesses.clear();\n         _wso.current_shuffled_witnesses.reserve( gpo.active_witnesses.size() );\n\n         for( const witness_id_type& w : gpo.active_witnesses )\n            _wso.current_shuffled_witnesses.push_back( w );\n\n         auto now_hi = uint64_t(head_block_time().sec_since_epoch()) << 32;\n         for( uint32_t i = 0; i < _wso.current_shuffled_witnesses.size(); ++i )\n         {\n            /// High performance random generator\n            /// http://xorshift.di.unimi.it/\n            uint64_t k = now_hi + uint64_t(i)*2685821657736338717ULL;\n            k ^= (k >> 12);\n            k ^= (k << 25);\n            k ^= (k >> 27);\n            k *= 2685821657736338717ULL;\n\n            uint32_t jmax = _wso.current_shuffled_witnesses.size() - i;\n            uint32_t j = i + k%jmax;\n            std::swap( _wso.current_shuffled_witnesses[i],\n                       _wso.current_shuffled_witnesses[j] );\n         }\n      });\n   }\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n#include <graphene/chain/transaction_evaluation_state.hpp>\n\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/market_evaluator.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\nnamespace graphene { namespace chain {\ndatabase& generic_evaluator::db()const { return trx_state->db(); }\n\n   operation_result generic_evaluator::start_evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply )\n   { try {\n      trx_state   = &eval_state;\n      //check_required_authorities(op);\n      auto result = evaluate( op );\n\n      if( apply ) result = this->apply( op );\n      return result;\n   } FC_CAPTURE_AND_RETHROW() }\n\n   void generic_evaluator::prepare_fee(account_id_type account_id, asset fee)\n   {\n      const database& d = db();\n      fee_from_account = fee;\n      FC_ASSERT( fee.amount >= 0 );\n      fee_paying_account = &account_id(d);\n      fee_paying_account_statistics = &fee_paying_account->statistics(d);\n\n      fee_asset = &fee.asset_id(d);\n      fee_asset_dyn_data = &fee_asset->dynamic_asset_data_id(d);\n\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *fee_asset ), \n            \"Account ${acct} '${name}' attempted to pay fee by using asset ${a} '${sym}', \"\n            \"which is unauthorized due to whitelist / blacklist\",\n            ( \"acct\", fee_paying_account->id)(\"name\", fee_paying_account->name)(\"a\", fee_asset->id)\n            (\"sym\", fee_asset->symbol) );\n\n      if( fee_from_account.asset_id == asset_id_type() )\n         core_fee_paid = fee_from_account.amount;\n      else\n      {\n         asset fee_from_pool = fee_from_account * fee_asset->options.core_exchange_rate;\n         FC_ASSERT( fee_from_pool.asset_id == asset_id_type() );\n         core_fee_paid = fee_from_pool.amount;\n         FC_ASSERT( core_fee_paid <= fee_asset_dyn_data->fee_pool, \"Fee pool balance of '${b}' is less than the ${r} required to convert ${c}\",\n                    (\"r\", db().to_pretty_string( fee_from_pool))(\"b\",db().to_pretty_string(fee_asset_dyn_data->fee_pool))(\"c\",db().to_pretty_string(fee)) );\n      }\n   }\n\n   void generic_evaluator::convert_fee()\n   {\n      if( fee_asset->get_id() != asset_id_type() )\n      {\n         db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) {\n            d.accumulated_fees += fee_from_account.amount;\n            d.fee_pool -= core_fee_paid;\n         });\n      }\n   }\n\n   void generic_evaluator::pay_fee()\n   { try {\n      database& d = db();\n      d.modify(*fee_paying_account_statistics, [this,&d](account_statistics_object& s)\n      {\n         s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold );\n      });\n   } FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE\n\n   void generic_evaluator::pay_fba_fee( uint64_t fba_id )\n   {\n      database& d = db();\n      const fba_accumulator_object& fba = d.get( fba_accumulator_id_type( fba_id ) );\n      if( !fba.is_configured(d) )\n      {\n         generic_evaluator::pay_fee();\n         return;\n      }\n      d.modify( fba, [&]( fba_accumulator_object& _fba )\n      {\n         _fba.accumulated_fba_fees += core_fee_paid;\n      } );\n   }\n\n   share_type generic_evaluator::calculate_fee_for_operation(const operation& op) const\n   {\n     return db().current_fee_schedule().calculate_fee( op ).amount;\n   }\n   void generic_evaluator::db_adjust_balance(const account_id_type& fee_payer, asset fee_from_account)\n   {\n     db().adjust_balance(fee_payer, fee_from_account);\n   }\n\n} }\n"
  },
  {
    "path": "libraries/chain/exceptions.cpp",
    "content": "/*\n * Copyright (c) 2019 BitShares Blockchain Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/internal_exceptions.hpp>\n\nnamespace graphene { namespace chain {\n\n   // Internal exceptions\n\n   FC_IMPLEMENT_DERIVED_EXCEPTION( internal_exception, chain_exception, 3990000, \"internal exception\" )\n\n   GRAPHENE_IMPLEMENT_INTERNAL_EXCEPTION( verify_auth_max_auth_exceeded, 1, \"Exceeds max authority fan-out\" )\n   GRAPHENE_IMPLEMENT_INTERNAL_EXCEPTION( verify_auth_account_not_found, 2, \"Auth account not found\" )\n\n\n   // Public exceptions\n\n   FC_IMPLEMENT_EXCEPTION( chain_exception, 3000000, \"blockchain exception\" )\n\n   FC_IMPLEMENT_DERIVED_EXCEPTION( database_query_exception,     chain_exception, 3010000,\n                                   \"database query exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( block_validate_exception,     chain_exception, 3020000,\n                                   \"block validation exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( transaction_process_exception,chain_exception, 3030000,\n                                   \"transaction processing exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( operation_validate_exception, chain_exception, 3040000,\n                                   \"operation validation exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( operation_evaluate_exception, chain_exception, 3050000,\n                                   \"operation evaluation exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( utility_exception,            chain_exception, 3060000,\n                                   \"utility method exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( undo_database_exception,      chain_exception, 3070000,\n                                   \"undo database exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( unlinkable_block_exception,   chain_exception, 3080000, \"unlinkable block\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( black_swan_exception,         chain_exception, 3090000, \"black swan\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( plugin_exception,             chain_exception, 3100000, \"plugin exception\" )\n\n   FC_IMPLEMENT_DERIVED_EXCEPTION( insufficient_feeds,           chain_exception, 37006, \"insufficient feeds\" )\n\n   FC_IMPLEMENT_DERIVED_EXCEPTION( duplicate_transaction,        transaction_process_exception, 3030001,\n                                   \"duplicate transaction\" )\n\n   FC_IMPLEMENT_DERIVED_EXCEPTION( pop_empty_chain,              undo_database_exception, 3070001,\n                                   \"there are no blocks to pop\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( transfer );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( from_account_not_whitelisted, transfer, 1, \"owner mismatch\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( to_account_not_whitelisted, transfer, 2, \"owner mismatch\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( restricted_transfer_asset, transfer, 3, \"restricted transfer asset\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_create );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( kill_unfilled, limit_order_create, 1,\n         \"Killing limit order due to unable to fill\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( market_not_whitelisted, limit_order_create, 2,\n         \"The market has not been whitelisted by the selling asset\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( market_blacklisted, limit_order_create, 3,\n         \"The market has been blacklisted by the selling asset\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( selling_asset_unauthorized, limit_order_create, 4,\n         \"The account is not allowed to transact the selling asset\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( receiving_asset_unauthorized, limit_order_create, 5,\n         \"The account is not allowed to transact the receiving asset\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( insufficient_balance, limit_order_create, 6,\n         \"Insufficient balance\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_update );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_update, 1, \"Order does not exist\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_update, 2, \"Order owned by someone else\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_cancel );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_cancel, 1, \"Order does not exist\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_cancel, 2, \"Order owned by someone else\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( call_order_update );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( unfilled_margin_call, call_order_update, 1,\n         \"Updating call order would trigger a margin call that cannot be fully filled\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( account_create );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_create, 1, \"Exceeds max authority fan-out\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( auth_account_not_found, account_create, 2, \"Auth account not found\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( buyback_incorrect_issuer, account_create, 3,\n         \"Incorrect issuer specified for account\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( buyback_already_exists, account_create, 4,\n         \"Cannot create buyback for asset which already has buyback\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( buyback_too_many_markets, account_create, 5, \"Too many buyback markets\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( account_update );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_update, 1, \"Exceeds max authority fan-out\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( auth_account_not_found, account_update, 2, \"Auth account not found\" )\n\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( account_whitelist );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( account_upgrade );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( account_transfer );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_create );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_update );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_update_bitasset );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_update_feed_producers );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_issue );\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_reserve );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( invalid_on_mia, asset_reserve, 1, \"invalid on mia\" )\n\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_fund_fee_pool );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_settle );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_global_settle );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_publish_feed );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( committee_member_create );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( witness_create );\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( proposal_create );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( review_period_required, proposal_create, 1, \"review_period required\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( review_period_insufficient, proposal_create, 2,\n         \"review_period insufficient\" )\n\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( proposal_update );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( proposal_delete );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( withdraw_permission_create );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( withdraw_permission_update );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( withdraw_permission_claim );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( withdraw_permission_delete );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( fill_order );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( global_parameters_update );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( vesting_balance_create );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( vesting_balance_withdraw );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( worker_create );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( custom );\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( assert );\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( balance_claim );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( claimed_too_often, balance_claim, 1, \"balance claimed too often\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( invalid_claim_amount, balance_claim, 2, \"invalid claim amount\" )\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, balance_claim, 3, \"owner mismatch\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( override_transfer );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( not_permitted, override_transfer, 1, \"not permitted\" )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( blind_transfer );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( unknown_commitment, blind_transfer, 1,\n         \"Attempting to claim an unknown prior commitment\" );\n\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( transfer_from_blind_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_claim_fees_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( bid_collateral_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_claim_pool_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( asset_update_issuer_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( htlc_create_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( htlc_redeem_operation )\n   //GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( htlc_extend_operation )\n\n   GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( liquidity_pool_exchange );\n   GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( unfillable_price, liquidity_pool_exchange, 1,\n         \"Unable to exchange at expected price\" );\n\n   #define GRAPHENE_RECODE_EXC( cause_type, effect_type ) \\\n      catch( const cause_type& e ) \\\n      { throw( effect_type( e.what(), e.get_log() ) ); }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/fba_object.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/fba_object.hpp>\n\nnamespace graphene { namespace chain {\n\nbool fba_accumulator_object::is_configured( const database& db )const\n{\n   if( !designated_asset.valid() )\n   {\n      ilog( \"FBA fee in block ${b} not paid because designated asset was not configured\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n   const asset_object* dasset = db.find(*designated_asset);\n   if( dasset == nullptr )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  designated asset does not exist\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n   if( dasset->is_market_issued() )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  FBA is a BitAsset\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n\n   const uint16_t allowed_flags = charge_market_fee;\n\n   // check enabled issuer_permissions bits is subset of allowed_flags bits\n   if( (dasset->options.issuer_permissions & allowed_flags) != dasset->options.issuer_permissions )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  Disallowed permissions enabled\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n\n   // check enabled issuer_permissions bits is subset of allowed_flags bits\n   if( (dasset->options.flags & allowed_flags) != dasset->options.flags )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  Disallowed flags enabled\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n\n   if( !dasset->buyback_account.valid() )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  designated asset does not have a buyback account\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n   const account_object& issuer_acct = dasset->issuer(db);\n\n   if( !issuer_acct.owner_special_authority.is_type< top_holders_special_authority >() )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  designated asset issuer has not set owner top_n control\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n   if( !issuer_acct.active_special_authority.is_type< top_holders_special_authority >() )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  designated asset issuer has not set active top_n control\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n   if( issuer_acct.owner_special_authority.get< top_holders_special_authority >().asset != *designated_asset )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  designated asset issuer's top_n_control is not set to designated asset\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n   if( issuer_acct.active_special_authority.get< top_holders_special_authority >().asset != *designated_asset )\n   {\n      ilog( \"FBA fee in block ${b} not paid because of FBA misconfiguration:  designated asset issuer's top_n_control is not set to designated asset\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n\n   if( issuer_acct.top_n_control_flags != (account_object::top_n_control_owner | account_object::top_n_control_active) )\n   {\n      ilog( \"FBA fee in block ${b} not paid because designated asset's top_n control has not yet activated (wait until next maintenance interval)\", (\"b\", db.head_block_num()) );\n      return false;\n   }\n\n   return true;\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/fork_database.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/fork_database.hpp>\n#include <graphene/chain/exceptions.hpp>\n\nnamespace graphene { namespace chain {\nfork_database::fork_database()\n{\n}\nvoid fork_database::reset()\n{\n   _head.reset();\n   _index.clear();\n}\n\nvoid fork_database::pop_block()\n{\n   FC_ASSERT( _head, \"no block to pop\" );\n   auto prev = _head->prev.lock();\n   FC_ASSERT( prev, \"popping block would leave head block null\" );\n   _head = prev;\n}\n\nvoid     fork_database::start_block(signed_block b)\n{\n   auto item = std::make_shared<fork_item>(std::move(b));\n   _index.insert(item);\n   _head = item;\n}\n\n/**\n * Pushes the block into the fork database\n *\n */\nshared_ptr<fork_item>  fork_database::push_block(const signed_block& b)\n{\n   auto item = std::make_shared<fork_item>(b);\n   try {\n      _push_block(item);\n   }\n   catch ( const unlinkable_block_exception& e )\n   {\n      wlog( \"Pushing block to fork database that failed to link: ${id}, ${num}\", (\"id\",b.id())(\"num\",b.block_num()) );\n      wlog( \"Head: ${num}, ${id}\", (\"num\",_head->data.block_num())(\"id\",_head->data.id()) );\n      throw;\n   }\n   return _head;\n}\n\nvoid  fork_database::_push_block(const item_ptr& item)\n{\n   if( _head ) // make sure the block is within the range that we are caching\n   {\n      FC_ASSERT( item->num > std::max<int64_t>( 0, int64_t(_head->num) - (_max_size) ),\n                 \"attempting to push a block that is too old\", \n                 (\"item->num\",item->num)(\"head\",_head->num)(\"max_size\",_max_size));\n   }\n\n   if( _head && item->previous_id() != block_id_type() )\n   {\n      auto& index = _index.get<block_id>();\n      auto itr = index.find(item->previous_id());\n      GRAPHENE_ASSERT(itr != index.end(), unlinkable_block_exception, \"block does not link to known chain\");\n      item->prev = *itr;\n   }\n\n   _index.insert(item);\n   if( !_head ) _head = item;\n   else if( item->num > _head->num )\n   {\n      _head = item;\n      uint32_t min_num = _head->num - std::min( _max_size, _head->num );\n      auto& num_idx = _index.get<block_num>();\n      while( !num_idx.empty() && (*num_idx.begin())->num < min_num )\n         num_idx.erase( num_idx.begin() );\n   }\n}\n\nvoid fork_database::set_max_size( uint32_t s )\n{\n   _max_size = s;\n   if( !_head ) return;\n\n   auto& by_num_idx = _index.get<block_num>();\n   auto itr = by_num_idx.begin();\n   while( itr != by_num_idx.end() )\n   {\n      if( (*itr)->num < std::max(int64_t(0),int64_t(_head->num) - _max_size) )\n         by_num_idx.erase(itr);\n      else\n         break;\n      itr = by_num_idx.begin();\n   }\n}\n\nbool fork_database::is_known_block(const block_id_type& id)const\n{\n   auto& index = _index.get<block_id>();\n   auto itr = index.find(id);\n   return itr != index.end();\n}\n\nitem_ptr fork_database::fetch_block(const block_id_type& id)const\n{\n   auto& index = _index.get<block_id>();\n   auto itr = index.find(id);\n   if( itr != index.end() )\n      return *itr;\n   return item_ptr();\n}\n\nvector<item_ptr> fork_database::fetch_block_by_number(uint32_t num)const\n{\n   vector<item_ptr> result;\n   auto itr = _index.get<block_num>().find(num);\n   while( itr != _index.get<block_num>().end() )\n   {\n      if( (*itr)->num == num )\n         result.push_back( *itr );\n      else\n         break;\n      ++itr;\n   }\n   return result;\n}\n\npair<fork_database::branch_type,fork_database::branch_type>\n  fork_database::fetch_branch_from(block_id_type first, block_id_type second)const\n{ try {\n   // This function gets a branch (i.e. vector<fork_item>) leading\n   // back to the most recent common ancestor.\n   pair<branch_type,branch_type> result;\n   auto first_branch_itr = _index.get<block_id>().find(first);\n   FC_ASSERT(first_branch_itr != _index.get<block_id>().end());\n   auto first_branch = *first_branch_itr;\n\n   auto second_branch_itr = _index.get<block_id>().find(second);\n   FC_ASSERT(second_branch_itr != _index.get<block_id>().end());\n   auto second_branch = *second_branch_itr;\n\n\n   while( first_branch->data.block_num() > second_branch->data.block_num() )\n   {\n      result.first.push_back(first_branch);\n      first_branch = first_branch->prev.lock();\n      FC_ASSERT(first_branch);\n   }\n   while( second_branch->data.block_num() > first_branch->data.block_num() )\n   {\n      result.second.push_back( second_branch );\n      second_branch = second_branch->prev.lock();\n      FC_ASSERT(second_branch);\n   }\n   while( first_branch->data.previous != second_branch->data.previous )\n   {\n      result.first.push_back(first_branch);\n      result.second.push_back(second_branch);\n      first_branch = first_branch->prev.lock();\n      FC_ASSERT(first_branch);\n      second_branch = second_branch->prev.lock();\n      FC_ASSERT(second_branch);\n   }\n   if( first_branch && second_branch )\n   {\n      result.first.push_back(first_branch);\n      result.second.push_back(second_branch);\n   }\n   return result;\n} FC_CAPTURE_AND_RETHROW( (first)(second) ) }\n\nvoid fork_database::set_head(shared_ptr<fork_item> h)\n{\n   _head = h;\n}\n\nvoid fork_database::remove(block_id_type id)\n{\n   _index.get<block_id>().erase(id);\n   // If we're removing head, try to pop it\n   if( _head && _head->id == id )\n   {\n      try\n      {\n         pop_block();\n      }\n      catch( fc::exception& e ) // If unable to pop normally, E.G. if head's prev is null, reset it\n      {\n         _head.reset();\n      }\n   }\n}\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/genesis_state.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/genesis_state.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace chain {\n\nchain_id_type genesis_state_type::compute_chain_id() const\n{\n   return initial_chain_id;\n}\n\nvoid genesis_state_type::override_witness_signing_keys( const std::string& new_key )\n{\n   public_key_type new_pubkey( new_key );\n   for( auto& wit : initial_witness_candidates )\n   {\n      wit.block_signing_key = new_pubkey;\n   }\n}\n\n} } // graphene::chain\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_account_type, BOOST_PP_SEQ_NIL,\n           (name)(owner_key)(active_key)(is_lifetime_member) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_asset_type, BOOST_PP_SEQ_NIL,\n           (symbol)(issuer_name)(description)(precision)(max_supply)(accumulated_fees)(is_bitasset)\n           (collateral_records))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_asset_type::initial_collateral_position,\n           BOOST_PP_SEQ_NIL, (owner)(collateral)(debt))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_balance_type, BOOST_PP_SEQ_NIL,\n           (owner)(asset_symbol)(amount))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_vesting_balance_type, BOOST_PP_SEQ_NIL,\n           (owner)(asset_symbol)(amount)(begin_timestamp)(vesting_duration_seconds)(begin_balance))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_witness_type, BOOST_PP_SEQ_NIL,\n           (owner_name)(block_signing_key))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_committee_member_type, BOOST_PP_SEQ_NIL,\n           (owner_name))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type::initial_worker_type, BOOST_PP_SEQ_NIL,\n           (owner_name)(daily_pay))\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::chain::genesis_state_type, BOOST_PP_SEQ_NIL,\n           (initial_timestamp)(max_core_supply)(initial_parameters)(initial_accounts)(initial_assets)\n           (initial_balances)(initial_vesting_balances)(initial_active_witnesses)(initial_witness_candidates)\n           (initial_committee_candidates)(initial_worker_candidates)\n           (immutable_parameters))\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_account_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_asset_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_asset_type::initial_collateral_position )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_balance_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_vesting_balance_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_witness_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_committee_member_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_worker_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type )\n"
  },
  {
    "path": "libraries/chain/get_config.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/get_config.hpp>\n#include <graphene/chain/config.hpp>\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace chain {\n\nfc::variant_object get_config()\n{\n   fc::mutable_variant_object result;\n\n   result[ \"GRAPHENE_SYMBOL\" ] = GRAPHENE_SYMBOL;\n   result[ \"GRAPHENE_ADDRESS_PREFIX\" ] = GRAPHENE_ADDRESS_PREFIX;\n   result[ \"GRAPHENE_MIN_ACCOUNT_NAME_LENGTH\" ] = GRAPHENE_MIN_ACCOUNT_NAME_LENGTH;\n   result[ \"GRAPHENE_MAX_ACCOUNT_NAME_LENGTH\" ] = GRAPHENE_MAX_ACCOUNT_NAME_LENGTH;\n   result[ \"GRAPHENE_MIN_ASSET_SYMBOL_LENGTH\" ] = GRAPHENE_MIN_ASSET_SYMBOL_LENGTH;\n   result[ \"GRAPHENE_MAX_ASSET_SYMBOL_LENGTH\" ] = GRAPHENE_MAX_ASSET_SYMBOL_LENGTH;\n   result[ \"GRAPHENE_MAX_SHARE_SUPPLY\" ] = GRAPHENE_MAX_SHARE_SUPPLY;\n   result[ \"GRAPHENE_MAX_SIG_CHECK_DEPTH\" ] = GRAPHENE_MAX_SIG_CHECK_DEPTH;\n   result[ \"GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT\" ] = GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT;\n   result[ \"GRAPHENE_MIN_BLOCK_INTERVAL\" ] = GRAPHENE_MIN_BLOCK_INTERVAL;\n   result[ \"GRAPHENE_MAX_BLOCK_INTERVAL\" ] = GRAPHENE_MAX_BLOCK_INTERVAL;\n   result[ \"GRAPHENE_DEFAULT_BLOCK_INTERVAL\" ] = GRAPHENE_DEFAULT_BLOCK_INTERVAL;\n   result[ \"GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE\" ] = GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE;\n   result[ \"GRAPHENE_DEFAULT_MAX_BLOCK_SIZE\" ] = GRAPHENE_DEFAULT_MAX_BLOCK_SIZE;\n   result[ \"GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION\" ] = GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION;\n   result[ \"GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL\" ] = GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL;\n   result[ \"GRAPHENE_DEFAULT_MAINTENANCE_SKIP_SLOTS\" ] = GRAPHENE_DEFAULT_MAINTENANCE_SKIP_SLOTS;\n   result[ \"GRAPHENE_MIN_UNDO_HISTORY\" ] = GRAPHENE_MIN_UNDO_HISTORY;\n   result[ \"GRAPHENE_MAX_UNDO_HISTORY\" ] = GRAPHENE_MAX_UNDO_HISTORY;\n   result[ \"GRAPHENE_MIN_BLOCK_SIZE_LIMIT\" ] = GRAPHENE_MIN_BLOCK_SIZE_LIMIT;\n   result[ \"GRAPHENE_BLOCKCHAIN_PRECISION\" ] = GRAPHENE_BLOCKCHAIN_PRECISION;\n   result[ \"GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS\" ] = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;\n   result[ \"GRAPHENE_100_PERCENT\" ] = GRAPHENE_100_PERCENT;\n   result[ \"GRAPHENE_1_PERCENT\" ] = GRAPHENE_1_PERCENT;\n   result[ \"GRAPHENE_MAX_MARKET_FEE_PERCENT\" ] = GRAPHENE_MAX_MARKET_FEE_PERCENT;\n   result[ \"GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY\" ] = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY;\n   result[ \"GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET\" ] = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET;\n   result[ \"GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME\" ] = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME;\n   result[ \"GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME\" ] = GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME;\n   result[ \"GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP\" ] = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP;\n   result[ \"GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES\" ] = GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES;\n   result[ \"GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS\" ] = GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS;\n   result[ \"GRAPHENE_COLLATERAL_RATIO_DENOM\" ] = GRAPHENE_COLLATERAL_RATIO_DENOM;\n   result[ \"GRAPHENE_MIN_COLLATERAL_RATIO\" ] = GRAPHENE_MIN_COLLATERAL_RATIO;\n   result[ \"GRAPHENE_MAX_COLLATERAL_RATIO\" ] = GRAPHENE_MAX_COLLATERAL_RATIO;\n   result[ \"GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO\" ] = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;\n   result[ \"GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO\" ] = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO;\n   result[ \"GRAPHENE_DEFAULT_MAX_WITNESSES\" ] = GRAPHENE_DEFAULT_MAX_WITNESSES;\n   result[ \"GRAPHENE_DEFAULT_MAX_COMMITTEE\" ] = GRAPHENE_DEFAULT_MAX_COMMITTEE;\n   result[ \"GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC\" ] = GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC;\n   result[ \"GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC\" ] = GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC;\n   result[ \"GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE\" ] = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n   result[ \"GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE\" ] = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE;\n   result[ \"GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC\" ] = GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC;\n   result[ \"GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD\" ] = GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD;\n   result[ \"GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE\" ] = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE;\n   result[ \"GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE\" ] = GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE;\n   result[ \"GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD\" ] = GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD;\n   result[ \"GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE\" ] = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE;\n   result[ \"GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS\" ] = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS;\n   result[ \"GRAPHENE_MAX_WORKER_NAME_LENGTH\" ] = GRAPHENE_MAX_WORKER_NAME_LENGTH;\n   result[ \"GRAPHENE_MAX_URL_LENGTH\" ] = GRAPHENE_MAX_URL_LENGTH;\n   result[ \"GRAPHENE_CORE_ASSET_CYCLE_RATE\" ] = GRAPHENE_CORE_ASSET_CYCLE_RATE;\n   result[ \"GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS\" ] = GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS;\n   result[ \"GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK\" ] = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK;\n   result[ \"GRAPHENE_DEFAULT_WITNESS_PAY_VESTING_SECONDS\" ] = GRAPHENE_DEFAULT_WITNESS_PAY_VESTING_SECONDS;\n   result[ \"GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY\" ] = GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY;\n   result[ \"GRAPHENE_COMMITTEE_ACCOUNT\" ] = fc::variant(GRAPHENE_COMMITTEE_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS);\n   result[ \"GRAPHENE_WITNESS_ACCOUNT\" ] = fc::variant(GRAPHENE_WITNESS_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS);\n   result[ \"GRAPHENE_RELAXED_COMMITTEE_ACCOUNT\" ] = fc::variant(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS);\n   result[ \"GRAPHENE_NULL_ACCOUNT\" ] = fc::variant(GRAPHENE_NULL_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS);\n   result[ \"GRAPHENE_TEMP_ACCOUNT\" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS);\n\n   return result;\n}\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/hardfork.d/000-200-preamble.hf",
    "content": "/*****************************************\n *                                       *\n * This file is automatically generated. *\n * To create new hardfork, please modify *\n * the .hf files in hardfork.d instead   *\n * of modifying this file.               *\n *                                       *\n *****************************************/\n\n#pragma once\n"
  },
  {
    "path": "libraries/chain/hardfork.d/385.hf",
    "content": "// #385 October 23 enforce PARENT.CHILD and allow short names\n#ifndef HARDFORK_385_TIME\n#define HARDFORK_385_TIME (fc::time_point_sec( 1445558400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/436.hf",
    "content": "// #436 Prevent margin call from being triggered unless feed < call price\n#ifndef HARDFORK_436_TIME\n#define HARDFORK_436_TIME (fc::time_point_sec( 1450288800 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/445.hf",
    "content": "// #445 Refund create order fees on cancel\n#ifndef HARDFORK_445_TIME\n#define HARDFORK_445_TIME (fc::time_point_sec( 1450288800 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/453.hf",
    "content": "// #453 Hardfork to retroactively correct referral percentages\n#ifndef HARDFORK_453_TIME\n#define HARDFORK_453_TIME (fc::time_point_sec( 1450288800 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/480.hf",
    "content": "// #480 Fix non-BTS MIA core_exchange_rate check\n#ifndef HARDFORK_480_TIME\n#define HARDFORK_480_TIME (fc::time_point_sec( 1450378800 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/483.hf",
    "content": "// #483 Operation history numbering change\n#ifndef HARDFORK_483_TIME\n#define HARDFORK_483_TIME (fc::time_point_sec( 1450378800 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/533.hf",
    "content": "// #533 Improve vote counting implementation\n#ifndef HARDFORK_533_TIME\n#define HARDFORK_533_TIME (fc::time_point_sec( 1456250400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/555.hf",
    "content": "// #555 Buyback accounts\n#ifndef HARDFORK_555_TIME\n#define HARDFORK_555_TIME (fc::time_point_sec( 1456250400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/563.hf",
    "content": "// #563 Stealth fee routing\n#ifndef HARDFORK_563_TIME\n#define HARDFORK_563_TIME (fc::time_point_sec( 1456250400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/607.hf",
    "content": "// #607 Disable negative voting on workers\n#ifndef HARDFORK_607_TIME\n#define HARDFORK_607_TIME (fc::time_point_sec( 1458752400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/613.hf",
    "content": "// #613 Deprecate annual membership\n#ifndef HARDFORK_613_TIME\n#define HARDFORK_613_TIME (fc::time_point_sec( 1458752400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/615.hf",
    "content": "// #615 Fix price feed expiration check, so websocket server will never spam too much data\n#ifndef HARDFORK_615_TIME\n#define HARDFORK_615_TIME (fc::time_point_sec( 1458752400 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/BSIP_40.hf",
    "content": "// BSIP 40 (Custom Active Authorities) hardfork check\n#ifndef HARDFORK_BSIP_40_TIME\n// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled\n#define HARDFORK_BSIP_40_TIME (fc::time_point_sec( 1893456000 ))\n#define HARDFORK_BSIP_40_PASSED(now) (now >= HARDFORK_BSIP_40_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/BSIP_48_75.hf",
    "content": "// hardfork check for\n// - BSIP 48 : new issuer permissions \"lock_max_supply\" and \"disable_new_supply\", precision update, skip cer\n// - BSIP 75 : asset owner set MCR, ICR and MSSR\n#ifndef HARDFORK_BSIP_48_75_TIME\n#define HARDFORK_BSIP_48_75_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#define HARDFORK_BSIP_48_75_PASSED(now) (now >= HARDFORK_BSIP_48_75_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/BSIP_77.hf",
    "content": "// BSIP 77 (\"Initial Collateral Ratio\" (ICR)) hardfork check\n#ifndef HARDFORK_BSIP_77_TIME\n#define HARDFORK_BSIP_77_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#define HARDFORK_BSIP_77_PASSED(now) (now >= HARDFORK_BSIP_77_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/BSIP_81.hf",
    "content": "// BSIP 81 (Simple Maker-Taker Market Fees) hardfork check\n#ifndef HARDFORK_BSIP_81_TIME\n#define HARDFORK_BSIP_81_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/BSIP_85.hf",
    "content": "// BSIP 85 (Maker order creation fee discount) hardfork check\n#ifndef HARDFORK_BSIP_85_TIME\n#define HARDFORK_BSIP_85_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#define HARDFORK_BSIP_85_PASSED(now) (now >= HARDFORK_BSIP_85_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/BSIP_86.hf",
    "content": "// BSIP 86 (Share market fees to the network) hardfork check\n#ifndef HARDFORK_BSIP_86_TIME\n#define HARDFORK_BSIP_86_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#define HARDFORK_BSIP_86_PASSED(now) (now >= HARDFORK_BSIP_86_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1270.hf",
    "content": "// bitshares-core issue #1270 Call price is inconsistent when MCR changed\n#ifndef HARDFORK_CORE_1270_TIME\n#define HARDFORK_CORE_1270_TIME (fc::time_point_sec( 1556028120 ) ) // 2019-04-23T14:02:00Z\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1465.hf",
    "content": "// bitshares-core issue #1465 check max_supply before processing call_order_update\n#ifndef HARDFORK_CORE_1465_TIME\n#define HARDFORK_CORE_1465_TIME (fc::time_point_sec( 1556028120 ) ) // 2019-04-23T14:02:00Z\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1479.hf",
    "content": "// bitshares-core issue #1479 nodes crashing on self-approving proposal\n#ifndef HARDFORK_CORE_1479_TIME\n#define HARDFORK_CORE_1479_TIME (fc::time_point_sec( 1545436800 )) // 2018-12-22T00:00:00Z\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1573.hf",
    "content": "// bitshares-core issue #1573 check transaction size\n#ifndef HARDFORK_CORE_1573_TIME\n#define HARDFORK_CORE_1573_TIME (fc::time_point_sec( 1556028120 ) ) // 2019-04-23T14:02:00Z\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1604.hf",
    "content": "// bitshares-core issue #1604 Operation to modify existing limit order\n#ifndef HARDFORK_CORE_1604_TIME\n#define HARDFORK_CORE_1604_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC\n#define HARDFORK_CORE_1604_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_1604_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1669.hf",
    "content": "// bitshares-core issue #1669 Stop using call_price when globally settling\n#ifndef HARDFORK_CORE_1669_TIME\n#define HARDFORK_CORE_1669_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1692.hf",
    "content": "// bitshares-core issue #1692 validation check of bid_collateral\n#ifndef HARDFORK_CORE_1692_TIME\n#define HARDFORK_CORE_1692_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1774.hf",
    "content": "// #1774 Too restrictive check in market fee sharing\n#ifndef HARDFORK_1774_TIME\n#define HARDFORK_1774_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1780.hf",
    "content": "// Market fees of settle orders aren't shared to referral program\n#ifndef HARDFORK_CORE_1780_TIME\n#define HARDFORK_CORE_1780_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_1800.hf",
    "content": "// bitshares-core issue #1800 Fix \"Temp-account market fee sharing\"\n#ifndef HARDFORK_CORE_1800_TIME\n#define HARDFORK_CORE_1800_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_184.hf",
    "content": "// bitshares-core issue #184 Fix \"Potential something-for-nothing fill bug\"\n#ifndef HARDFORK_CORE_184_TIME\n#define HARDFORK_CORE_184_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_199.hf",
    "content": "// bitshares-core #199 Require owner key for change of asset-issuer (new operation)\n#ifndef HARDFORK_CORE_199_TIME\n#define HARDFORK_CORE_199_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_210.hf",
    "content": "// #210 Check authorities on custom_operation\n#ifndef HARDFORK_CORE_210_TIME\n#define HARDFORK_CORE_210_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n// Bugfix: pre-HF 210, custom_operation's required_auths field was ignored.\n#define MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time) (chain_time <= HARDFORK_CORE_210_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2103.hf",
    "content": "// bitshares-core issue #2103 250M BTS supply\n#ifndef HARDFORK_CORE_2103_TIME\n#define HARDFORK_CORE_2103_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#define HARDFORK_CORE_2103_PASSED(now) (now >= HARDFORK_CORE_2103_TIME)\n#define HARDFORK_CORE_2103_BALANCE_ID 56720 // 1.15.56720\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_214.hf",
    "content": "// bitshares-core #214 Proposal cannot contain proposal_update_operation\n#ifndef HARDFORK_CORE_214_TIME\n#define HARDFORK_CORE_214_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_216.hf",
    "content": "// bitshares-core #216 Process to reset a Smartcoin after a Black Swan\n#ifndef HARDFORK_CORE_216_TIME\n#define HARDFORK_CORE_216_TIME (fc::time_point_sec( 1512747600 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2262.hf",
    "content": "// bitshares-core issue #2262: Remove voting power from liquid BTS and tickets\n#ifndef HARDFORK_CORE_2262_TIME\n#define HARDFORK_CORE_2262_TIME (fc::time_point_sec( 1601312520 )) // Monday, September 28, 2020 17:02:00 UTC\n#define HARDFORK_CORE_2262_PASSED(next_maint_time) (next_maint_time > HARDFORK_CORE_2262_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2281.hf",
    "content": "// bitshares-core issue #2281 Add option for MPA owners to disable collateral bidding\n#ifndef HARDFORK_CORE_2281_TIME\n#define HARDFORK_CORE_2281_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2281_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2281_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2282.hf",
    "content": "// bitshares-core issue #2282 Certain bitasset options should still be updatable after GS\n#ifndef HARDFORK_CORE_2282_TIME\n#define HARDFORK_CORE_2282_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2282_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2282_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2290.hf",
    "content": "// bitshares-core issue #2290 Revival of globally-settled assets should depend on ICR but not MCR\n#ifndef HARDFORK_CORE_2290_TIME\n#define HARDFORK_CORE_2290_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2290_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2290_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2295.hf",
    "content": "// bitshares-core issue #2295 Skip asset authorization checks for from_account for override_transfer\n#ifndef HARDFORK_CORE_2295_TIME\n#define HARDFORK_CORE_2295_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2295_PASSED(now) (now >= HARDFORK_CORE_2295_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2350.hf",
    "content": "// bitshares-core issue #2350 Liquidity pool exchange operation to comply with whitelist and blacklist market settings\n#ifndef HARDFORK_CORE_2350_TIME\n#define HARDFORK_CORE_2350_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2350_PASSED(now) (now >= HARDFORK_CORE_2350_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2351.hf",
    "content": "// bitshares-core issue #2351 No-collateral Funding\n#ifndef HARDFORK_CORE_2351_TIME\n#define HARDFORK_CORE_2351_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2351_PASSED(now) (now >= HARDFORK_CORE_2351_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2362.hf",
    "content": "// bitshares-core issue #2362 Simple collateralized P2P funding\n#ifndef HARDFORK_CORE_2362_TIME\n#define HARDFORK_CORE_2362_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2362_PASSED(now) (now >= HARDFORK_CORE_2362_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2467.hf",
    "content": "// bitshares-core issue #2467 Alternative black swan response methods\n#ifndef HARDFORK_CORE_2467_TIME\n#define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2467_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2467_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2481.hf",
    "content": "// bitshares-core issue #2481 Match force-settlements with margin calls at normal margin call fill price\n#ifndef HARDFORK_CORE_2481_TIME\n#define HARDFORK_CORE_2481_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_2481_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2481_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2535.hf",
    "content": "// bitshares-core issue #2535 Simple Order-Sends-Order (OSO)\n#ifndef HARDFORK_CORE_2535_TIME\n#define HARDFORK_CORE_2535_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC\n#define HARDFORK_CORE_2535_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2535_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2582.hf",
    "content": "// bitshares-core issue #2582 price feed issues\n#ifndef HARDFORK_CORE_2582_TIME\n#define HARDFORK_CORE_2582_TIME (fc::time_point_sec( 1656203698 )) // Sunday, June 26, 2022 0:34:58 UTC\n#define HARDFORK_CORE_2582_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2582_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2587.hf",
    "content": "// bitshares-core issue #2587 settle more than total debt in individual settlement fund when no sufficient price feeds\n#ifndef HARDFORK_CORE_2587_TIME\n#define HARDFORK_CORE_2587_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC\n#define HARDFORK_CORE_2587_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2587_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2591.hf",
    "content": "// bitshares-core issue #2591 Tighter peg when collateral price rises and settlement fund or settlement order exists\n#ifndef HARDFORK_CORE_2591_TIME\n#define HARDFORK_CORE_2591_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC\n#define HARDFORK_CORE_2591_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2591_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2595.hf",
    "content": "// bitshares-core issue #2595 Credit deal auto-repayment\n#ifndef HARDFORK_CORE_2595_TIME\n#define HARDFORK_CORE_2595_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC\n#define HARDFORK_CORE_2595_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2595_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_2604.hf",
    "content": "// bitshares-core issue #2604 Allow updating liquidity pool fee rates with certain restrictions\n#ifndef HARDFORK_CORE_2604_TIME\n#define HARDFORK_CORE_2604_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC\n#define HARDFORK_CORE_2604_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2604_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_265.hf",
    "content": "// bitshares-core issue #265 Account_history plugin: notify all related accounts after a new account is created\n#ifndef HARDFORK_CORE_265_TIME\n#define HARDFORK_CORE_265_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_265_PASSED(now) (now >= HARDFORK_CORE_265_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_338.hf",
    "content": "// bitshares-core issue #338 Fix \"margin call order fills at price of matching limit_order\"\n#ifndef HARDFORK_CORE_338_TIME\n#define HARDFORK_CORE_338_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_342.hf",
    "content": "// bitshares-core issue #342\n// Mitigate rounding issue when matching orders\n#ifndef HARDFORK_CORE_342_TIME\n#define HARDFORK_CORE_342_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_343.hf",
    "content": "// bitshares-core issue #343\n// Fix \"Inconsistent sorting of call orders between matching against a limit order and a force settle order\"\n#ifndef HARDFORK_CORE_343_TIME\n#define HARDFORK_CORE_343_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_429.hf",
    "content": "// bitshares-core #429 rounding issue when creating assets\n#ifndef HARDFORK_CORE_429_TIME\n#define HARDFORK_CORE_429_TIME (fc::time_point_sec( 1512747600 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_453.hf",
    "content": "// bitshares-core issue #453 Fix \"Multiple limit order and call order matching issue\"\n#ifndef HARDFORK_CORE_453_TIME\n#define HARDFORK_CORE_453_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_460.hf",
    "content": "// bitshares-core issue #460 Prediction Market price feed should not cause black swan\n#ifndef HARDFORK_CORE_460_TIME\n#define HARDFORK_CORE_460_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_518.hf",
    "content": "// bitshares-core issue #518 Clean up bitasset_data during maintenance\n#ifndef HARDFORK_CORE_518_TIME\n#define HARDFORK_CORE_518_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_583.hf",
    "content": "// bitshares-core issue #583 Always allow updating a call order to higher collateral ratio\n#ifndef HARDFORK_CORE_583_TIME\n#define HARDFORK_CORE_583_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_584.hf",
    "content": "// bitshares-core issue #584 Owner keys of non-immediately required accounts can not authorize a transaction\n// https://github.com/bitshares/bitshares-core/issues/584\n#ifndef HARDFORK_CORE_584_TIME\n#define HARDFORK_CORE_584_TIME (fc::time_point_sec( 1556028120 ) ) // 2019-04-23T14:02:00Z\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_604.hf",
    "content": "// bitshares-core issue #604\n// Implement BSIP 26: refund order creation fee in original paid asset when order is cancelled\n#ifndef HARDFORK_CORE_604_TIME\n#define HARDFORK_CORE_604_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_606.hf",
    "content": "// bitshares-core issue #606 Fix \"Undercollateralized short positions should be called regardless of asks\"\n#ifndef HARDFORK_CORE_606_TIME\n#define HARDFORK_CORE_606_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_625.hf",
    "content": "// bitshares-core issue #625 Fix \"Potential erratic order matching issue involving margin call orders\"\n#ifndef HARDFORK_CORE_625_TIME\n#define HARDFORK_CORE_625_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_834.hf",
    "content": "// bitshares-core issue #834 \"BSIP38: add target CR option to short positions\"\n#ifndef HARDFORK_CORE_834_TIME\n#define HARDFORK_CORE_834_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_868_890.hf",
    "content": "// bitshares-core issue #868 Clear price feed data after updated a bitAsset's backing asset ID\n// bitshares-core issue #890 Update median feeds after feed_lifetime_sec changed\n#ifndef HARDFORK_CORE_868_890_TIME\n#define HARDFORK_CORE_868_890_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_922_931.hf",
    "content": "// bitshares-core issue #922 Missing checks when updating an asset's bitasset_data\n// bitshares-core issue #931 Changing backing asset ID runs some checks against the old value instead of the new\n#ifndef HARDFORK_CORE_922_931_TIME\n#define HARDFORK_CORE_922_931_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_935.hf",
    "content": "// bitshares-core issue #935 Call check_call_orders not only when settlement_price changed\n#ifndef HARDFORK_CORE_935_TIME\n#define HARDFORK_CORE_935_TIME (fc::time_point_sec( 1532008920 )) // Thu, 19 Jul 2018 14:02:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_973.hf",
    "content": "// bitshares-core issue #973 check asset authorizations for operations\n#ifndef HARDFORK_CORE_973_TIME\n#define HARDFORK_CORE_973_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC\n#define HARDFORK_CORE_973_PASSED(now) (now >= HARDFORK_CORE_973_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_BSIP64.hf",
    "content": "// bitshares BSIP 64 HTLC modifications\n#ifndef HARDFORK_CORE_BSIP64_TIME\n#define HARDFORK_CORE_BSIP64_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_BSIP74.hf",
    "content": "// bitshares-core BSIP 74 add margin call fee\n#ifndef HARDFORK_CORE_BSIP74_TIME\n#define HARDFORK_CORE_BSIP74_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_BSIP87.hf",
    "content": "// bitshares-core BSIP 87: add force-settlement fee percentage:\n#ifndef HARDFORK_CORE_BSIP87_TIME\n#define HARDFORK_CORE_BSIP87_TIME (fc::time_point_sec( 1596117300 )) // Thursday, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf",
    "content": "// This hardfork enables the extension to asset_claim_fees_operation to claim collateral-denominated fees.\n// These fees are collected by BSIPs 87 and 74.  This should be set to match the earlier of either\n// HARDFORK_CORE_BSIP87_TIME or HARDFORK_CORE_BSIP74_TIME.\n// This hardfork check should be removable after the hardfork date passes.\n#ifndef HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME\n#define HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME (fc::time_point_sec( 1596117300 )) // Thu, July 30, 2020 13:55:00 UTC\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/LIQUIDITY_POOL.hf",
    "content": "// Liquidity pool\n#ifndef HARDFORK_LIQUIDITY_POOL_TIME\n#define HARDFORK_LIQUIDITY_POOL_TIME (fc::time_point_sec( 1601312520 )) // Monday, September 28, 2020 17:02:00 UTC\n#define HARDFORK_LIQUIDITY_POOL_PASSED(now) (now >= HARDFORK_LIQUIDITY_POOL_TIME)\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/core-143.hf",
    "content": "// #143 Require voted entities to exist\n#ifndef HARDFORK_CORE_143_TIME\n#define HARDFORK_CORE_143_TIME (fc::time_point_sec( 1512747600 ))\n#endif\n"
  },
  {
    "path": "libraries/chain/hardfork.d/core-1468.hf",
    "content": "// HTLC implementation\n#ifndef HARDFORK_CORE_1468_TIME\n#define HARDFORK_CORE_1468_TIME (fc::time_point_sec( 1556028120 ) ) // 2019-04-23T14:02:00Z\n#endif\n"
  },
  {
    "path": "libraries/chain/htlc_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2018 jmjatlanta and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/htlc_evaluator.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\nnamespace graphene {\n   namespace chain {\n      namespace detail\n      {\n         void check_htlc_create_hf_bsip64(const fc::time_point_sec& block_time,\n               const htlc_create_operation& op, const asset_object& asset_to_transfer)\n         {\n            if (block_time < HARDFORK_CORE_BSIP64_TIME)\n            {\n               // memo field added at harfork BSIP64\n               // NOTE: both of these checks can be removed after hardfork time\n               FC_ASSERT( !op.extensions.value.memo.valid(),\n                     \"Memo unavailable until after HARDFORK BSIP64\");\n               // HASH160 added at hardfork BSIP64\n               FC_ASSERT( !op.preimage_hash.is_type<fc::hash160>(),\n                     \"HASH160 unavailable until after HARDFORK BSIP64\" );\n            }\n            else\n            {\n               // this can be moved to the normal non-hf checks after HF_BSIP64\n               //  IF there were no restricted transfers before HF_BSIP64\n               FC_ASSERT( !asset_to_transfer.is_transfer_restricted()\n                     || op.from == asset_to_transfer.issuer || op.to == asset_to_transfer.issuer,\n                     \"Asset ${asset} cannot be transfered.\", (\"asset\", asset_to_transfer.id) );\n            }\n         }\n\n         void check_htlc_redeem_hf_bsip64(const fc::time_point_sec& block_time,\n               const htlc_redeem_operation& op, const htlc_object* htlc_obj)\n         {\n            // TODO: The hardfork portion of this check can be removed if no HTLC redemptions are\n            // attempted on an HTLC with a 0 preimage size before the hardfork date.\n            if ( htlc_obj->conditions.hash_lock.preimage_size > 0U ||\n                  block_time < HARDFORK_CORE_BSIP64_TIME )\n               FC_ASSERT(op.preimage.size() == htlc_obj->conditions.hash_lock.preimage_size,\n                     \"Preimage size mismatch.\");\n         }\n      } // end of graphene::chain::details\n\n      optional<htlc_options> get_committee_htlc_options(graphene::chain::database& db)\n      {\n         return db.get_global_properties().parameters.extensions.value.updatable_htlc_options;\n      }\n\n      void_result htlc_create_evaluator::do_evaluate(const htlc_create_operation& o)\n      {\n         graphene::chain::database& d = db();\n         optional<htlc_options> htlc_options = get_committee_htlc_options(db());\n\n         FC_ASSERT(htlc_options, \"HTLC Committee options are not set.\");\n\n         // make sure the expiration is reasonable\n         FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs,\n               \"HTLC Timeout exceeds allowed length\" );\n         // make sure the preimage length is reasonable\n         FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size,\n               \"HTLC preimage length exceeds allowed length\" );\n         // make sure the sender has the funds for the HTLC\n         FC_ASSERT( d.get_balance( o.from, o.amount.asset_id) >= (o.amount), \"Insufficient funds\") ;\n         const auto& asset_to_transfer = o.amount.asset_id( d );\n         const auto& from_account = o.from( d );\n         const auto& to_account = o.to( d );\n         detail::check_htlc_create_hf_bsip64(d.head_block_time(), o, asset_to_transfer);\n         FC_ASSERT( is_authorized_asset( d, from_account, asset_to_transfer ),\n               \"Asset ${asset} is not authorized for account ${acct}.\",\n               ( \"asset\", asset_to_transfer.id )( \"acct\", from_account.id ) );\n         FC_ASSERT( is_authorized_asset( d, to_account, asset_to_transfer ),\n               \"Asset ${asset} is not authorized for account ${acct}.\",\n               ( \"asset\", asset_to_transfer.id )( \"acct\", to_account.id ) );\n         return void_result();\n      }\n\n      object_id_type htlc_create_evaluator::do_apply(const htlc_create_operation& o)\n      {\n         try {\n            graphene::chain::database& dbase = db();\n            dbase.adjust_balance( o.from, -o.amount );\n\n            const htlc_object& esc = db().create<htlc_object>([&dbase,&o]( htlc_object& esc ) {\n               esc.transfer.from                  = o.from;\n               esc.transfer.to                    = o.to;\n               esc.transfer.amount                = o.amount.amount;\n               esc.transfer.asset_id              = o.amount.asset_id;\n               esc.conditions.hash_lock.preimage_hash = o.preimage_hash;\n               esc.conditions.hash_lock.preimage_size = o.preimage_size;\n               if ( o.extensions.value.memo.valid() )\n                  esc.memo = o.extensions.value.memo;\n               esc.conditions.time_lock.expiration    = dbase.head_block_time() + o.claim_period_seconds;\n            });\n            return  esc.id;\n\n         } FC_CAPTURE_AND_RETHROW( (o) )\n      }\n\n      class htlc_redeem_visitor\n      {\n      //private:\n         const std::vector<char>& data;\n      public:\n         typedef bool result_type;\n\n         htlc_redeem_visitor( const std::vector<char>& preimage )\n            : data( preimage ) {}\n\n         template<typename T>\n         bool operator()( const T& preimage_hash )const\n         {\n            return T::hash( (const char*)data.data(), (uint32_t) data.size() ) == preimage_hash;\n         }\n      };\n\n      void_result htlc_redeem_evaluator::do_evaluate(const htlc_redeem_operation& o)\n      {\n         auto& d = db();\n         htlc_obj = &d.get(o.htlc_id);\n         detail::check_htlc_redeem_hf_bsip64(d.head_block_time(), o, htlc_obj);\n\n         const htlc_redeem_visitor vtor( o.preimage );\n         FC_ASSERT( htlc_obj->conditions.hash_lock.preimage_hash.visit( vtor ),\n               \"Provided preimage does not generate correct hash.\");\n\n         return void_result();\n      }\n\n      void_result htlc_redeem_evaluator::do_apply(const htlc_redeem_operation& o)\n      {\n         const auto amount = asset(htlc_obj->transfer.amount, htlc_obj->transfer.asset_id);\n         db().adjust_balance(htlc_obj->transfer.to, amount);\n         // notify related parties\n         htlc_redeemed_operation virt_op( htlc_obj->get_id(), htlc_obj->transfer.from, htlc_obj->transfer.to,\n               o.redeemer,\n               amount, htlc_obj->conditions.hash_lock.preimage_hash, htlc_obj->conditions.hash_lock.preimage_size,\n               o.preimage );\n         db().push_applied_operation( virt_op );\n         db().remove(*htlc_obj);\n         return void_result();\n      }\n\n      void_result htlc_extend_evaluator::do_evaluate(const htlc_extend_operation& o)\n      {\n         htlc_obj = &db().get(o.htlc_id);\n         FC_ASSERT(o.update_issuer == htlc_obj->transfer.from, \"HTLC may only be extended by its creator.\");\n         optional<htlc_options> htlc_options = get_committee_htlc_options(db());\n         FC_ASSERT( htlc_obj->conditions.time_lock.expiration.sec_since_epoch()\n               + static_cast<uint64_t>(o.seconds_to_add) < fc::time_point_sec::maximum().sec_since_epoch(),\n               \"Extension would cause an invalid date\");\n         FC_ASSERT( htlc_obj->conditions.time_lock.expiration + o.seconds_to_add\n                <=  db().head_block_time() + htlc_options->max_timeout_secs,\n                \"Extension pushes contract too far into the future\" );\n         return void_result();\n      }\n\n      void_result htlc_extend_evaluator::do_apply(const htlc_extend_operation& o)\n      {\n         db().modify(*htlc_obj, [&o](htlc_object& db_obj) {\n            db_obj.conditions.time_lock.expiration += o.seconds_to_add;\n         });\n\n         return void_result();\n      }\n\n   } // namespace chain\n} // namespace graphene\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/account_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/account_object.hpp>\n\nnamespace graphene { namespace chain {\n\nclass account_create_evaluator : public evaluator<account_create_evaluator>\n{\npublic:\n   typedef account_create_operation operation_type;\n\n   void_result do_evaluate( const account_create_operation& o );\n   object_id_type do_apply( const account_create_operation& o ) ;\n};\n\nclass account_update_evaluator : public evaluator<account_update_evaluator>\n{\npublic:\n   typedef account_update_operation operation_type;\n\n   void_result do_evaluate( const account_update_operation& o );\n   void_result do_apply( const account_update_operation& o );\n\n   const account_object* acnt;\n};\n\nclass account_upgrade_evaluator : public evaluator<account_upgrade_evaluator>\n{\npublic:\n   typedef account_upgrade_operation operation_type;\n\n   void_result do_evaluate(const operation_type& o);\n   void_result do_apply(const operation_type& o);\n\n   const account_object* account;\n};\n\nclass account_whitelist_evaluator : public evaluator<account_whitelist_evaluator>\n{\npublic:\n   typedef account_whitelist_operation operation_type;\n\n   void_result do_evaluate( const account_whitelist_operation& o);\n   void_result do_apply( const account_whitelist_operation& o);\n\n   const account_object* listed_account;\n};\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/account_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n#include <graphene/protocol/account.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n   class database;\n   class account_object;\n   class vesting_balance_object;\n\n   /**\n    * @class account_statistics_object\n    * @ingroup object\n    * @ingroup implementation\n    *\n    * This object contains regularly updated statistical data about an account. It is provided for the purpose of\n    * separating the account data that changes frequently from the account data that is mostly static, which will\n    * minimize the amount of data that must be backed up as part of the undo history everytime a transfer is made.\n    */\n   class account_statistics_object : public graphene::db::abstract_object<account_statistics_object,\n                                               implementation_ids, impl_account_statistics_object_type>\n   {\n      public:\n         account_id_type  owner;\n\n         string           name; ///< redundantly store account name here for better maintenance performance\n\n         /**\n          * Keep the most recent operation as a root pointer to a linked list of the transaction history.\n          */\n         account_history_id_type most_recent_op;\n         /** Total operations related to this account. */\n         uint64_t                            total_ops = 0;\n         /** Total operations related to this account that has been removed from the database. */\n         uint64_t                            removed_ops = 0;\n\n         /**\n          * When calculating votes it is necessary to know how much is stored in orders (and thus unavailable for\n          * transfers). Rather than maintaining an index of [asset,owner,order_id] we will simply maintain the running\n          * total here and update it every time an order is created or modified.\n          */\n         share_type total_core_in_orders;\n\n         /// Total amount of core token in inactive lock_forever tickets\n         share_type total_core_inactive;\n\n         /// Total amount of core token in active lock_forever tickets\n         share_type total_core_pob;\n\n         /// Total amount of core token in other tickets\n         share_type total_core_pol;\n\n         /// Total value of tickets whose current type is lock_forever\n         share_type total_pob_value;\n\n         /// Total value of tickets whose current type is not lock_forever\n         share_type total_pol_value;\n\n         /// Redundantly store core balance in this object for better maintenance performance.\n         /// Only updates on maintenance.\n         share_type core_in_balance;\n\n         bool has_cashback_vb = false; ///< redundantly store this for better maintenance performance\n\n         bool is_voting = false; ///< redundately store \"if this account is voting\" for better maintenance performance\n\n         time_point_sec last_vote_time; ///< last time voted\n\n         /// Voting Power Stats\n         ///@{\n         uint64_t vp_all = 0;           ///<  all voting power.\n         uint64_t vp_active = 0;        ///<  active voting power, if there is no attenuation, it is equal to vp_all.\n         uint64_t vp_committee = 0;     ///<  the final voting power for the committees.\n         uint64_t vp_witness = 0;       ///<  the final voting power for the witnesses.\n         uint64_t vp_worker = 0;        ///<  the final voting power for the workers.\n         /// Timestamp of the last count of votes.\n         /// If there is no statistics,\n         /// the date is less than `_db.get_dynamic_global_properties().last_vote_tally_time`.\n         time_point_sec vote_tally_time;\n         ///@}\n\n         /// Whether this account owns some CORE asset and is voting\n         inline bool has_some_core_voting() const\n         {\n            return is_voting && ( total_core_in_orders > 0 || core_in_balance > 0 || has_cashback_vb\n                                  || total_core_pol > 0 );\n         }\n\n         /**\n          * Tracks the total fees paid by this account for the purpose of calculating bulk discounts.\n          */\n         share_type lifetime_fees_paid;\n\n         /**\n          * Tracks the fees paid by this account which have not been disseminated to the various parties that receive\n          * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid\n          * doing massive amounts of uint128 arithmetic on each and every operation.\n          *\n          * These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance\n          * interval.\n          */\n         share_type pending_fees;\n         /**\n          * Same as @ref pending_fees, except these fees will be paid out as pre-vested cash-back (immediately\n          * available for withdrawal) rather than requiring the normal vesting period.\n          */\n         share_type pending_vested_fees;\n\n         /// Whether this account has pending fees, no matter vested or not\n         inline bool has_pending_fees() const { return pending_fees > 0 || pending_vested_fees > 0; }\n\n         /// Whether need to process this account during the maintenance interval\n         inline bool need_maintenance() const { return has_some_core_voting() || has_pending_fees(); }\n\n         /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees\n         void process_fees(const account_object& a, database& d) const;\n\n         /**\n          * Core fees are paid into the account_statistics_object by this method\n          */\n         void pay_fee( share_type core_fee, share_type cashback_vesting_threshold );\n   };\n\n   /**\n    * @brief Tracks the balance of a single account/asset pair\n    * @ingroup object\n    *\n    * This object is indexed on owner and asset_type so that black swan\n    * events in asset_type can be processed quickly.\n    */\n   class account_balance_object : public abstract_object<account_balance_object,\n                                            implementation_ids, impl_account_balance_object_type>\n   {\n      public:\n         account_id_type   owner;\n         asset_id_type     asset_type;\n         share_type        balance;\n         /// Whether need to process this balance object in maintenance interval\n         bool              maintenance_flag = false;\n\n         asset get_balance()const { return asset(balance, asset_type); }\n         void  adjust_balance(const asset& delta);\n   };\n\n\n   /**\n    * @brief This class represents an account on the object graph\n    * @ingroup object\n    * @ingroup protocol\n    *\n    * Accounts are the primary unit of authority on the graphene system. Users must have an account in order to use\n    * assets, trade in the markets, vote for committee_members, etc.\n    */\n   class account_object : public graphene::db::abstract_object<account_object, protocol_ids, account_object_type>\n   {\n      public:\n         /**\n          * The time at which this account's membership expires.\n          * If set to any time in the past, the account is a basic account.\n          * If set to time_point_sec::maximum(), the account is a lifetime member.\n          * If set to any time not in the past less than time_point_sec::maximum(), the account is an annual member.\n          *\n          * See @ref is_lifetime_member, @ref is_basic_account, @ref is_annual_member, and @ref is_member\n          */\n         time_point_sec membership_expiration_date;\n\n         ///The account that paid the fee to register this account. Receives a percentage of referral rewards.\n         account_id_type registrar;\n         /// The account credited as referring this account. Receives a percentage of referral rewards.\n         account_id_type referrer;\n         /// The lifetime member at the top of the referral tree. Receives a percentage of referral rewards.\n         account_id_type lifetime_referrer;\n\n         /// Percentage of fee which should go to network.\n         uint16_t network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;\n         /// Percentage of fee which should go to lifetime referrer.\n         uint16_t lifetime_referrer_fee_percentage = 0;\n         /// Percentage of referral rewards (leftover fee after paying network and lifetime referrer) which should go\n         /// to referrer. The remainder of referral rewards goes to the registrar.\n         uint16_t referrer_rewards_percentage = 0;\n\n         /// The account's name. This name must be unique among all account names on the graph. May not be empty.\n         string name;\n\n         /**\n          * The owner authority represents absolute control over the account. Usually the keys in this authority will\n          * be kept in cold storage, as they should not be needed very often and compromise of these keys constitutes\n          * complete and irrevocable loss of the account. Generally the only time the owner authority is required is to\n          * update the active authority.\n          */\n         authority owner;\n         /// The owner authority contains the hot keys of the account. This authority has control over nearly all\n         /// operations the account may perform.\n         authority active;\n\n         account_options options;\n\n         /// Pre-calculated for better performance on chain maintenance\n         uint16_t num_committee_voted;\n\n         /// The reference implementation records the account's statistics in a separate object. This field contains the\n         /// ID of that object.\n         account_statistics_id_type statistics;\n\n         /**\n          * This is a set of all accounts which have 'whitelisted' this account. Whitelisting is only used in core\n          * validation for the purpose of authorizing accounts to hold and transact in whitelisted assets. This\n          * account cannot update this set, except by transferring ownership of the account, which will clear it. Other\n          * accounts may add or remove their IDs from this set.\n          */\n         flat_set<account_id_type> whitelisting_accounts;\n\n         /**\n          * Optionally track all of the accounts this account has whitelisted or blacklisted, these should\n          * be made Immutable so that when the account object is cloned no deep copy is required.  This state is\n          * tracked for GUI display purposes.\n          *\n          * TODO: move white list tracking to its own multi-index container rather than having 4 fields on an\n          * account.   This will scale better because under the current design if you whitelist 2000 accounts,\n          * then every time someone fetches this account object they will get the full list of 2000 accounts.\n          */\n         ///@{\n         set<account_id_type> whitelisted_accounts;\n         set<account_id_type> blacklisted_accounts;\n         ///@}\n\n\n         /**\n          * This is a set of all accounts which have 'blacklisted' this account. Blacklisting is only used in core\n          * validation for the purpose of forbidding accounts from holding and transacting in whitelisted assets. This\n          * account cannot update this set, and it will be preserved even if the account is transferred. Other accounts\n          * may add or remove their IDs from this set.\n          */\n         flat_set<account_id_type> blacklisting_accounts;\n\n         /**\n          * Vesting balance which receives cashback_reward deposits.\n          */\n         optional<vesting_balance_id_type> cashback_vb;\n\n         special_authority owner_special_authority = no_special_authority();\n         special_authority active_special_authority = no_special_authority();\n\n         /**\n          * This flag is set when the top_n logic sets both authorities,\n          * and gets reset when authority or special_authority is set.\n          */\n         uint8_t top_n_control_flags = 0;\n         static const uint8_t top_n_control_owner  = 1;\n         static const uint8_t top_n_control_active = 2;\n\n         /**\n          * This is a set of assets which the account is allowed to have.\n          * This is utilized to restrict buyback accounts to the assets that trade in their markets.\n          * In the future we may expand this to allow accounts to e.g. voluntarily restrict incoming transfers.\n          */\n         optional< flat_set<asset_id_type> > allowed_assets;\n\n         /// The block number when the account was created\n         uint32_t       creation_block_num = 0;\n         /// The time when the account was created\n         time_point_sec creation_time;\n\n         bool has_special_authority()const\n         {\n            return (!owner_special_authority.is_type< no_special_authority >())\n                || (!active_special_authority.is_type< no_special_authority >());\n         }\n\n         template<typename DB>\n         const vesting_balance_object& cashback_balance(const DB& db)const\n         {\n            FC_ASSERT(cashback_vb);\n            return db.get(*cashback_vb);\n         }\n\n         /// @return true if this is a lifetime member account; false otherwise.\n         bool is_lifetime_member()const\n         {\n            return membership_expiration_date == time_point_sec::maximum();\n         }\n         /// @return true if this is a basic account; false otherwise.\n         bool is_basic_account(time_point_sec now)const\n         {\n            return now > membership_expiration_date;\n         }\n         /// @return true if the account is an unexpired annual member; false otherwise.\n         /// @note This method will return false for lifetime members.\n         bool is_annual_member(time_point_sec now)const\n         {\n            return !is_lifetime_member() && !is_basic_account(now);\n         }\n         /// @return true if the account is an annual or lifetime member; false otherwise.\n         bool is_member(time_point_sec now)const\n         {\n            return !is_basic_account(now);\n         }\n   };\n\n   /**\n    *  @brief This secondary index will allow a reverse lookup of all accounts that a particular key or account\n    *  is an potential signing authority.\n    */\n   class account_member_index : public secondary_index\n   {\n      public:\n         virtual void object_inserted( const object& obj ) override;\n         virtual void object_removed( const object& obj ) override;\n         virtual void about_to_modify( const object& before ) override;\n         virtual void object_modified( const object& after  ) override;\n\n\n         /** given an account or key, map it to the set of accounts that reference it in an active or owner authority */\n         map< account_id_type, set<account_id_type> >                    account_to_account_memberships;\n         map< public_key_type, set<account_id_type>, pubkey_comparator > account_to_key_memberships;\n         /** some accounts use address authorities in the genesis block */\n         map< address, set<account_id_type> >                            account_to_address_memberships;\n\n\n      protected:\n         set<account_id_type>                    get_account_members( const account_object& a )const;\n         set<public_key_type, pubkey_comparator> get_key_members( const account_object& a )const;\n         set<address>                            get_address_members( const account_object& a )const;\n\n         set<account_id_type>                    before_account_members;\n         set<public_key_type, pubkey_comparator> before_key_members;\n         set<address>                            before_address_members;\n   };\n\n\n   /**\n    *  @brief This secondary index will allow fast access to the balance objects\n    *         that belonging to an account.\n    */\n   class balances_by_account_index : public secondary_index\n   {\n      public:\n         virtual void object_inserted( const object& obj ) override;\n         virtual void object_removed( const object& obj ) override;\n         virtual void about_to_modify( const object& before ) override;\n         virtual void object_modified( const object& after  ) override;\n\n         const map< asset_id_type, const account_balance_object* >& get_account_balances(\n                  const account_id_type& acct )const;\n         const account_balance_object* get_account_balance( const account_id_type& acct,\n                                                            const asset_id_type& asset )const;\n\n      private:\n         static const uint8_t  bits;\n         static const uint64_t mask;\n\n         /** Maps each account to its balance objects */\n         vector< vector< map< asset_id_type, const account_balance_object* > > > balances;\n         std::stack< object_id_type > ids_being_modified;\n   };\n\n   struct by_asset_balance;\n   struct by_maintenance_flag;\n   /**\n    * @ingroup object_index\n    */\n   typedef multi_index_container<\n      account_balance_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_non_unique< tag<by_maintenance_flag>,\n                             member< account_balance_object, bool, &account_balance_object::maintenance_flag > >,\n         ordered_unique< tag<by_asset_balance>,\n            composite_key<\n               account_balance_object,\n               member<account_balance_object, asset_id_type, &account_balance_object::asset_type>,\n               member<account_balance_object, share_type, &account_balance_object::balance>,\n               member<account_balance_object, account_id_type, &account_balance_object::owner>\n            >,\n            composite_key_compare<\n               std::less< asset_id_type >,\n               std::greater< share_type >,\n               std::less< account_id_type >\n            >\n         >\n      >\n   > account_balance_object_multi_index_type;\n\n   /**\n    * @ingroup object_index\n    */\n   typedef generic_index<account_balance_object, account_balance_object_multi_index_type> account_balance_index;\n\n   struct by_name;\n\n   /**\n    * @ingroup object_index\n    */\n   typedef multi_index_container<\n      account_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_unique< tag<by_name>, member<account_object, string, &account_object::name> >\n      >\n   > account_multi_index_type;\n\n   /**\n    * @ingroup object_index\n    */\n   typedef generic_index<account_object, account_multi_index_type> account_index;\n\n   struct by_maintenance_seq;\n   struct by_voting_power_active;\n\n   /**\n    * @ingroup object_index\n    */\n   typedef multi_index_container<\n      account_statistics_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_unique< tag<by_maintenance_seq>,\n            composite_key<\n               account_statistics_object,\n               const_mem_fun<account_statistics_object, bool, &account_statistics_object::need_maintenance>,\n               member<account_statistics_object, string, &account_statistics_object::name>\n            >\n         >,\n         ordered_non_unique< tag<by_voting_power_active>,\n            composite_key<\n               account_statistics_object,\n               member<account_statistics_object, time_point_sec, &account_statistics_object::vote_tally_time>,\n               member<account_statistics_object, uint64_t, &account_statistics_object::vp_active>\n            >,\n            composite_key_compare<\n               std::greater< time_point_sec >,\n               std::greater< uint64_t >\n            >\n         >\n      >\n   > account_stats_multi_index_type;\n\n   /**\n    * @ingroup object_index\n    */\n   typedef generic_index<account_statistics_object, account_stats_multi_index_type> account_stats_index;\n\n}}\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::account_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::account_balance_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::account_statistics_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::account_object )\nFC_REFLECT_TYPENAME( graphene::chain::account_balance_object )\nFC_REFLECT_TYPENAME( graphene::chain::account_statistics_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::account_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::account_balance_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::account_statistics_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/assert_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace chain {\n\n   class assert_evaluator : public evaluator<assert_evaluator>\n   {\n      public:\n         typedef assert_operation operation_type;\n\n         void_result do_evaluate( const assert_operation& o );\n         void_result do_apply( const assert_operation& o );\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/asset_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n#include <locale>\n\nnamespace graphene { namespace chain {\n\n   class asset_create_evaluator : public evaluator<asset_create_evaluator>\n   {\n      public:\n         using operation_type = asset_create_operation;\n\n         void_result do_evaluate( const asset_create_operation& o ) const;\n         object_id_type do_apply( const asset_create_operation& o ) const;\n\n         /** override the default behavior defined by generic_evalautor which is to\n          * post the fee to fee_paying_account_stats.pending_fees\n          */\n         void pay_fee() override;\n      private:\n         bool fee_is_odd;\n   };\n\n   class asset_issue_evaluator : public evaluator<asset_issue_evaluator>\n   {\n      public:\n         using operation_type = asset_issue_operation;\n         void_result do_evaluate( const asset_issue_operation& o );\n         void_result do_apply( const asset_issue_operation& o ) const;\n\n      private:\n         const asset_dynamic_data_object* asset_dyn_data = nullptr;\n         const account_object*            to_account = nullptr;\n   };\n\n   class asset_reserve_evaluator : public evaluator<asset_reserve_evaluator>\n   {\n      public:\n         using operation_type = asset_reserve_operation;\n         void_result do_evaluate( const asset_reserve_operation& o );\n         void_result do_apply( const asset_reserve_operation& o ) const;\n\n      private:\n         const asset_dynamic_data_object* asset_dyn_data = nullptr;\n         const account_object*            from_account = nullptr;\n   };\n\n\n   class asset_update_evaluator : public evaluator<asset_update_evaluator>\n   {\n      public:\n         using operation_type = asset_update_operation;\n\n         void_result do_evaluate( const asset_update_operation& o );\n         void_result do_apply( const asset_update_operation& o );\n\n      private:\n         const asset_object* asset_to_update = nullptr;\n         const asset_bitasset_data_object* bitasset_data = nullptr;\n   };\n\n   class asset_update_issuer_evaluator : public evaluator<asset_update_issuer_evaluator>\n   {\n      public:\n         using operation_type = asset_update_issuer_operation;\n\n         void_result do_evaluate( const asset_update_issuer_operation& o );\n         void_result do_apply( const asset_update_issuer_operation& o );\n\n      private:\n         const asset_object* asset_to_update = nullptr;\n   };\n\n   class asset_update_bitasset_evaluator : public evaluator<asset_update_bitasset_evaluator>\n   {\n      public:\n         using operation_type = asset_update_bitasset_operation;\n\n         void_result do_evaluate( const asset_update_bitasset_operation& o );\n         void_result do_apply( const asset_update_bitasset_operation& o );\n\n      private:\n         const asset_bitasset_data_object* bitasset_to_update = nullptr;\n         const asset_object* asset_to_update = nullptr;\n\n         bool update_feeds_due_to_bsrm_change = false;\n   };\n\n   class asset_update_feed_producers_evaluator : public evaluator<asset_update_feed_producers_evaluator>\n   {\n      public:\n         using operation_type = asset_update_feed_producers_operation;\n\n         void_result do_evaluate( const operation_type& o );\n         void_result do_apply( const operation_type& o ) const;\n\n      private:\n         const asset_object* asset_to_update = nullptr;\n   };\n\n   class asset_fund_fee_pool_evaluator : public evaluator<asset_fund_fee_pool_evaluator>\n   {\n      public:\n         using operation_type = asset_fund_fee_pool_operation;\n\n         void_result do_evaluate(const asset_fund_fee_pool_operation& op);\n         void_result do_apply(const asset_fund_fee_pool_operation& op) const;\n\n      private:\n         const asset_dynamic_data_object* asset_dyn_data = nullptr;\n   };\n\n   class asset_global_settle_evaluator : public evaluator<asset_global_settle_evaluator>\n   {\n      public:\n         using operation_type = asset_global_settle_operation;\n\n         void_result do_evaluate(const operation_type& op);\n         void_result do_apply(const operation_type& op);\n\n      private:\n         const asset_object* asset_to_settle = nullptr;\n   };\n   class asset_settle_evaluator : public evaluator<asset_settle_evaluator>\n   {\n      public:\n         using operation_type = asset_settle_operation;\n\n         void_result do_evaluate(const operation_type& op);\n         operation_result do_apply(const operation_type& op);\n\n      private:\n         const asset_object* asset_to_settle = nullptr;\n         const asset_bitasset_data_object* bitasset_ptr = nullptr;\n   };\n\n   class asset_publish_feeds_evaluator : public evaluator<asset_publish_feeds_evaluator>\n   {\n      public:\n         using operation_type = asset_publish_feed_operation;\n\n         void_result do_evaluate( const asset_publish_feed_operation& o );\n         void_result do_apply( const asset_publish_feed_operation& o );\n\n      private:\n         const asset_object* asset_ptr = nullptr;\n         const asset_bitasset_data_object* bitasset_ptr = nullptr;\n   };\n\n   class asset_claim_fees_evaluator : public evaluator<asset_claim_fees_evaluator>\n   {\n      public:\n         using operation_type = asset_claim_fees_operation;\n\n         void_result do_evaluate( const asset_claim_fees_operation& o );\n         void_result do_apply( const asset_claim_fees_operation& o );\n\n      private:\n         const asset_object* container_asset = nullptr;\n         const asset_dynamic_data_object* container_ddo = nullptr;\n   };\n\n   class asset_claim_pool_evaluator : public evaluator<asset_claim_pool_evaluator>\n   {\n      public:\n         using operation_type = asset_claim_pool_operation;\n\n         void_result do_evaluate( const asset_claim_pool_operation& o );\n         void_result do_apply( const asset_claim_pool_operation& o );\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/asset_object.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n#include <graphene/protocol/asset_ops.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\n/**\n * @defgroup prediction_market Prediction Market\n *\n * A prediction market is a specialized BitAsset such that total debt and total collateral are always equal amounts\n * (although asset IDs differ). No margin calls or force settlements may be performed on a prediction market asset. A\n * prediction market is globally settled by the issuer after the event being predicted resolves, thus a prediction\n * market must always have the @c global_settle permission enabled. The maximum price for global settlement or short\n * sale of a prediction market asset is 1-to-1.\n */\n\nnamespace graphene { namespace chain {\n   class asset_bitasset_data_object;\n\n   /**\n    *  @brief tracks the asset information that changes frequently\n    *  @ingroup object\n    *  @ingroup implementation\n    *\n    *  Because the asset_object is very large it doesn't make sense to save an undo state\n    *  for all of the parameters that never change.   This object factors out the parameters\n    *  of an asset that change in almost every transaction that involves the asset.\n    *\n    *  This object exists as an implementation detail and its ID should never be referenced by\n    *  a blockchain operation.\n    */\n   class asset_dynamic_data_object : public abstract_object<asset_dynamic_data_object,\n                                               implementation_ids, impl_asset_dynamic_data_object_type>\n   {\n      public:\n         /// The number of shares currently in existence\n         share_type current_supply;\n         share_type confidential_supply; ///< total asset held in confidential balances\n         share_type accumulated_fees; ///< fees accumulate to be paid out over time\n         share_type accumulated_collateral_fees; ///< accumulated collateral-denominated fees (for bitassets)\n         share_type fee_pool;         ///< in core asset\n   };\n\n   /**\n    *  @brief tracks the parameters of an asset\n    *  @ingroup object\n    *\n    *  All assets have a globally unique symbol name that controls how they are traded and an issuer who\n    *  has authority over the parameters of the asset.\n    */\n   class asset_object : public graphene::db::abstract_object<asset_object, protocol_ids, asset_object_type>\n   {\n      public:\n         /// This function does not check if any registered asset has this symbol or not; it simply checks whether the\n         /// symbol would be valid.\n         /// @return true if symbol is a valid ticker symbol; false otherwise.\n         static bool is_valid_symbol( const string& symbol );\n\n         /// @return true if this is a market-issued asset; false otherwise.\n         bool is_market_issued()const { return bitasset_data_id.valid(); }\n         /// @return true if this is a share asset of a liquidity pool; false otherwise.\n         bool is_liquidity_pool_share_asset()const { return for_liquidity_pool.valid(); }\n         /// @return true if users may request force-settlement of this market-issued asset; false otherwise\n         bool can_force_settle()const { return (0 == (options.flags & disable_force_settle)); }\n         /// @return true if the issuer of this market-issued asset may globally settle the asset; false otherwise\n         bool can_global_settle()const { return (0 != (options.issuer_permissions & global_settle)); }\n         /// @return true if this asset charges a fee for the issuer on market operations; false otherwise\n         bool charges_market_fees()const { return (0 != (options.flags & charge_market_fee)); }\n         /// @return true if this asset may only be transferred to/from the issuer or market orders\n         bool is_transfer_restricted()const { return (0 != (options.flags & transfer_restricted)); }\n         bool can_override()const { return (0 != (options.flags & override_authority)); }\n         bool allow_confidential()const\n         { return (0 == (options.flags & asset_issuer_permission_flags::disable_confidential)); }\n         /// @return true if max supply of the asset can be updated\n         bool can_update_max_supply()const { return (0 == (options.flags & lock_max_supply)); }\n         /// @return true if can create new supply for the asset\n         bool can_create_new_supply()const { return (0 == (options.flags & disable_new_supply)); }\n         /// @return true if the asset owner can update MCR directly\n         bool can_owner_update_mcr()const { return (0 == (options.issuer_permissions & disable_mcr_update)); }\n         /// @return true if the asset owner can update ICR directly\n         bool can_owner_update_icr()const { return (0 == (options.issuer_permissions & disable_icr_update)); }\n         /// @return true if the asset owner can update MSSR directly\n         bool can_owner_update_mssr()const { return (0 == (options.issuer_permissions & disable_mssr_update)); }\n         /// @return true if the asset owner can change black swan response method\n         bool can_owner_update_bsrm()const { return (0 == (options.issuer_permissions & disable_bsrm_update)); }\n         /// @return true if can bid collateral for the asset\n         bool can_bid_collateral()const { return (0 == (options.flags & disable_collateral_bidding)); }\n\n         /// Helper function to get an asset object with the given amount in this asset's type\n         asset amount(share_type a)const { return asset(a, asset_id_type(id)); }\n         /// Convert a string amount (i.e. \"123.45\") to an asset object with this asset's type\n         /// The string may have a decimal and/or a negative sign.\n         asset amount_from_string(string amount_string)const;\n         /// Convert an asset to a textual representation, i.e. \"123.45\"\n         string amount_to_string(share_type amount)const;\n         /// Convert an asset to a textual representation, i.e. \"123.45\"\n         string amount_to_string(const asset& amount)const\n         { FC_ASSERT(amount.asset_id == get_id()); return amount_to_string(amount.amount); }\n         /// Convert an asset to a textual representation with symbol, i.e. \"123.45 USD\"\n         string amount_to_pretty_string(share_type amount)const\n         { return amount_to_string(amount) + \" \" + symbol; }\n         /// Convert an asset to a textual representation with symbol, i.e. \"123.45 USD\"\n         string amount_to_pretty_string(const asset &amount)const\n         { FC_ASSERT(amount.asset_id == get_id()); return amount_to_pretty_string(amount.amount); }\n\n         /// Ticker symbol for this asset, i.e. \"USD\"\n         string symbol;\n         /// Maximum number of digits after the decimal point (must be <= 12)\n         uint8_t precision = 0;\n         /// ID of the account which issued this asset.\n         account_id_type issuer;\n\n         asset_options options;\n\n         /// Current supply, fee pool, and collected fees are stored in a separate object as they change frequently.\n         asset_dynamic_data_id_type  dynamic_asset_data_id;\n         /// Extra data associated with BitAssets. This field is non-null if and only if is_market_issued() returns true\n         optional<asset_bitasset_data_id_type> bitasset_data_id;\n\n         optional<account_id_type> buyback_account;\n\n         /// The ID of the liquidity pool if the asset is the share asset of a liquidity pool\n         optional<liquidity_pool_id_type> for_liquidity_pool;\n\n         /// The block number when the asset object was created\n         uint32_t       creation_block_num = 0;\n         /// The time when the asset object was created\n         time_point_sec creation_time;\n\n         void validate()const\n         {\n            // UIAs may not be prediction markets, have force settlement, or global settlements\n            if( !is_market_issued() )\n            {\n               FC_ASSERT(0 == (options.flags & disable_force_settle) && 0 == (options.flags & global_settle));\n               FC_ASSERT(0 == (options.issuer_permissions & disable_force_settle)\n                         && 0 == (options.issuer_permissions & global_settle));\n            }\n         }\n\n         template<class DB>\n         const asset_bitasset_data_object& bitasset_data(const DB& db)const\n         {\n            FC_ASSERT( bitasset_data_id.valid(),\n                       \"Asset ${a} (${id}) is not a market issued asset.\",\n                       (\"a\",this->symbol)(\"id\",this->id) );\n            return db.get( *bitasset_data_id );\n         }\n\n         template<class DB>\n         const asset_dynamic_data_object& dynamic_data(const DB& db)const\n         { return db.get(dynamic_asset_data_id); }\n\n         /**\n          *  The total amount of an asset that is reserved for future issuance.\n          */\n         template<class DB>\n         share_type reserved( const DB& db )const\n         { return options.max_supply - dynamic_data(db).current_supply; }\n\n         /// @return true if asset can accumulate fees in the given denomination\n         template<class DB>\n         bool can_accumulate_fee(const DB& db, const asset& fee) const {\n            return (( fee.asset_id == get_id() ) ||\n                    ( is_market_issued() && fee.asset_id == bitasset_data(db).options.short_backing_asset ));\n         }\n\n         /***\n          * @brief receive a fee asset to accrue in dynamic_data object\n          *\n          * Asset owners define various fees (market fees, force-settle fees, etc.) to be\n          * collected for the asset owners. These fees are typically denominated in the asset\n          * itself, but for bitassets some of the fees are denominated in the collateral\n          * asset. This will place the fee in the right container.\n          */\n         template<class DB>\n         void accumulate_fee(DB& db, const asset& fee) const\n         {\n            if (fee.amount == 0) return;\n            FC_ASSERT( fee.amount >= 0, \"Fee amount must be non-negative.\" );\n            const auto& dyn_data = dynamic_asset_data_id(db);\n            if (fee.asset_id == get_id()) { // fee same as asset\n               db.modify( dyn_data, [&fee]( asset_dynamic_data_object& obj ){\n                  obj.accumulated_fees += fee.amount;\n               });\n            } else { // fee different asset; perhaps collateral-denominated fee\n               FC_ASSERT( is_market_issued(),\n                          \"Asset ${a} (${id}) cannot accept fee of asset (${fid}).\",\n                          (\"a\",this->symbol)(\"id\",this->id)(\"fid\",fee.asset_id) );\n               const auto & bad = bitasset_data(db);\n               FC_ASSERT( fee.asset_id == bad.options.short_backing_asset,\n                          \"Asset ${a} (${id}) cannot accept fee of asset (${fid}).\",\n                          (\"a\",this->symbol)(\"id\",this->id)(\"fid\",fee.asset_id) );\n               db.modify( dyn_data, [&fee]( asset_dynamic_data_object& obj ){\n                  obj.accumulated_collateral_fees += fee.amount;\n               });\n            }\n         }\n\n   };\n\n   /**\n    *  @brief defines market parameters for margin positions, extended with an initial_collateral_ratio field\n    */\n   struct price_feed_with_icr : public price_feed\n   {\n      /// After BSIP77, when creating a new debt position or updating an existing position,\n      /// the position will be checked against this parameter.\n      /// Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM\n      uint16_t initial_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;\n\n      price_feed_with_icr()\n      : price_feed(), initial_collateral_ratio( maintenance_collateral_ratio )\n      {}\n\n      price_feed_with_icr( const price_feed& pf, const optional<uint16_t>& icr = {} )\n      : price_feed( pf ), initial_collateral_ratio( icr.valid() ? *icr : pf.maintenance_collateral_ratio )\n      {}\n\n      /// The result will be used to check new debt positions and position updates.\n      /// Calculation: ~settlement_price * initial_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM\n      price get_initial_collateralization()const;\n   };\n\n   /**\n    *  @brief contains properties that only apply to bitassets (market issued assets)\n    *\n    *  @ingroup object\n    *  @ingroup implementation\n    */\n   class asset_bitasset_data_object : public abstract_object<asset_bitasset_data_object,\n                                                implementation_ids, impl_asset_bitasset_data_object_type>\n   {\n      public:\n         /// The asset this object belong to\n         asset_id_type asset_id;\n\n         /// The tunable options for BitAssets are stored in this field.\n         bitasset_options options;\n\n         /// Feeds published for this asset.\n         /// The keys in this map are the feed publishing accounts.\n         /// The timestamp on each feed is the time it was published.\n         flat_map<account_id_type, pair<time_point_sec,price_feed_with_icr>> feeds;\n         /// This is the median of values from the currently active feeds.\n         price_feed_with_icr median_feed;\n         /// This is the currently active price feed, calculated from @ref median_feed and other parameters.\n         price_feed_with_icr current_feed;\n         /// This is the publication time of the oldest feed which was factored into current_feed.\n         time_point_sec current_feed_publication_time;\n\n         /// @return whether @ref current_feed is different from @ref median_feed\n         bool is_current_feed_price_capped()const\n         { return ( median_feed.settlement_price != current_feed.settlement_price ); }\n\n         /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin\n         /// call territory.\n         /// This value is derived from @ref current_feed for better performance and should be kept consistent.\n         price current_maintenance_collateralization;\n         /// After BSIP77, when creating a new debt position or updating an existing position, the position\n         /// will be checked against the `initial_collateral_ratio` (ICR) parameter in the bitasset options.\n         /// This value is derived from @ref current_feed (which includes `ICR`) for better performance and\n         /// should be kept consistent.\n         price current_initial_collateralization;\n\n         /// True if this asset implements a @ref prediction_market\n         bool is_prediction_market = false;\n\n         /// This is the volume of this asset which has been force-settled this maintanence interval\n         share_type force_settled_volume;\n         /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply\n         share_type max_force_settlement_volume(share_type current_supply)const;\n\n         /// @return true if the bitasset has been globally settled, false otherwise\n         bool is_globally_settled()const { return !settlement_price.is_null(); }\n\n         /**\n          *  In the event of global settlement, all margin positions\n          *  are settled with the siezed collateral being moved into the settlement fund. From this\n          *  point on forced settlement occurs immediately when requested, using the settlement price and fund.\n          *\n          *  @note After the core-2591 hardfork, forced settlements may be paid at the margin call order price (MCOP)\n          *        when applicable, but are not limited to the settlement price stated here.\n          */\n         ///@{\n         /// Price at which force settlements of a globally settled asset will occur\n         price settlement_price;\n         /// Amount of collateral which is available for force settlement due to global settlement\n         share_type settlement_fund;\n         ///@}\n\n         /// The individual settlement pool.\n         /// In the event of individual settlements (to fund or to order), debt and collateral of the margin positions\n         /// which got settled are moved here.\n         /// * For individual settlement to fund, collateral assets in the pool can only be retrieved through\n         ///     forced settlements.\n         /// * For individual settlement to order, collateral assets in the pool can only be retrieved through\n         ///     limit orders.\n         ///@{\n         /// Amount of debt due to individual settlements\n         share_type individual_settlement_debt;\n         /// Amount of collateral due to individual settlements\n         share_type individual_settlement_fund;\n         ///@}\n\n         /// @return true if the individual settlement pool is not empty and the bitasset's black swan response method\n         ///              (BSRM) is @ref\n         ///            graphene::protocol::bitasset_options::black_swan_response_type::individual_settlement_to_fund,\n         ///         false otherwise\n         bool is_individually_settled_to_fund()const\n         {\n            using bsrm_type = bitasset_options::black_swan_response_type;\n            return ( ( individual_settlement_debt != 0 ) &&\n                     ( bsrm_type::individual_settlement_to_fund == get_black_swan_response_method() ) );\n         }\n\n         /// Get the price of the individual settlement pool\n         price get_individual_settlement_price() const\n         {\n            return asset( individual_settlement_debt, asset_id )\n                   / asset( individual_settlement_fund, options.short_backing_asset );\n         }\n\n         /// Get the effective black swan response method of this bitasset\n         bitasset_options::black_swan_response_type get_black_swan_response_method() const\n         {\n            return options.get_black_swan_response_method();\n         }\n\n         /// Get margin call order price (MCOP) of this bitasset\n         price get_margin_call_order_price() const\n         {\n            return current_feed.margin_call_order_price( options.extensions.value.margin_call_fee_ratio );\n         }\n\n         /// Get margin call order ratio (MCOR) of this bitasset\n         ratio_type get_margin_call_order_ratio() const\n         {\n            return current_feed.margin_call_order_ratio( options.extensions.value.margin_call_fee_ratio );\n         }\n\n         /// Get margin call pays ratio (MCPR) of this bitasset\n         ratio_type get_margin_call_pays_ratio() const\n         {\n            return current_feed.margin_call_pays_ratio( options.extensions.value.margin_call_fee_ratio );\n         }\n\n         /// Track whether core_exchange_rate in corresponding @ref asset_object has updated\n         bool asset_cer_updated = false;\n\n         /// Track whether core exchange rate in current feed has updated\n         bool feed_cer_updated = false;\n\n         /// Whether need to update core_exchange_rate in @ref asset_object\n         bool need_to_update_cer() const\n         {\n            return ( ( feed_cer_updated || asset_cer_updated ) && !current_feed.core_exchange_rate.is_null() );\n         }\n\n         /// The time when @ref current_feed would expire\n         time_point_sec feed_expiration_time()const\n         {\n            uint32_t current_feed_seconds = current_feed_publication_time.sec_since_epoch();\n            if( (std::numeric_limits<uint32_t>::max() - current_feed_seconds) <= options.feed_lifetime_sec )\n               return time_point_sec::maximum();\n            else\n               return current_feed_publication_time + options.feed_lifetime_sec;\n         }\n         /// The old and buggy implementation of @ref feed_is_expired before the No. 615 hardfork.\n         /// See https://github.com/cryptonomex/graphene/issues/615\n         bool feed_is_expired_before_hf_615(time_point_sec current_time)const\n         { return feed_expiration_time() >= current_time; }\n         /// @return whether @ref current_feed has expired\n         bool feed_is_expired(time_point_sec current_time)const\n         { return feed_expiration_time() <= current_time; }\n\n         /******\n          * @brief calculate the median feed\n          *\n          * This calculates the median feed from @ref feeds, feed_lifetime_sec\n          * in @ref options, and the given parameters.\n          * It may update the @ref median_feed, @ref current_feed_publication_time,\n          * @ref current_initial_collateralization and\n          * @ref current_maintenance_collateralization member variables.\n          *\n          * @param current_time the current time to use in the calculations\n          * @param next_maintenance_time the next chain maintenance time\n          *\n          * @note Called by @ref database::update_bitasset_current_feed() which updates @ref current_feed afterwards.\n          */\n         void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time);\n      private:\n         /// Derive @ref current_maintenance_collateralization and @ref current_initial_collateralization from\n         /// other member variables.\n         void refresh_cache();\n   };\n\n   /// Key extractor for short backing asset\n   struct bitasset_backing_asst_extractor\n   {\n      using result_type = asset_id_type;\n      result_type operator() (const asset_bitasset_data_object& obj) const\n      {\n         return obj.options.short_backing_asset;\n      }\n   };\n\n   struct by_short_backing_asset;\n   struct by_feed_expiration;\n   struct by_cer_update;\n\n   using bitasset_data_multi_index_type = multi_index_container<\n      asset_bitasset_data_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_non_unique< tag<by_short_backing_asset>, bitasset_backing_asst_extractor >,\n         ordered_unique< tag<by_feed_expiration>,\n            composite_key< asset_bitasset_data_object,\n               const_mem_fun< asset_bitasset_data_object, time_point_sec,\n                              &asset_bitasset_data_object::feed_expiration_time >,\n               member< asset_bitasset_data_object, asset_id_type, &asset_bitasset_data_object::asset_id >\n            >\n         >,\n         ordered_non_unique< tag<by_cer_update>,\n                             const_mem_fun< asset_bitasset_data_object, bool,\n                                            &asset_bitasset_data_object::need_to_update_cer >\n         >\n      >\n   >;\n   using asset_bitasset_data_index = generic_index< asset_bitasset_data_object, bitasset_data_multi_index_type >;\n\n   struct by_symbol;\n   struct by_type;\n   struct by_issuer;\n   using asset_object_multi_index_type = multi_index_container<\n      asset_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_unique< tag<by_symbol>, member<asset_object, string, &asset_object::symbol> >,\n         ordered_unique< tag<by_type>,\n            composite_key< asset_object,\n                const_mem_fun<asset_object, bool, &asset_object::is_market_issued>,\n                member< object, object_id_type, &object::id >\n            >\n         >,\n         ordered_unique< tag<by_issuer>,\n            composite_key< asset_object,\n                member< asset_object, account_id_type, &asset_object::issuer >,\n                member< object, object_id_type, &object::id >\n            >\n         >\n      >\n   >;\n   using asset_index = generic_index< asset_object, asset_object_multi_index_type >;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_dynamic_data_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_bitasset_data_object)\n\nFC_REFLECT_DERIVED( graphene::chain::price_feed_with_icr, (graphene::protocol::price_feed),\n                    (initial_collateral_ratio) )\n\n// Note: this is left here but not moved to a cpp file due to the extended_asset_object struct in API.\nFC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),\n                    (symbol)\n                    (precision)\n                    (issuer)\n                    (options)\n                    (dynamic_asset_data_id)\n                    (bitasset_data_id)\n                    (buyback_account)\n                    (for_liquidity_pool)\n                    (creation_block_num)\n                    (creation_time)\n                  )\n\nFC_REFLECT_TYPENAME( graphene::chain::asset_bitasset_data_object )\nFC_REFLECT_TYPENAME( graphene::chain::asset_dynamic_data_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::price_feed_with_icr )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::asset_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::asset_bitasset_data_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::asset_dynamic_data_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/balance_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/transaction.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/exceptions.hpp>\n\nnamespace graphene { namespace chain {\n\nclass balance_claim_evaluator : public evaluator<balance_claim_evaluator>\n{\npublic:\n   typedef balance_claim_operation operation_type;\n\n   const balance_object* balance = nullptr;\n\n   void_result do_evaluate(const balance_claim_operation& op);\n\n   /**\n    * @note the fee is always 0 for this particular operation because once the\n    * balance is claimed it frees up memory and it cannot be used to spam the network\n    */\n   void_result do_apply(const balance_claim_operation& op);\n};\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/balance_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/vesting_balance_object.hpp>\n\nnamespace graphene { namespace chain {\n\n   class balance_object : public abstract_object<balance_object, protocol_ids, balance_object_type>\n   {\n      public:\n         bool is_vesting_balance()const\n         { return vesting_policy.valid(); }\n         asset available(fc::time_point_sec now)const\n         {\n            return is_vesting_balance()? vesting_policy->get_allowed_withdraw({balance, now, {}})\n                                       : balance;\n         }\n\n         address owner;\n         asset   balance;\n         optional<linear_vesting_policy> vesting_policy;\n         time_point_sec last_claim_date;\n         asset_id_type asset_type()const { return balance.asset_id; }\n   };\n\n   struct by_owner;\n\n   /**\n    * @ingroup object_index\n    */\n   using balance_multi_index_type = multi_index_container<\n      balance_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_non_unique< tag<by_owner>, composite_key<\n            balance_object,\n            member<balance_object, address, &balance_object::owner>,\n            const_mem_fun<balance_object, asset_id_type, &balance_object::asset_type>\n         > >\n      >\n   >;\n\n   /**\n    * @ingroup object_index\n    */\n   using balance_index = generic_index<balance_object, balance_multi_index_type>;\n} }\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::balance_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::balance_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::balance_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/block_database.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <fstream>\n#include <graphene/protocol/block.hpp>\n\n#include <fc/filesystem.hpp>\n\nnamespace graphene { namespace chain {\n   struct index_entry;\n   using namespace graphene::protocol;\n\n   class block_database \n   {\n      public:\n         void open( const fc::path& dbdir );\n         bool is_open()const;\n         void flush();\n         void close();\n\n         void store( const block_id_type& id, const signed_block& b );\n         void remove( const block_id_type& id );\n\n         bool                   contains( const block_id_type& id )const;\n         block_id_type          fetch_block_id( uint32_t block_num )const;\n         optional<signed_block> fetch_optional( const block_id_type& id )const;\n         optional<signed_block> fetch_by_number( uint32_t block_num )const;\n         optional<signed_block> last()const;\n         optional<block_id_type> last_id()const;\n         size_t                 blocks_current_position()const;\n         size_t                 total_block_size()const;\n      private:\n         optional<index_entry> last_index_entry()const;\n         fc::path _index_filename;\n         mutable std::fstream _blocks;\n         mutable std::fstream _block_num_to_pos;\n   };\n} }\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/block_summary_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/types.hpp>\n#include <graphene/db/object.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace graphene::db;\n\n   /**\n    *  @brief tracks minimal information about past blocks to implement TaPOS\n    *  @ingroup object\n    *\n    *  When attempting to calculate the validity of a transaction we need to\n    *  lookup a past block and check its block hash and the time it occurred\n    *  so we can calculate whether the current transaction is valid and at\n    *  what time it should expire.\n    */\n   class block_summary_object : public abstract_object<block_summary_object,\n                                          implementation_ids, impl_block_summary_object_type>\n   {\n      public:\n         block_id_type      block_id;\n   };\n\n} }\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::block_summary_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::block_summary_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::block_summary_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/budget_record_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\nnamespace graphene { namespace chain {\n\nstruct budget_record\n{\n   uint64_t time_since_last_budget = 0;\n\n   /// Sources of budget\n   ///@{\n   share_type from_initial_reserve = 0;\n   share_type from_accumulated_fees = 0;\n   share_type from_unused_witness_budget = 0;\n   ///@}\n\n   /// Witness budget requested by the committee\n   share_type requested_witness_budget = 0;\n\n   /// Funds that can be released from reserve at maximum rate\n   share_type total_budget = 0;\n\n   /// Sinks of budget, should sum up to total_budget\n   ///@{\n   share_type witness_budget = 0;\n   share_type worker_budget = 0;\n   ///@}\n\n   /// Unused budget\n   share_type leftover_worker_funds = 0;\n\n   /// Change in supply due to budget operations\n   share_type supply_delta = 0;\n\n   /// Maximum supply\n   share_type max_supply;\n\n   /// Current supply\n   share_type current_supply;\n};\n\nclass budget_record_object : public graphene::db::abstract_object<budget_record_object,\n                                       implementation_ids, impl_budget_record_object_type>\n{\n   public:\n      fc::time_point_sec time;\n      budget_record record;\n};\n\n} }\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::budget_record_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::budget_record )\nFC_REFLECT_TYPENAME( graphene::chain::budget_record_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::budget_record )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::budget_record_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/buyback.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/buyback.hpp>\n\nnamespace graphene { namespace chain {\n\nclass database;\n\nvoid evaluate_buyback_account_options( const database& db, const buyback_account_options& auth );\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/buyback_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n * buyback_authority_object only exists to help with a specific indexing problem.\n * We want to be able to iterate over all assets that have a buyback program.\n * However, assets which have a buyback program are very rare.  So rather\n * than indexing asset_object by the buyback field (requiring additional\n * bookkeeping for every asset), we instead maintain a buyback_object\n * pointing to each asset which has buyback (requiring additional\n * bookkeeping only for every asset which has buyback).\n *\n * This class is an implementation detail.\n */\n\nclass buyback_object : public graphene::db::abstract_object< buyback_object,\n                                 implementation_ids, impl_buyback_object_type >\n{\n   public:\n      asset_id_type asset_to_buy;\n};\n\nstruct by_asset;\n\ntypedef multi_index_container<\n   buyback_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_asset>, member< buyback_object, asset_id_type, &buyback_object::asset_to_buy> >\n   >\n> buyback_multi_index_type;\n\ntypedef generic_index< buyback_object, buyback_multi_index_type > buyback_index;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::buyback_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::buyback_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::buyback_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/chain_property_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/immutable_chain_parameters.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n * Contains invariants which are set at genesis and never changed.\n */\nclass chain_property_object : public abstract_object<chain_property_object,\n                                        implementation_ids, impl_chain_property_object_type>\n{\n   public:\n      chain_id_type chain_id;\n      immutable_chain_parameters immutable_parameters;\n};\n\n} }\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::chain_property_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::chain_property_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::chain_property_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/committee_member_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n\nnamespace graphene { namespace chain {\n\n   class committee_member_create_evaluator : public evaluator<committee_member_create_evaluator>\n   {\n      public:\n         typedef committee_member_create_operation operation_type;\n\n         void_result do_evaluate( const committee_member_create_operation& o );\n         object_id_type do_apply( const committee_member_create_operation& o );\n   };\n\n   class committee_member_update_evaluator : public evaluator<committee_member_update_evaluator>\n   {\n      public:\n         typedef committee_member_update_operation operation_type;\n\n         void_result do_evaluate( const committee_member_update_operation& o );\n         void_result do_apply( const committee_member_update_operation& o );\n   };\n\n   class committee_member_update_global_parameters_evaluator : public evaluator<committee_member_update_global_parameters_evaluator>\n   {\n      public:\n         typedef committee_member_update_global_parameters_operation operation_type;\n\n         void_result do_evaluate( const committee_member_update_global_parameters_operation& o );\n         void_result do_apply( const committee_member_update_global_parameters_operation& o );\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/committee_member_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n#include <graphene/protocol/vote.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace graphene::db;\n\n   /**\n    *  @brief tracks information about a committee_member account.\n    *  @ingroup object\n    *\n    *  A committee_member is responsible for setting blockchain parameters and has\n    *  dynamic multi-sig control over the committee account.  The current set of\n    *  active committee_members has control.\n    *\n    *  committee_members were separated into a separate object to make iterating over\n    *  the set of committee_member easy.\n    */\n   class committee_member_object : public abstract_object<committee_member_object,\n                                             protocol_ids, committee_member_object_type>\n   {\n      public:\n         account_id_type  committee_member_account;\n         vote_id_type     vote_id;\n         uint64_t         total_votes = 0;\n         string           url;\n   };\n\n   struct by_account;\n   struct by_vote_id;\n   using committee_member_multi_index_type = multi_index_container<\n      committee_member_object,\n      indexed_by<\n         ordered_unique< tag<by_id>,\n            member<object, object_id_type, &object::id>\n         >,\n         ordered_unique< tag<by_account>,\n            member<committee_member_object, account_id_type, &committee_member_object::committee_member_account>\n         >,\n         ordered_unique< tag<by_vote_id>,\n            member<committee_member_object, vote_id_type, &committee_member_object::vote_id>\n         >\n      >\n   >;\n   using committee_member_index = generic_index<committee_member_object, committee_member_multi_index_type>;\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::committee_member_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::committee_member_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::committee_member_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/confidential_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/types.hpp>\n\nnamespace graphene { namespace chain {\n\nclass transfer_to_blind_evaluator : public evaluator<transfer_to_blind_evaluator>\n{\n   public:\n      typedef transfer_to_blind_operation operation_type;\n\n      void_result do_evaluate( const transfer_to_blind_operation& o );\n      void_result do_apply( const transfer_to_blind_operation& o ) ;\n\n      virtual void pay_fee() override;\n};\n\nclass transfer_from_blind_evaluator : public evaluator<transfer_from_blind_evaluator>\n{\n   public:\n      typedef transfer_from_blind_operation operation_type;\n\n      void_result do_evaluate( const transfer_from_blind_operation& o );\n      void_result do_apply( const transfer_from_blind_operation& o ) ;\n\n      virtual void pay_fee() override;\n};\n\nclass blind_transfer_evaluator : public evaluator<blind_transfer_evaluator>\n{\n   public:\n      typedef blind_transfer_operation operation_type;\n\n      void_result do_evaluate( const blind_transfer_operation& o );\n      void_result do_apply( const blind_transfer_operation& o ) ;\n\n      virtual void pay_fee() override;\n};\n\n} } // namespace graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/confidential_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/authority.hpp>\n#include <graphene/protocol/types.hpp>\n\n#include <graphene/db/generic_index.hpp>\n\n#include <fc/crypto/elliptic.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n * @class blinded_balance_object\n * @brief tracks a blinded balance commitment\n * @ingroup object\n * @ingroup protocol\n */\nclass blinded_balance_object : public graphene::db::abstract_object<blinded_balance_object,\n                                         implementation_ids, impl_blinded_balance_object_type>\n{\n   public:\n      fc::ecc::commitment_type                commitment;\n      asset_id_type                           asset_id;\n      authority                               owner;\n};\n\nstruct by_commitment;\n\n/**\n * @ingroup object_index\n */\nusing blinded_balance_obj_multi_idx = multi_index_container<\n   blinded_balance_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_commitment>,\n         member<blinded_balance_object, commitment_type, &blinded_balance_object::commitment> >\n   >\n>;\nusing blinded_balance_index = generic_index<blinded_balance_object, blinded_balance_obj_multi_idx>;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::blinded_balance_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::blinded_balance_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::blinded_balance_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/config.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/config.hpp>\n\n#include <string>\n\n#define GRAPHENE_MIN_UNDO_HISTORY 10\n#define GRAPHENE_MAX_UNDO_HISTORY 10000\n\n#define GRAPHENE_MAX_NESTED_OBJECTS (200)\n\nconst std::string GRAPHENE_CURRENT_DB_VERSION = \"20230906\";\n\n#define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT             4\n#define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT             3\n\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n\n#include <graphene/protocol/credit_offer.hpp>\n\nnamespace graphene { namespace chain {\n\n   class credit_offer_object;\n\n   class credit_offer_create_evaluator : public evaluator<credit_offer_create_evaluator>\n   {\n      public:\n         using operation_type = credit_offer_create_operation;\n\n         void_result do_evaluate( const credit_offer_create_operation& op ) const;\n         object_id_type do_apply( const credit_offer_create_operation& op ) const;\n   };\n\n   class credit_offer_delete_evaluator : public evaluator<credit_offer_delete_evaluator>\n   {\n      public:\n         using operation_type = credit_offer_delete_operation;\n\n         void_result do_evaluate( const credit_offer_delete_operation& op );\n         asset do_apply( const credit_offer_delete_operation& op ) const;\n\n         const credit_offer_object* _offer = nullptr;\n   };\n\n   class credit_offer_update_evaluator : public evaluator<credit_offer_update_evaluator>\n   {\n      public:\n         using operation_type = credit_offer_update_operation;\n\n         void_result do_evaluate( const credit_offer_update_operation& op );\n         void_result do_apply( const credit_offer_update_operation& op ) const;\n\n         const credit_offer_object* _offer = nullptr;\n   };\n\n   class credit_offer_accept_evaluator : public evaluator<credit_offer_accept_evaluator>\n   {\n      public:\n         using operation_type = credit_offer_accept_operation;\n\n         void_result do_evaluate( const credit_offer_accept_operation& op );\n         extendable_operation_result do_apply( const credit_offer_accept_operation& op ) const;\n\n         const credit_offer_object* _offer = nullptr;\n         const credit_deal_summary_object* _deal_summary = nullptr;\n   };\n\n   class credit_deal_repay_evaluator : public evaluator<credit_deal_repay_evaluator>\n   {\n      public:\n         using operation_type = credit_deal_repay_operation;\n\n         void_result do_evaluate( const credit_deal_repay_operation& op );\n         extendable_operation_result do_apply( const credit_deal_repay_operation& op ) const;\n\n         const credit_deal_object* _deal = nullptr;\n   };\n\n   class credit_deal_update_evaluator : public evaluator<credit_deal_update_evaluator>\n   {\n      public:\n         using operation_type = credit_deal_update_operation;\n\n         void_result do_evaluate( const credit_deal_update_operation& op );\n         void_result do_apply( const credit_deal_update_operation& op ) const;\n\n         const credit_deal_object* _deal = nullptr;\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/credit_offer_object.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n *  @brief A credit offer is a fund that can be used by other accounts who provide certain collateral.\n *  @ingroup object\n *  @ingroup protocol\n *\n */\nclass credit_offer_object : public abstract_object<credit_offer_object, protocol_ids, credit_offer_object_type>\n{\n   public:\n      account_id_type owner_account;            ///< Owner of the fund\n      asset_id_type   asset_type;               ///< Asset type in the fund\n      share_type      total_balance;            ///< Total size of the fund\n      share_type      current_balance;          ///< Usable amount in the fund\n      uint32_t        fee_rate = 0;             ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM\n      uint32_t        max_duration_seconds = 0; ///< The time limit that borrowed funds should be repaid\n      share_type      min_deal_amount;          ///< Minimum amount to borrow for each new deal\n      bool            enabled = false;          ///< Whether this offer is available\n      time_point_sec  auto_disable_time;        ///< The time when this offer will be disabled automatically\n\n      /// Types and rates of acceptable collateral\n      flat_map<asset_id_type, price>          acceptable_collateral;\n\n      /// Allowed borrowers and their maximum amounts to borrow. No limitation if empty.\n      flat_map<account_id_type, share_type>   acceptable_borrowers;\n};\n\nstruct by_auto_disable_time; // for protocol\nstruct by_owner;             // for API\nstruct by_asset_type;        // for API\n\n/**\n* @ingroup object_index\n*/\nusing credit_offer_multi_index_type = multi_index_container<\n   credit_offer_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_auto_disable_time>,\n         composite_key< credit_offer_object,\n            member< credit_offer_object, bool, &credit_offer_object::enabled >,\n            member< credit_offer_object, time_point_sec, &credit_offer_object::auto_disable_time >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_owner>,\n         composite_key< credit_offer_object,\n            member< credit_offer_object, account_id_type, &credit_offer_object::owner_account >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_asset_type>,\n         composite_key< credit_offer_object,\n            member< credit_offer_object, asset_id_type, &credit_offer_object::asset_type >,\n            member< object, object_id_type, &object::id>\n         >\n      >\n   >\n>;\n\n/**\n* @ingroup object_index\n*/\nusing credit_offer_index = generic_index<credit_offer_object, credit_offer_multi_index_type>;\n\n\n/**\n *  @brief A credit deal describes the details of a borrower's borrowing of funds from a credit offer.\n *  @ingroup object\n *  @ingroup protocol\n *\n */\nclass credit_deal_object : public abstract_object<credit_deal_object, protocol_ids, credit_deal_object_type>\n{\n   public:\n      account_id_type      borrower;           ///< Borrower\n      credit_offer_id_type offer_id;           ///< ID of the credit offer\n      account_id_type      offer_owner;        ///< Owner of the credit offer, redundant info for ease of querying\n      asset_id_type        debt_asset;         ///< Asset type of the debt, redundant info for ease of querying\n      share_type           debt_amount;        ///< How much funds borrowed\n      asset_id_type        collateral_asset;   ///< Asset type of the collateral\n      share_type           collateral_amount;  ///< How much funds in collateral\n      uint32_t             fee_rate = 0;       ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM\n      time_point_sec       latest_repay_time;  ///< The deadline when the debt should be repaid\n      uint8_t              auto_repay;         ///< The specified automatic repayment type\n};\n\nstruct by_latest_repay_time; // for protocol\nstruct by_offer_id;          // for API\nstruct by_offer_owner;       // for API\nstruct by_borrower;          // for API\nstruct by_debt_asset;        // for API\nstruct by_collateral_asset;  // for API\n\n/**\n* @ingroup object_index\n*/\nusing credit_deal_multi_index_type = multi_index_container<\n   credit_deal_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_latest_repay_time>,\n         composite_key< credit_deal_object,\n            member< credit_deal_object, time_point_sec, &credit_deal_object::latest_repay_time >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_offer_id>,\n         composite_key< credit_deal_object,\n            member< credit_deal_object, credit_offer_id_type, &credit_deal_object::offer_id >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_offer_owner>,\n         composite_key< credit_deal_object,\n            member< credit_deal_object, account_id_type, &credit_deal_object::offer_owner >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_borrower>,\n         composite_key< credit_deal_object,\n            member< credit_deal_object, account_id_type, &credit_deal_object::borrower >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_debt_asset>,\n         composite_key< credit_deal_object,\n            member< credit_deal_object, asset_id_type, &credit_deal_object::debt_asset >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_collateral_asset>,\n         composite_key< credit_deal_object,\n            member< credit_deal_object, asset_id_type, &credit_deal_object::collateral_asset >,\n            member< object, object_id_type, &object::id>\n         >\n      >\n   >\n>;\n\n/**\n* @ingroup object_index\n*/\nusing credit_deal_index = generic_index<credit_deal_object, credit_deal_multi_index_type>;\n\n\n/**\n *  @brief A credit deal summary describes the summary of a borrower's borrowing of funds from a credit offer.\n *  @ingroup object\n *  @ingroup implementation\n *\n */\nclass credit_deal_summary_object : public abstract_object<credit_deal_summary_object,\n                                             implementation_ids, impl_credit_deal_summary_object_type>\n{\n   public:\n      account_id_type      borrower;           ///< Borrower\n      credit_offer_id_type offer_id;           ///< ID of the credit offer\n      account_id_type      offer_owner;        ///< Owner of the credit offer, redundant info for ease of querying\n      asset_id_type        debt_asset;         ///< Asset type of the debt, redundant info for ease of querying\n      share_type           total_debt_amount;  ///< How much funds borrowed\n};\n\nstruct by_offer_borrower;    // for protocol\n\n/**\n* @ingroup object_index\n*/\nusing credit_deal_summary_index_type = multi_index_container<\n   credit_deal_summary_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_offer_borrower>,\n         composite_key< credit_deal_summary_object,\n            member< credit_deal_summary_object, credit_offer_id_type, &credit_deal_summary_object::offer_id >,\n            member< credit_deal_summary_object, account_id_type, &credit_deal_summary_object::borrower >\n         >\n      >\n   >\n>;\n\n/**\n* @ingroup object_index\n*/\nusing credit_deal_summary_index = generic_index<credit_deal_summary_object, credit_deal_summary_index_type>;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE( graphene::chain::credit_offer_object )\nMAP_OBJECT_ID_TO_TYPE( graphene::chain::credit_deal_object )\nMAP_OBJECT_ID_TO_TYPE( graphene::chain::credit_deal_summary_object )\n\nFC_REFLECT_TYPENAME( graphene::chain::credit_offer_object )\nFC_REFLECT_TYPENAME( graphene::chain::credit_deal_object )\nFC_REFLECT_TYPENAME( graphene::chain::credit_deal_summary_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::credit_offer_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_summary_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include <graphene/chain/evaluator.hpp>\n\nnamespace graphene { namespace chain {\nclass custom_authority_object;\n\nclass custom_authority_create_evaluator : public evaluator<custom_authority_create_evaluator> {\npublic:\n   using operation_type = custom_authority_create_operation;\n\n   void_result do_evaluate(const operation_type& op);\n   object_id_type do_apply(const operation_type& op);\n};\n\nclass custom_authority_update_evaluator : public evaluator<custom_authority_update_evaluator> {\npublic:\n   using operation_type = custom_authority_update_operation;\n   const custom_authority_object* old_object = nullptr;\n\n   void_result do_evaluate(const operation_type& op);\n   void_result do_apply(const operation_type& op);\n};\n\nclass custom_authority_delete_evaluator : public evaluator<custom_authority_delete_evaluator> {\npublic:\n   using operation_type = custom_authority_delete_operation;\n   const custom_authority_object* old_object = nullptr;\n\n   void_result do_evaluate(const operation_type& op);\n   void_result do_apply(const operation_type& op);\n};\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/custom_authority_object.hpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/authority.hpp>\n#include <graphene/protocol/custom_authority.hpp>\n#include <graphene/protocol/restriction_predicate.hpp>\n#include <graphene/db/generic_index.hpp>\n#include <graphene/chain/types.hpp>\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\n   /**\n    * @brief Tracks account custom authorities\n    * @ingroup object\n    *\n    */\n   class custom_authority_object : public abstract_object<custom_authority_object,\n                                             protocol_ids, custom_authority_object_type>\n   {\n      /// Unreflected field to store a cache of the predicate function\n      /// Note that this cache can be modified when the object is const!\n      mutable optional<restriction_predicate_function> predicate_cache;\n\n   public:\n      account_id_type account;\n      bool enabled;\n      time_point_sec valid_from;\n      time_point_sec valid_to;\n      unsigned_int operation_type;\n      authority auth;\n      flat_map<uint16_t, restriction> restrictions;\n      uint16_t restriction_counter = 0;\n\n      /// Check whether the custom authority is valid\n      bool is_valid(time_point_sec now) const { return enabled && now >= valid_from && now < valid_to; }\n\n      /// Get the restrictions as a vector rather than a map\n      vector<restriction> get_restrictions() const {\n         vector<restriction> rs;\n         std::transform(restrictions.begin(), restrictions.end(),\n                        std::back_inserter(rs), [](auto i) { return i.second; });\n         return rs;\n      }\n      /// Get predicate, from cache if possible, and update cache if not (modifies const object!)\n      restriction_predicate_function get_predicate() const {\n         if (!predicate_cache.valid())\n            update_predicate_cache();\n\n         return *predicate_cache;\n      }\n      /// Regenerate predicate function and update predicate cache\n      void update_predicate_cache() const {\n         predicate_cache = get_restriction_predicate(get_restrictions(), operation_type);\n      }\n      /// Clear the cache of the predicate function\n      void clear_predicate_cache() { predicate_cache.reset(); }\n   };\n\n   struct by_account_custom;\n   struct by_expiration;\n\n   /**\n    * @ingroup object_index\n    */\n   typedef multi_index_container<\n      custom_authority_object,\n      indexed_by<\n         ordered_unique<tag<by_id>, member<object, object_id_type, &object::id>>,\n         ordered_unique<tag<by_account_custom>,\n            composite_key<custom_authority_object,\n               member<custom_authority_object, account_id_type, &custom_authority_object::account>,\n               member<custom_authority_object, unsigned_int, &custom_authority_object::operation_type>,\n               member<custom_authority_object, bool, &custom_authority_object::enabled>,\n               member<object, object_id_type, &object::id>\n            >>,\n         ordered_unique<tag<by_expiration>,\n            composite_key<custom_authority_object,\n               member<custom_authority_object, time_point_sec, &custom_authority_object::valid_to>,\n               member<object, object_id_type, &object::id>\n            >\n         >\n      >\n   > custom_authority_multi_index_type;\n\n   /**\n    * @ingroup object_index\n    */\n   using custom_authority_index = generic_index<custom_authority_object, custom_authority_multi_index_type>;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::custom_authority_object)\n\nFC_REFLECT_TYPENAME(graphene::chain::custom_authority_object)\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION(graphene::chain::custom_authority_object)\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/custom_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace chain {\n\n   class custom_evaluator : public evaluator<custom_evaluator>\n   {\n      public:\n         typedef custom_operation operation_type;\n\n         void_result do_evaluate( const custom_operation& o ){ return void_result(); }\n         void_result do_apply( const custom_operation& o ){ return void_result(); }\n   };\n} }\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/database.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/node_property_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/fork_database.hpp>\n#include <graphene/chain/block_database.hpp>\n#include <graphene/chain/genesis_state.hpp>\n#include <graphene/chain/evaluator.hpp>\n\n#include <graphene/db/object_database.hpp>\n#include <graphene/db/object.hpp>\n#include <graphene/db/simple_index.hpp>\n#include <fc/signals.hpp>\n\n#include <fc/log/logger.hpp>\n\n#include <map>\n\nnamespace graphene { namespace protocol { struct predicate_result; } }\n\nnamespace graphene { namespace chain {\n   using graphene::db::abstract_object;\n   using graphene::db::object;\n   class op_evaluator;\n   class transaction_evaluation_state;\n   class proposal_object;\n   class operation_history_object;\n   class chain_property_object;\n   class witness_schedule_object;\n   class witness_object;\n   class force_settlement_object;\n   class limit_order_object;\n   class collateral_bid_object;\n   class call_order_object;\n\n   struct budget_record;\n   enum class vesting_balance_type;\n\n   /**\n    *   @class database\n    *   @brief tracks the blockchain state in an extensible manner\n    */\n   class database : public db::object_database\n   {\n         //////////////////// db_management.cpp ////////////////////\n      public:\n         database();\n         ~database() override;\n\n         enum validation_steps\n         {\n            skip_nothing                = 0,\n            skip_witness_signature      = 1 << 0,  ///< used while reindexing\n            skip_transaction_signatures = 1 << 1,  ///< used by non-witness nodes\n            skip_transaction_dupe_check = 1 << 2,  ///< used while reindexing\n            skip_block_size_check       = 1 << 4,  ///< used when applying locally generated transactions\n            skip_tapos_check            = 1 << 5,  ///< used while reindexing -- note this skips expiration check too\n            // skip_authority_check        = 1 << 6,  // removed, effectively identical to skip_transaction_signatures\n            skip_merkle_check           = 1 << 7,  ///< used while reindexing\n            skip_assert_evaluation      = 1 << 8,  ///< used while reindexing\n            skip_undo_history_check     = 1 << 9,  ///< used while reindexing\n            skip_witness_schedule_check = 1 << 10 ///< used while reindexing\n         };\n\n         /**\n          * @brief Open a database, creating a new one if necessary\n          *\n          * Opens a database in the specified directory. If no initialized database is found, genesis_loader is called\n          * and its return value is used as the genesis state when initializing the new database\n          *\n          * genesis_loader will not be called if an existing database is found.\n          *\n          * @param data_dir Path to open or create database in\n          * @param genesis_loader A callable object which returns the genesis state to initialize new databases on\n          * @param db_version a version string that changes when the internal database format and/or logic is modified\n          */\n          void open(\n             const fc::path& data_dir,\n             std::function<genesis_state_type()> genesis_loader,\n             const std::string& db_version );\n\n         /**\n          * @brief Rebuild object graph from block history and open detabase\n          * @param data_dir the path to store the database\n          *\n          * This method may be called after or instead of @ref database::open, and will rebuild the object graph by\n          * replaying blockchain history. When this method exits successfully, the database will be open.\n          */\n         void reindex(fc::path data_dir);\n\n         /**\n          * @brief wipe Delete database from disk, and potentially the raw chain as well.\n          * @param data_dir the path to store the database\n          * @param include_blocks If true, delete the raw chain as well as the database.\n          *\n          * Will close the database before wiping. Database will be closed when this function returns.\n          */\n         void wipe(const fc::path& data_dir, bool include_blocks);\n         void close(bool rewind = true);\n\n         //////////////////// db_witness_schedule.cpp ////////////////////\n\n         /**\n          * @brief Get the witness scheduled for block production in a slot.\n          *\n          * slot_num always corresponds to a time in the future.\n          *\n          * If slot_num == 1, returns the next scheduled witness.\n          * If slot_num == 2, returns the next scheduled witness after\n          * 1 block gap.\n          *\n          * Use the get_slot_time() and get_slot_at_time() functions\n          * to convert between slot_num and timestamp.\n          *\n          * Passing slot_num == 0 returns GRAPHENE_NULL_WITNESS\n          */\n         witness_id_type get_scheduled_witness(uint32_t slot_num)const;\n\n         /**\n          * Get the time at which the given slot occurs.\n          *\n          * If slot_num == 0, return time_point_sec().\n          *\n          * If slot_num == N for N > 0, return the Nth next\n          * block-interval-aligned time greater than head_block_time().\n          */\n         fc::time_point_sec get_slot_time(uint32_t slot_num)const;\n\n         /**\n          * Get the last slot which occurs AT or BEFORE the given time.\n          *\n          * The return value is the greatest value N such that\n          * get_slot_time( N ) <= when.\n          *\n          * If no such N exists, return 0.\n          */\n         uint32_t get_slot_at_time(fc::time_point_sec when)const;\n\n         /**\n          *  Calculate the percent of block production slots that were missed in the\n          *  past 128 blocks, not including the current block.\n          */\n         uint32_t witness_participation_rate()const;\n      private:\n         uint32_t update_witness_missed_blocks( const signed_block& b );\n\n         void update_witness_schedule();\n\n         //////////////////// db_getter.cpp ////////////////////\n\n      public:\n         const chain_id_type&                   get_chain_id()const;\n         const asset_object&                    get_core_asset()const;\n         const asset_dynamic_data_object&       get_core_dynamic_data()const;\n         const chain_property_object&           get_chain_properties()const;\n         const global_property_object&          get_global_properties()const;\n         const dynamic_global_property_object&  get_dynamic_global_properties()const;\n         const node_property_object&            get_node_properties()const;\n         const fee_schedule&                    current_fee_schedule()const;\n         const account_statistics_object&       get_account_stats_by_owner( account_id_type owner )const;\n         const witness_schedule_object&         get_witness_schedule_object()const;\n\n         time_point_sec   head_block_time()const;\n         uint32_t         head_block_num()const;\n         block_id_type    head_block_id()const;\n         witness_id_type  head_block_witness()const;\n\n         decltype( chain_parameters::block_interval ) block_interval( )const;\n\n         node_property_object& node_properties();\n\n         /**\n          * @brief Get a list of custom authorities which can validate the provided operation for the provided account\n          * @param account The account whose authority is required\n          * @param op The operation requring the specified account's authority\n          * @param rejected_authorities [Optional] A pointer to a map that should be populated with the custom\n          * authorities which were valid, but rejected because the operation did not comply with the restrictions\n          * @return A vector of authorities which can be used to authorize op in place of account\n          */\n         vector<authority> get_viable_custom_authorities(\n                 account_id_type account, const operation& op,\n                 rejected_predicate_map* rejected_authorities = nullptr )const;\n\n         uint32_t last_non_undoable_block_num() const;\n\n         /// Find the limit order which is the individual settlement fund of the specified asset\n         /// @param a ID of the asset\n         /// @return nullptr if not found, pointer to the limit order if found\n         const limit_order_object* find_settled_debt_order( const asset_id_type& a )const;\n\n         /// Find the call order with the least collateral ratio\n         /// @param bitasset The bitasset object\n         /// @param force_by_collateral_index Whether to forcefully search via the by_collateral index\n         /// @return nullptr if not found, pointer to the call order if found\n         const call_order_object* find_least_collateralized_short( const asset_bitasset_data_object& bitasset,\n                                                                   bool force_by_collateral_index )const;\n\n         //////////////////// db_init.cpp ////////////////////\n         ///@{\n\n         /// Reset the object graph in-memory\n         void initialize_indexes(); // Mark as public since it is used in tests\n      private:\n         void initialize_evaluators();\n         void init_genesis(const genesis_state_type& genesis_state = genesis_state_type());\n\n         template<typename EvaluatorType>\n         void register_evaluator()\n         {\n            const auto op_type = operation::tag<typename EvaluatorType::operation_type>::value;\n            FC_ASSERT( op_type >= 0, \"Negative operation type\" );\n            FC_ASSERT( op_type < _operation_evaluators.size(),\n                       \"The operation type (${a}) must be smaller than the size of _operation_evaluators (${b})\",\n                       (\"a\", op_type)(\"b\", _operation_evaluators.size()) );\n            _operation_evaluators[op_type] = std::make_unique<op_evaluator_impl<EvaluatorType>>();\n         }\n         ///@}\n\n         //////////////////// db_balance.cpp ////////////////////\n\n      public:\n         /**\n          * @brief Retrieve a particular account's balance in a given asset\n          * @param owner Account whose balance should be retrieved\n          * @param asset_id ID of the asset to get balance in\n          * @return owner's balance in asset\n          */\n         asset get_balance(account_id_type owner, asset_id_type asset_id)const;\n         /// This is an overloaded method.\n         asset get_balance(const account_object& owner, const asset_object& asset_obj)const;\n\n         /**\n          * @brief Adjust a particular account's balance in a given asset by a delta\n          * @param account ID of account whose balance should be adjusted\n          * @param delta Asset ID and amount to adjust balance by\n          */\n         void adjust_balance(account_id_type account, asset delta);\n\n         void deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta);\n         /**\n          * @brief Retrieve a particular account's market fee vesting balance in a given asset\n          * @param account_id Account whose balance should be retrieved\n          * @param asset_id ID of the asset to get balance in\n          * @return owner's balance in asset\n          */\n         asset get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id);\n\n         /**\n          * @brief Helper to make lazy deposit to CDD VBO.\n          *\n          * If the given optional VBID is not valid(),\n          * or it does not have a CDD vesting policy,\n          * or the owner / vesting_seconds of the policy\n          * does not match the parameter, then credit amount\n          * to newly created VBID and return it.\n          *\n          * Otherwise, credit amount to ovbid.\n          *\n          * @return ID of newly created VBO, but only if VBO was created.\n          */\n         optional< vesting_balance_id_type > deposit_lazy_vesting(\n            const optional< vesting_balance_id_type >& ovbid,\n            share_type amount,\n            uint32_t req_vesting_seconds,\n            vesting_balance_type balance_type,\n            account_id_type req_owner,\n            bool require_vesting );\n\n         /// helper to handle cashback rewards\n         void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true);\n         /// helper to handle witness pay\n         void deposit_witness_pay(const witness_object& wit, share_type amount);\n\n         string to_pretty_string( const asset& a )const;\n\n         //////////////////// db_debug.cpp ////////////////////\n\n         void debug_dump();\n         void apply_debug_updates();\n         void debug_update( const fc::variant_object& update );\n\n         //////////////////// db_market.cpp ////////////////////\n\n         /// @ingroup Market Helpers\n         /// @{\n\n         /// Globally settle @p bitasset at @p settle_price, let margin calls pay a premium and margin call fee if\n         /// @p check_margin_calls is @c true (in this case others would be closed not at @p settle_price but at a\n         /// price better for their owners).\n         void globally_settle_asset( const asset_object& bitasset, const price& settle_price,\n                                     bool check_margin_calls = false );\n         void cancel_settle_order( const force_settlement_object& order );\n         void cancel_limit_order( const limit_order_object& order,\n                                  bool create_virtual_op = true,\n                                  bool skip_cancel_fee = false );\n         void revive_bitasset( const asset_object& bitasset, const asset_bitasset_data_object& bad );\n         void cancel_bid(const collateral_bid_object& bid, bool create_virtual_op = true);\n         void execute_bid( const collateral_bid_object& bid, share_type debt_covered,\n                           share_type collateral_from_fund, const price_feed& current_feed );\n\n      private:\n         /// Clean up for a limit order and then remove it from database\n         void cleanup_and_remove_limit_order( const limit_order_object& order );\n         /// Process on_fill for a limit order\n         /// @return the ID of the newly created take profit order (in that case), otherwise null\n         optional<limit_order_id_type> process_limit_order_on_fill( const limit_order_object& order,\n                                                                    const asset& order_receives );\n         void _cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad );\n         bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true,\n                                   const asset_bitasset_data_object* bitasset_ptr = nullptr );\n         template<typename IndexType>\n         void globally_settle_asset_impl( const asset_object& bitasset,\n                                          const price& settle_price,\n                                          const IndexType& call_index,\n                                          bool check_margin_calls = false );\n         /// Individually settle the @p call_order. Called when the call order is undercollateralized.\n         /// See @ref protocol::bitasset_options::black_swan_response_type for more info.\n         /// @param bitasset the bitasset object\n         /// @param call_order the call order\n         void individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& call_order );\n         /// Match force settlements with margin calls\n         /// @param bitasset the asset that to be checked\n         /// @return true if matched at least one margin call order\n         bool match_force_settlements( const asset_bitasset_data_object& bitasset );\n         /// Matches the two orders\n         /// @param settle the force-settlement order\n         /// @param call the call order\n         /// @param match_price the price to calculate how much the call order pays\n         /// @param bitasset the bitasset object corresponding to debt asset of the call order\n         /// @param max_settlement the maximum debt amount to be filled during this match\n         /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate\n         ///        how much the settle order receives when the call order is being margin called\n         /// @param is_margin_call whether the call order is being margin called\n         /// @param settle_order_is_taker whether the settle_order is the taker\n         /// @return the amount of asset settled\n         asset match_impl( const force_settlement_object& settle,\n                           const call_order_object& call,\n                           const price& match_price,\n                           const asset_bitasset_data_object& bitasset,\n                           const asset& max_settlement,\n                           const price& fill_price,\n                           bool is_margin_call = false,\n                           bool settle_order_is_taker = true );\n\n      public:\n         /**\n          * @brief Process a new limit order through the markets\n          * @param new_order_object The new order to process\n          * @return true if order was completely filled; false otherwise\n          *\n          * This function takes a new limit order, and runs the markets attempting to match it with existing orders\n          * already on the books.\n          */\n         ///@{\n         bool apply_order_before_hardfork_625( const limit_order_object& new_order_object );\n         bool apply_order(                     const limit_order_object& new_order_object );\n         ///@}\n\n         /**\n          * @brief Process a new force-settlement request\n          * @param new_settlement The new force-settlement request\n          * @param bitasset The bitasset data object\n          * @param asset_obj The asset object\n          *\n          * Since the core-2481 hard fork, this function is called after a new force-settlement object is created\n          * to check if there are margin calls to be matched instantly.\n          */\n         void apply_force_settlement( const force_settlement_object& new_settlement,\n                                      const asset_bitasset_data_object& bitasset,\n                                      const asset_object& asset_obj );\n\n         bool check_call_orders( const asset_object& mia, bool enable_black_swan = true,\n                                 bool for_new_limit_order = false,\n                                 const asset_bitasset_data_object* bitasset_ptr = nullptr,\n                                 bool mute_exceptions = false,\n                                 bool skip_matching_settle_orders = false );\n\n         /**\n          * @brief Match the settled debt order of the specified asset as taker with other orders on the opposite side\n          *        of the order book\n          * @param bitasset The bitasset data object\n          *\n          * Since the core-2591 hard fork, this function is called after processed all call orders in\n          * @ref check_call_orders().\n          */\n         void check_settled_debt_order( const asset_bitasset_data_object& bitasset );\n\n         // Note: Ideally this should be private.\n         //       Now it is public because we use it in a non-member function in db_market.cpp .\n         enum class match_result_type\n         {\n            none_filled = 0,\n            only_taker_filled = 1,\n            only_maker_filled = 2,\n            both_filled = 3\n         };\n\n      private:\n         /**\n          * Matches the two orders, the first parameter is taker, the second is maker.\n          *\n          * @return which orders were filled (and thus removed)\n          */\n         ///@{\n         match_result_type match( const limit_order_object& taker, const limit_order_object& maker,\n                                  const price& trade_price );\n         match_result_type match_limit_normal_limit( const limit_order_object& taker, const limit_order_object& maker,\n                                                     const price& trade_price );\n         match_result_type match_limit_settled_debt( const limit_order_object& taker, const limit_order_object& maker,\n                                                     const price& trade_price );\n         match_result_type match_settled_debt_limit( const limit_order_object& taker, const limit_order_object& maker,\n                                                     const price& trade_price );\n         /***\n          * @brief Match limit order as taker to a call order as maker\n          * @param taker the order that is removing liquidity from the book\n          * @param maker the order that put liquidity on the book\n          * @param trade_price the price the trade should execute at\n          * @param bitasset the bitasset object corresponding to debt asset of the call order\n          * @param call_pays_price price call order pays. Call order may pay more collateral\n          *    than limit order takes if call order subject to a margin call fee.\n          * @returns 0 if no orders were matched, 1 if taker was filled, 2 if maker was filled, 3 if both were filled\n          */\n         match_result_type match( const limit_order_object& taker, const call_order_object& maker,\n                    const price& trade_price,\n                    const asset_bitasset_data_object& bitasset,\n                    const price& call_pays_price);\n         /// If separate call_pays_price not provided, assume call pays at trade_price:\n         match_result_type match( const limit_order_object& taker, const call_order_object& maker,\n                    const price& trade_price,\n                    const asset_bitasset_data_object& bitasset) {\n            return match(taker, maker, trade_price, bitasset, trade_price);\n         }\n\n         ///@}\n\n         /// Matches the two orders, the first parameter is taker, the second is maker.\n         /// @param settle the force-settlement order\n         /// @param call the call order\n         /// @param match_price the price to calculate how much the call order pays\n         /// @param bitasset the bitasset object corresponding to debt asset of the call order\n         /// @param max_settlement the maximum debt amount to be filled during this match\n         /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate\n         ///        how much the settle order receives when the call order is being margin called\n         /// @param is_margin_call whether the call order is being margin called\n         /// @return the amount of asset settled\n         asset match( const force_settlement_object& settle,\n                      const call_order_object& call,\n                      const price& match_price,\n                      const asset_bitasset_data_object& bitasset,\n                      const asset& max_settlement,\n                      const price& fill_price,\n                      bool is_margin_call = false );\n\n         /// Matches the two orders, the first parameter is taker, the second is maker.\n         /// @param call the call order being margin called\n         /// @param settle the force-settlement order\n         /// @param match_price the price to calculate how much the call order pays\n         /// @param bitasset the bitasset object corresponding to debt asset of the call order\n         /// @param max_settlement the maximum debt amount to be filled during this match\n         /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate\n         ///        how much the settle order receives.\n         /// @return the amount of asset settled\n         asset match( const call_order_object& call,\n                      const force_settlement_object& settle,\n                      const price& match_price,\n                      const asset_bitasset_data_object& bitasset,\n                      const asset& max_settlement,\n                      const price& fill_price );\n\n         /**\n          * @brief fills limit order\n          * @param order the order\n          * @param pays what the account is paying\n          * @param receives what the account is receiving\n          * @param cull_if_small take care of dust\n          * @param fill_price the transaction price\n          * @param is_maker TRUE if this order is maker, FALSE if taker\n          * @return true if the order was completely filled and thus freed.\n          */\n         bool fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives,\n               bool cull_if_small, const price& fill_price, const bool is_maker );\n         /***\n          * @brief attempt to fill a call order\n          * @param order the order\n          * @param pays what the buyer pays for the collateral\n          * @param receives the collateral received by the buyer\n          * @param fill_price the price the transaction executed at\n          * @param is_maker TRUE if the buyer is the maker, FALSE if the buyer is the taker\n          * @param margin_fee Margin call fees paid in collateral asset\n          * @param reduce_current_supply Whether to reduce current supply of the asset. Usually it is true.\n          *                              When globally settleing or individually settling it is false.\n          * @returns TRUE if the order was completely filled\n          */\n         bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives,\n                               const price& fill_price, const bool is_maker, const asset& margin_fee,\n                               bool reduce_current_supply = true );\n\n         /// Overload provides compatible default value for margin_fee: (margin_fee.asset_id == pays.asset_id)\n         bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives,\n                               const price& fill_price, const bool is_maker,\n                               bool reduce_current_supply = true )\n         {\n            return fill_call_order( order, pays, receives, fill_price, is_maker, asset(0, pays.asset_id),\n                                    reduce_current_supply );\n         }\n\n         bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives,\n                                 const price& fill_price, bool is_maker, bool pay_force_settle_fee = true );\n\n         /// helpers to fill_order\n         /// @{\n         void pay_order( const account_object& receiver, const asset& receives, const asset& pays );\n\n      public:\n         /**\n          * @brief Calculate the market fee that is to be taken\n          * @param trade_asset the asset (passed in to avoid a lookup)\n          * @param trade_amount the quantity that the fee calculation is based upon\n          * @param is_maker TRUE if this is the fee for a maker, FALSE if taker\n          */\n         asset calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount,\n                                     const bool& is_maker )const;\n         asset pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives,\n                               const bool& is_maker, const optional<asset>& calculated_market_fees = {});\n         asset pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives);\n         /// @}\n         ///@}\n\n         //////////////////// db_block.cpp ////////////////////\n\n         /**\n          *  @return true if the block is in our fork DB or saved to disk as\n          *  part of the official chain, otherwise return false\n          */\n         bool                       is_known_block( const block_id_type& id )const;\n         bool                       is_known_transaction( const transaction_id_type& id )const;\n         block_id_type              get_block_id_for_num( uint32_t block_num )const;\n         optional<signed_block>     fetch_block_by_id( const block_id_type& id )const;\n         optional<signed_block>     fetch_block_by_number( uint32_t num )const;\n         const signed_transaction&  get_recent_transaction( const transaction_id_type& trx_id )const;\n         std::vector<block_id_type> get_block_ids_on_fork(block_id_type head_of_fork) const;\n\n         void                       add_checkpoints( const flat_map<uint32_t,block_id_type>& checkpts );\n         const flat_map<uint32_t,block_id_type> get_checkpoints()const { return _checkpoints; }\n         bool before_last_checkpoint()const;\n\n         bool push_block( const signed_block& b, uint32_t skip = skip_nothing );\n         processed_transaction push_transaction( const precomputable_transaction& trx, uint32_t skip = skip_nothing );\n      private:\n         bool _push_block( const signed_block& b );\n      public:\n         // It is public because it is used in pending_transactions_restorer in db_with.hpp\n         processed_transaction _push_transaction( const precomputable_transaction& trx );\n         ///@throws fc::exception if the proposed transaction fails to apply.\n         processed_transaction push_proposal( const proposal_object& proposal );\n\n         signed_block generate_block(\n            const fc::time_point_sec when,\n            witness_id_type witness_id,\n            const fc::ecc::private_key& block_signing_private_key,\n            uint32_t skip\n            );\n      private:\n         signed_block _generate_block(\n            const fc::time_point_sec when,\n            witness_id_type witness_id,\n            const fc::ecc::private_key& block_signing_private_key\n            );\n\n      public:\n         void pop_block();\n         void clear_pending();\n\n         /**\n          *  This method is used to track appied operations during the evaluation of a block, these\n          *  operations should include any operation actually included in a transaction as well\n          *  as any implied/virtual operations that resulted, such as filling an order.  The\n          *  applied operations is cleared after applying each block and calling the block\n          *  observers which may want to index these operations.\n          *  @param op The operation to push\n          *  @param is_virtual Whether the operation is a virtual operation\n          *\n          *  @return the op_id which can be used to set the result after it has finished being applied.\n          */\n         uint32_t  push_applied_operation( const operation& op, bool is_virtual = true );\n         void      set_applied_operation_result( uint32_t op_id, const operation_result& r );\n         const vector<optional< operation_history_object > >& get_applied_operations()const;\n\n         /**\n          *  This signal is emitted after all operations and virtual operation for a\n          *  block have been applied but before the get_applied_operations() are cleared.\n          *\n          *  You may not yield from this callback because the blockchain is holding\n          *  the write lock and may be in an \"inconstant state\" until after it is\n          *  released.\n          */\n         fc::signal<void(const signed_block&)>           applied_block;\n\n         /**\n          * This signal is emitted any time a new transaction is added to the pending\n          * block state.\n          */\n         fc::signal<void(const signed_transaction&)>     on_pending_transaction;\n\n         /**\n          *  Emitted After a block has been applied and committed.  The callback\n          *  should not yield and should execute quickly.\n          */\n         fc::signal<void(const vector<object_id_type>&, const flat_set<account_id_type>&)> new_objects;\n\n         /**\n          *  Emitted After a block has been applied and committed.  The callback\n          *  should not yield and should execute quickly.\n          */\n         fc::signal<void(const vector<object_id_type>&, const flat_set<account_id_type>&)> changed_objects;\n\n         /** this signal is emitted any time an object is removed and contains a\n          * pointer to the last value of every object that was removed.\n          */\n         fc::signal<void(const vector<object_id_type>&,\n                         const vector<const object*>&, const flat_set<account_id_type>&)>  removed_objects;\n\n         ///@{\n         /**\n          *  This method validates transactions without adding it to the pending state.\n          *  @return true if the transaction would validate\n          */\n         processed_transaction validate_transaction( const signed_transaction& trx );\n\n\n         /** when popping a block, the transactions that were removed get cached here so they\n          * can be reapplied at the proper time */\n         std::deque< precomputable_transaction > _popped_tx;\n\n         /**\n          * @}\n          */\n\n         /** Precomputes digests, signatures and operation validations depending\n          *  on skip flags. \"Expensive\" computations may be done in a parallel\n          *  thread.\n          *\n          * @param block the block to preprocess\n          * @param skip indicates which computations can be skipped\n          * @return a future that will resolve to the input block with\n          *         precomputations applied\n          */\n         fc::future<void> precompute_parallel( const signed_block& block, const uint32_t skip = skip_nothing )const;\n\n         /** Precomputes digests, signatures and operation validations.\n          *  \"Expensive\" computations may be done in a parallel thread.\n          *\n          * @param trx the transaction to preprocess\n          * @return a future that will resolve to the input transaction with\n          *         precomputations applied\n          */\n         fc::future<void> precompute_parallel( const precomputable_transaction& trx )const;\n      private:\n         template<typename Trx>\n         void _precompute_parallel( const Trx* trx, const size_t count, const uint32_t skip )const;\n\n      protected:\n         // Mark pop_undo() as protected -- we do not want outside calling pop_undo(),\n         // it should call pop_block() instead\n         void pop_undo() { object_database::pop_undo(); }\n\n      private:\n         optional<undo_database::session>       _pending_tx_session;\n         vector< unique_ptr<op_evaluator> >     _operation_evaluators;\n\n         template<class Index>\n         vector<std::reference_wrapper<const typename Index::object_type>> sort_votable_objects(size_t count)const;\n\n      public:\n         // these were formerly private, but they have a fairly well-defined API, so let's make them public\n         void                  apply_block( const signed_block& next_block, uint32_t skip = skip_nothing );\n         processed_transaction apply_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing );\n         operation_result      apply_operation( transaction_evaluation_state& eval_state, const operation& op,\n                                                bool is_virtual = true );\n\n      private:\n         void                  _apply_block( const signed_block& next_block );\n         processed_transaction _apply_transaction( const signed_transaction& trx );\n\n         /// Validate, evaluate and apply a virtual operation using a temporary undo_database session,\n         /// if fail, rewind any changes made\n         operation_result      try_push_virtual_operation( transaction_evaluation_state& eval_state,\n                                                           const operation& op );\n\n         ///Steps involved in applying a new block\n         ///@{\n\n         const witness_object& validate_block_header( uint32_t skip, const signed_block& next_block )const;\n         const witness_object& _validate_block_header( const signed_block& next_block )const;\n         void verify_signing_witness( const signed_block& new_block, const fork_item& fork_entry )const;\n         void update_witnesses( fork_item& fork_entry )const;\n         void create_block_summary(const signed_block& next_block);\n\n         //////////////////// db_notify.cpp ////////////////////\n\n      protected:\n         void notify_applied_block( const signed_block& block );\n         void notify_on_pending_transaction( const signed_transaction& tx );\n         void notify_changed_objects();\n\n         //////////////////// db_update.cpp ////////////////////\n      public:\n         generic_operation_result process_tickets();\n         /// Derive @ref asset_bitasset_data_object::current_feed from other data in the database\n         /// @param bitasset The bitasset object\n         /// @param skip_median_update Whether to skip updating @ref asset_bitasset_data_object::median_feed\n         void update_bitasset_current_feed( const asset_bitasset_data_object& bitasset,\n                                            bool skip_median_update = false );\n      private:\n         void update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks );\n         void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block);\n         void update_last_irreversible_block();\n         void clear_expired_transactions();\n         void clear_expired_proposals();\n         void clear_expired_orders();\n         void clear_expired_force_settlements();\n         void update_expired_feeds();\n         void update_core_exchange_rates();\n         void update_maintenance_flag( bool new_maintenance_flag );\n         void update_withdraw_permissions();\n         void update_credit_offers_and_deals();\n         void clear_expired_htlcs();\n\n         ///Steps performed only at maintenance intervals\n         ///@{\n\n         //////////////////// db_maint.cpp ////////////////////\n\n         void initialize_budget_record( fc::time_point_sec now, budget_record& rec )const;\n         void process_budget();\n         void pay_workers( share_type& budget );\n         void perform_chain_maintenance( const signed_block& next_block );\n         void update_active_witnesses();\n         void update_active_committee_members();\n         void update_worker_votes();\n         void process_bids( const asset_bitasset_data_object& bad );\n         void process_bitassets();\n\n         template<class Type>\n         void perform_account_maintenance( Type tally_helper );\n         ///@}\n         ///@}\n\n         vector< processed_transaction >        _pending_tx;\n         fork_database                          _fork_db;\n\n         /**\n          *  Note: we can probably store blocks by block num rather than\n          *  block id because after the undo window is past the block ID\n          *  is no longer relevant and its number is irreversible.\n          *\n          *  During the \"fork window\" we can cache blocks in memory\n          *  until the fork is resolved.  This should make maintaining\n          *  the fork tree relatively simple.\n          */\n         block_database   _block_id_to_block;\n\n         /**\n          * Contains the set of ops that are in the process of being applied from\n          * the current block.  It contains real and virtual operations in the\n          * order they occur and is cleared after the applied_block signal is\n          * emited.\n          */\n         vector<optional<operation_history_object> >  _applied_ops;\n\n      public:\n         fc::time_point_sec                _current_block_time;\n         uint32_t                          _current_block_num    = 0;\n      private:\n         uint16_t                          _current_trx_in_block = 0;\n         uint16_t                          _current_op_in_trx    = 0;\n         uint32_t                          _current_virtual_op   = 0;\n\n         vector<uint64_t>                  _vote_tally_buffer;\n         vector<uint64_t>                  _witness_count_histogram_buffer;\n         vector<uint64_t>                  _committee_count_histogram_buffer;\n         std::array<uint64_t,2>            _total_voting_stake; // 0=committee, 1=witness,\n                                                                // as in vote_id_type::vote_type\n\n         flat_map<uint32_t,block_id_type>  _checkpoints;\n\n         node_property_object              _node_property_object;\n\n         /// Whether to update votes of standby witnesses and committee members when performing chain maintenance.\n         /// Set it to true to provide accurate data to API clients, set to false to have better performance.\n         bool                              _track_standby_votes = true;\n\n         /**\n          * Whether database is successfully opened or not.\n          *\n          * The database is considered open when there's no exception\n          * or assertion fail during database::open() method, and\n          * database::close() has not been called, or failed during execution.\n          */\n         bool                              _opened = false;\n\n         /// Counts nested undo sessions due to (for example) proposal updates or order-sends-order executions\n         uint32_t                          _undo_session_nesting_depth = 0;\n\n         /// Tracks assets affected by bitshares-core issue #453 before hard fork #615 in one block\n         flat_set<asset_id_type>           _issue_453_affected_assets;\n\n         /// Pointers to core asset object and global objects who will have immutable addresses after created\n         ///@{\n         const asset_object*                    _p_core_asset_obj          = nullptr;\n         const asset_dynamic_data_object*       _p_core_dynamic_data_obj   = nullptr;\n         const global_property_object*          _p_global_prop_obj         = nullptr;\n         const dynamic_global_property_object*  _p_dyn_global_prop_obj     = nullptr;\n         const chain_property_object*           _p_chain_property_obj      = nullptr;\n         const witness_schedule_object*         _p_witness_schedule_obj    = nullptr;\n         ///@}\n      public:\n         /// Enable or disable tracking of votes of standby witnesses and committee members\n         inline void enable_standby_votes_tracking(bool enable)  { _track_standby_votes = enable; }\n   };\n\n} }\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/db_with.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/database.hpp>\n\n/*\n * This file provides with() functions which modify the database\n * temporarily, then restore it.  These functions are mostly internal\n * implementation detail of the database.\n *\n * Essentially, we want to be able to use \"finally\" to restore the\n * database regardless of whether an exception is thrown or not, but there\n * is no \"finally\" in C++.  Instead, C++ requires us to create a struct\n * and put the finally block in a destructor.  Aagh!\n */\n\nnamespace graphene { namespace chain { namespace detail {\n/**\n * Class used to help the with_skip_flags implementation.\n * It must be defined in this header because it must be\n * available to the with_skip_flags implementation,\n * which is a template and therefore must also be defined\n * in this header.\n */\nstruct skip_flags_restorer\n{\n   skip_flags_restorer( node_property_object& npo, uint32_t old_skip_flags )\n      : _npo( npo ), _old_skip_flags( old_skip_flags )\n   {}\n\n   ~skip_flags_restorer()\n   {\n      _npo.skip_flags = _old_skip_flags;\n   }\n\n   node_property_object& _npo;\n   uint32_t _old_skip_flags;      // initialized in ctor\n};\n\n/**\n * Class used to help the without_pending_transactions\n * implementation.\n *\n * TODO:  Change the name of this class to better reflect the fact\n * that it restores popped transactions as well as pending transactions.\n */\nstruct pending_transactions_restorer\n{\n   pending_transactions_restorer( database& db, std::vector<processed_transaction>&& pending_transactions )\n      : _db(db), _pending_transactions( std::move(pending_transactions) )\n   {\n      _db.clear_pending();\n   }\n\n   ~pending_transactions_restorer()\n   {\n      for( const auto& tx : _db._popped_tx )\n      {\n         try {\n            if( !_db.is_known_transaction( tx.id() ) ) {\n               _db._push_transaction( tx );\n            }\n         } catch ( const fc::exception& ) { // ignore invalid transactions\n         }\n      }\n      _db._popped_tx.clear();\n      for( const processed_transaction& tx : _pending_transactions )\n      {\n         try\n         {\n            if( !_db.is_known_transaction( tx.id() ) ) {\n               _db._push_transaction( tx );\n            }\n         }\n         catch( const fc::exception& )\n         { // ignore invalid transactions\n         }\n      }\n   }\n\n   database& _db;\n   std::vector< processed_transaction > _pending_transactions;\n};\n\n/**\n * Set the skip_flags to the given value, call callback,\n * then reset skip_flags to their previous value after\n * callback is done.\n */\ntemplate< typename Lambda >\nvoid with_skip_flags(\n   database& db,\n   uint32_t skip_flags,\n   Lambda callback )\n{\n   node_property_object& npo = db.node_properties();\n   skip_flags_restorer restorer( npo, npo.skip_flags );\n   npo.skip_flags = skip_flags;\n   callback();\n   return;\n}\n\n/**\n * Empty pending_transactions, call callback,\n * then reset pending_transactions after callback is done.\n *\n * Pending transactions which no longer validate will be culled.\n */\ntemplate< typename Lambda >\nvoid without_pending_transactions(\n   database& db,\n   std::vector<processed_transaction>&& pending_transactions,\n   Lambda callback )\n{\n    pending_transactions_restorer restorer( db, std::move(pending_transactions) );\n    callback();\n    return;\n}\n\n} } } // graphene::chain::detail\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/transaction_evaluation_state.hpp>\n#include <graphene/protocol/operations.hpp>\n\nnamespace graphene { namespace chain {\n\n   class database;\n   class generic_evaluator;\n   class transaction_evaluation_state;\n   class account_object;\n   class account_statistics_object;\n   class asset_object;\n   class asset_dynamic_data_object;\n\n   class generic_evaluator\n   {\n   public:\n      virtual ~generic_evaluator(){}\n\n      virtual int get_type()const = 0;\n      virtual operation_result start_evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply);\n\n      /**\n       * @note derived classes should ASSUME that the default validation that is\n       * indepenent of chain state should be performed by op.validate() and should\n       * not perform these extra checks.\n       */\n      virtual operation_result evaluate(const operation& op) = 0;\n      virtual operation_result apply(const operation& op) = 0;\n\n      /**\n       * Routes the fee to where it needs to go.  The default implementation\n       * routes the fee to the account_statistics_object of the fee_paying_account.\n       *\n       * Before pay_fee() is called, the fee is computed by prepare_fee() and has been\n       * moved out of the fee_paying_account and (if paid in a non-CORE asset) converted\n       * by the asset's fee pool.\n       *\n       * Therefore, when pay_fee() is called, the fee only exists in this->core_fee_paid.\n       * So pay_fee() need only increment the receiving balance.\n       *\n       * The default implementation simply calls account_statistics_object->pay_fee() to\n       * increment pending_fees or pending_vested_fees.\n       */\n      virtual void pay_fee();\n\n      database& db()const;\n\n      //void check_required_authorities(const operation& op);\n   protected:\n      /**\n       * @brief Fetch objects relevant to fee payer and set pointer members\n       * @param account_id Account which is paying the fee\n       * @param fee The fee being paid. May be in assets other than core.\n       *\n       * This method verifies that the fee is valid and sets the object pointer members and the fee fields. It should\n       * be called during do_evaluate.\n       *\n       * In particular, core_fee_paid field is set by prepare_fee().\n       */\n      void prepare_fee(account_id_type account_id, asset fee);\n\n      /**\n       * Convert the fee into BTS through the exchange pool.\n       *\n       * Reads core_fee_paid field for how much CORE is deducted from the exchange pool,\n       * and fee_from_account for how much USD is added to the pool.\n       *\n       * Since prepare_fee() does the validation checks ensuring the account and fee pool\n       * have sufficient balance and the exchange rate is correct,\n       * those validation checks are not replicated here.\n       *\n       * Rather than returning a value, this method fills in core_fee_paid field.\n       */\n      virtual void convert_fee();\n\n      object_id_type get_relative_id( object_id_type rel_id )const;\n\n      /**\n       * pay_fee() for FBA subclass should simply call this method\n       */\n      void pay_fba_fee( uint64_t fba_id );\n\n      // the next two functions are helpers that allow template functions declared in this \n      // header to call db() without including database.hpp, which would\n      // cause a circular dependency\n      share_type calculate_fee_for_operation(const operation& op) const;\n      void db_adjust_balance(const account_id_type& fee_payer, asset fee_from_account);\n\n      asset                            fee_from_account;\n      share_type                       core_fee_paid;\n      const account_object*            fee_paying_account = nullptr;\n      const account_statistics_object* fee_paying_account_statistics = nullptr;\n      const asset_object*              fee_asset          = nullptr;\n      const asset_dynamic_data_object* fee_asset_dyn_data = nullptr;\n      transaction_evaluation_state*    trx_state;\n   };\n\n   class op_evaluator\n   {\n   public:\n      virtual ~op_evaluator(){}\n      virtual operation_result evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply) = 0;\n   };\n\n   template<typename T>\n   class op_evaluator_impl : public op_evaluator\n   {\n   public:\n      virtual operation_result evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply = true) override\n      {\n         T eval;\n         return eval.start_evaluate(eval_state, op, apply);\n      }\n   };\n\n   template<typename DerivedEvaluator>\n   class evaluator : public generic_evaluator\n   {\n   public:\n      virtual int get_type()const override { return operation::tag<typename DerivedEvaluator::operation_type>::value; }\n\n      virtual operation_result evaluate(const operation& o) final override\n      {\n         auto* eval = static_cast<DerivedEvaluator*>(this);\n         const auto& op = o.get<typename DerivedEvaluator::operation_type>();\n\n         prepare_fee(op.fee_payer(), op.fee);\n         if( !trx_state->skip_fee_schedule_check )\n         {\n            share_type required_fee = calculate_fee_for_operation(op);\n            GRAPHENE_ASSERT( core_fee_paid >= required_fee,\n                       insufficient_fee,\n                       \"Insufficient Fee Paid\",\n                       (\"core_fee_paid\",core_fee_paid)(\"required\", required_fee) );\n         }\n\n         return eval->do_evaluate(op);\n      }\n\n      virtual operation_result apply(const operation& o) final override\n      {\n         auto* eval = static_cast<DerivedEvaluator*>(this);\n         const auto& op = o.get<typename DerivedEvaluator::operation_type>();\n\n         convert_fee();\n         pay_fee();\n\n         auto result = eval->do_apply(op);\n\n         db_adjust_balance(op.fee_payer(), -fee_from_account);\n\n         return result;\n      }\n   };\n} }\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/exceptions.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/exception/exception.hpp>\n#include <graphene/protocol/exceptions.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/types.hpp>\n\n#define GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( op_name )                \\\n   FC_DECLARE_DERIVED_EXCEPTION(                                      \\\n      op_name ## _validate_exception,                                 \\\n      graphene::chain::operation_validate_exception,                  \\\n      3040000 + 100 * operation::tag< op_name ## _operation >::value  \\\n      )                                                               \\\n   FC_DECLARE_DERIVED_EXCEPTION(                                      \\\n      op_name ## _evaluate_exception,                                 \\\n      graphene::chain::operation_evaluate_exception,                  \\\n      3050000 + 100 * operation::tag< op_name ## _operation >::value  \\\n      )\n\n#define GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( op_name )              \\\n   FC_IMPLEMENT_DERIVED_EXCEPTION(                                    \\\n      op_name ## _validate_exception,                                 \\\n      graphene::chain::operation_validate_exception,                  \\\n      3040000 + 100 * operation::tag< op_name ## _operation >::value, \\\n      #op_name \"_operation validation exception\"                      \\\n      )                                                               \\\n   FC_IMPLEMENT_DERIVED_EXCEPTION(                                    \\\n      op_name ## _evaluate_exception,                                 \\\n      graphene::chain::operation_evaluate_exception,                  \\\n      3050000 + 100 * operation::tag< op_name ## _operation >::value, \\\n      #op_name \"_operation evaluation exception\"                      \\\n      )\n\n#define GRAPHENE_DECLARE_OP_VALIDATE_EXCEPTION( exc_name, op_name, seqnum ) \\\n   FC_DECLARE_DERIVED_EXCEPTION(                                      \\\n      op_name ## _ ## exc_name,                                       \\\n      graphene::chain::op_name ## _validate_exception,                \\\n      3040000 + 100 * operation::tag< op_name ## _operation >::value  \\\n         + seqnum                                                     \\\n      )\n\n#define GRAPHENE_IMPLEMENT_OP_VALIDATE_EXCEPTION( exc_name, op_name, seqnum, msg ) \\\n   FC_IMPLEMENT_DERIVED_EXCEPTION(                                    \\\n      op_name ## _ ## exc_name,                                       \\\n      graphene::chain::op_name ## _validate_exception,                \\\n      3040000 + 100 * operation::tag< op_name ## _operation >::value  \\\n         + seqnum,                                                    \\\n      msg                                                             \\\n      )\n\n#define GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( exc_name, op_name, seqnum ) \\\n   FC_DECLARE_DERIVED_EXCEPTION(                                      \\\n      op_name ## _ ## exc_name,                                       \\\n      graphene::chain::op_name ## _evaluate_exception,                \\\n      3050000 + 100 * operation::tag< op_name ## _operation >::value  \\\n         + seqnum                                                     \\\n      )\n\n#define GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( exc_name, op_name, seqnum, msg ) \\\n   FC_IMPLEMENT_DERIVED_EXCEPTION(                                    \\\n      op_name ## _ ## exc_name,                                       \\\n      graphene::chain::op_name ## _evaluate_exception,                \\\n      3050000 + 100 * operation::tag< op_name ## _operation >::value  \\\n         + seqnum,                                                    \\\n      msg                                                             \\\n      )\n\n#define GRAPHENE_TRY_NOTIFY( signal, ... )                                    \\\n   try                                                                        \\\n   {                                                                          \\\n      signal( __VA_ARGS__ );                                                  \\\n   }                                                                          \\\n   catch( const graphene::chain::plugin_exception& e )                        \\\n   {                                                                          \\\n      elog( \"Caught plugin exception: ${e}\", (\"e\", e.to_detail_string() ) );  \\\n      throw;                                                                  \\\n   }                                                                          \\\n   catch( ... )                                                               \\\n   {                                                                          \\\n      wlog( \"Caught unexpected exception in plugin\" );                        \\\n   }\n\nnamespace graphene { namespace chain {\n\n   FC_DECLARE_EXCEPTION( chain_exception, 3000000 )\n\n   FC_DECLARE_DERIVED_EXCEPTION( database_query_exception,     chain_exception, 3010000 )\n   FC_DECLARE_DERIVED_EXCEPTION( block_validate_exception,     chain_exception, 3020000 )\n   FC_DECLARE_DERIVED_EXCEPTION( transaction_process_exception,chain_exception, 3030000 )\n   FC_DECLARE_DERIVED_EXCEPTION( operation_validate_exception, chain_exception, 3040000 )\n   FC_DECLARE_DERIVED_EXCEPTION( operation_evaluate_exception, chain_exception, 3050000 )\n   FC_DECLARE_DERIVED_EXCEPTION( utility_exception,            chain_exception, 3060000 )\n   FC_DECLARE_DERIVED_EXCEPTION( undo_database_exception,      chain_exception, 3070000 )\n   FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception,   chain_exception, 3080000 )\n   FC_DECLARE_DERIVED_EXCEPTION( black_swan_exception,         chain_exception, 3090000 )\n   FC_DECLARE_DERIVED_EXCEPTION( plugin_exception,             chain_exception, 3100000 )\n\n   FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds,           chain_exception, 37006 )\n\n   FC_DECLARE_DERIVED_EXCEPTION( duplicate_transaction,        transaction_process_exception, 3030001 )\n\n   FC_DECLARE_DERIVED_EXCEPTION( pop_empty_chain,              undo_database_exception, 3070001 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( transfer );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( from_account_not_whitelisted, transfer, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( to_account_not_whitelisted, transfer, 2 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( restricted_transfer_asset, transfer, 3 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_create );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( kill_unfilled, limit_order_create, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( market_not_whitelisted, limit_order_create, 2 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( market_blacklisted, limit_order_create, 3 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( selling_asset_unauthorized, limit_order_create, 4 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( receiving_asset_unauthorized, limit_order_create, 5 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( insufficient_balance, limit_order_create, 6 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_update );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_update, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_update, 2 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_cancel );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_cancel, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_cancel, 2 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( call_order_update );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( unfilled_margin_call, call_order_update, 1 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_create );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_create, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( auth_account_not_found, account_create, 2 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_incorrect_issuer, account_create, 3 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_already_exists, account_create, 4 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_too_many_markets, account_create, 5 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_update );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_update, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( auth_account_not_found, account_update, 2 )\n\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_whitelist );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_upgrade );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_transfer );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_create );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update_bitasset );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update_feed_producers );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_issue );\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_reserve );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( invalid_on_mia, asset_reserve, 1 )\n\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_fund_fee_pool );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_settle );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_global_settle );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_publish_feed );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( committee_member_create );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( witness_create );\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( proposal_create );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( review_period_required, proposal_create, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( review_period_insufficient, proposal_create, 2 )\n\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( proposal_update );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( proposal_delete );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_create );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_update );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_claim );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( withdraw_permission_delete );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( fill_order );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( global_parameters_update );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( vesting_balance_create );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( vesting_balance_withdraw );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( worker_create );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( custom );\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( assert );\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( balance_claim );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( claimed_too_often, balance_claim, 1 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( invalid_claim_amount, balance_claim, 2 )\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, balance_claim, 3 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( override_transfer );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( not_permitted, override_transfer, 1 )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( blind_transfer );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( unknown_commitment, blind_transfer, 1 )\n\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( transfer_from_blind_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_claim_fees_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( bid_collateral_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_claim_pool_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( asset_update_issuer_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( htlc_create_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( htlc_redeem_operation )\n   //GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( htlc_extend_operation )\n\n   GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( liquidity_pool_exchange );\n   GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( unfillable_price, liquidity_pool_exchange, 1 )\n\n   #define GRAPHENE_RECODE_EXC( cause_type, effect_type ) \\\n      catch( const cause_type& e ) \\\n      { throw( effect_type( e.what(), e.get_log() ) ); }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/fba_accumulator_id.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n * An object will be created at genesis for each of these FBA accumulators.\n */\nenum graphene_fba_accumulator_id_enum\n{\n   fba_accumulator_id_transfer_to_blind = 0,\n   fba_accumulator_id_blind_transfer,\n   fba_accumulator_id_transfer_from_blind,\n   fba_accumulator_id_count\n};\n\n} }\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/fba_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\nnamespace graphene { namespace chain {\n\nclass database;\n\n/**\n * fba_accumulator_object accumulates fees to be paid out via buyback or other FBA mechanism.\n */\n\nclass fba_accumulator_object : public graphene::db::abstract_object< fba_accumulator_object,\n                                         implementation_ids, impl_fba_accumulator_object_type >\n{\n   public:\n      share_type accumulated_fba_fees;\n      optional< asset_id_type > designated_asset;\n\n      bool is_configured( const database& db )const;\n};\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::fba_accumulator_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::fba_accumulator_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/fork_database.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/block.hpp>\n\n#include <graphene/chain/types.hpp>\n\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n\nnamespace graphene { namespace chain {\n   using boost::multi_index_container;\n   using namespace boost::multi_index;\n\n   struct fork_item\n   {\n      fork_item( signed_block d )\n      :num(d.block_num()),id(d.id()),data( std::move(d) ){}\n\n      block_id_type previous_id()const { return data.previous; }\n\n      weak_ptr< fork_item > prev;\n      uint32_t              num;    // initialized in ctor\n      block_id_type         id;\n      signed_block          data;\n\n      // contains witness block signing keys scheduled *after* the block has been applied\n      shared_ptr< vector< pair< witness_id_type, public_key_type > > > scheduled_witnesses;\n      uint64_t                                                         next_block_aslot = 0;\n      fc::time_point_sec                                               next_block_time;\n   };\n   typedef shared_ptr<fork_item> item_ptr;\n\n\n   /**\n    *  As long as blocks are pushed in order the fork\n    *  database will maintain a linked tree of all blocks\n    *  that branch from the start_block.  The tree will\n    *  have a maximum depth of 1024 blocks after which\n    *  the database will start lopping off forks.\n    *\n    *  Every time a block is pushed into the fork DB the\n    *  block with the highest block_num will be returned.\n    */\n   class fork_database\n   {\n      public:\n         typedef vector<item_ptr> branch_type;\n         /// The maximum number of blocks that may be skipped in an out-of-order push\n         const static int MAX_BLOCK_REORDERING = 1024;\n\n         fork_database();\n         void reset();\n\n         void                             start_block(signed_block b);\n         void                             remove(block_id_type b);\n         void                             set_head(shared_ptr<fork_item> h);\n         bool                             is_known_block(const block_id_type& id)const;\n         shared_ptr<fork_item>            fetch_block(const block_id_type& id)const;\n         vector<item_ptr>                 fetch_block_by_number(uint32_t n)const;\n\n         /**\n          *  @return the new head block ( the longest fork )\n          */\n         shared_ptr<fork_item>            push_block(const signed_block& b);\n         shared_ptr<fork_item>            head()const { return _head; }\n         void                             pop_block();\n\n         /**\n          *  Given two head blocks, return two branches of the fork graph that\n          *  end with a common ancestor (same prior block)\n          */\n         pair< branch_type, branch_type >  fetch_branch_from(block_id_type first,\n                                                             block_id_type second)const;\n\n         struct block_id;\n         struct block_num;\n         typedef multi_index_container<\n            item_ptr,\n            indexed_by<\n               hashed_unique<tag<block_id>, member<fork_item, block_id_type, &fork_item::id>, std::hash<fc::ripemd160>>,\n               ordered_non_unique<tag<block_num>, member<fork_item,uint32_t,&fork_item::num>>\n            >\n         > fork_multi_index_type;\n\n         void set_max_size( uint32_t s );\n\n      private:\n         /** @return a pointer to the newly pushed item */\n         void _push_block(const item_ptr& b );\n         void _push_next(const item_ptr& newly_inserted);\n\n         uint32_t                 _max_size = 1024;\n\n         fork_multi_index_type    _index;\n         shared_ptr<fork_item>    _head;\n   };\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/genesis_state.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/address.hpp>\n#include <graphene/protocol/chain_parameters.hpp>\n#include <graphene/chain/types.hpp>\n#include <graphene/chain/immutable_chain_parameters.hpp>\n\n#include <fc/crypto/sha256.hpp>\n\n#include <string>\n#include <vector>\n\nnamespace graphene { namespace chain {\nusing std::string;\nusing std::vector;\n\nstruct genesis_state_type {\n   struct initial_account_type {\n      initial_account_type(const string& name = string(),\n                           const public_key_type& owner_key = public_key_type(),\n                           const public_key_type& active_key = public_key_type(),\n                           bool is_lifetime_member = false)\n         : name(name),\n           owner_key(owner_key),\n           active_key(active_key == public_key_type()? owner_key : active_key),\n           is_lifetime_member(is_lifetime_member)\n      {}\n      string name;\n      public_key_type owner_key;\n      public_key_type active_key;\n      bool is_lifetime_member = false;\n   };\n   struct initial_asset_type {\n      struct initial_collateral_position {\n         address owner;\n         share_type collateral;\n         share_type debt;\n      };\n\n      string symbol;\n      string issuer_name;\n\n      string description;\n      uint8_t precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;\n\n      share_type max_supply;\n      share_type accumulated_fees;\n\n      bool is_bitasset = false;\n      vector<initial_collateral_position> collateral_records;\n   };\n   struct initial_balance_type {\n      address owner;\n      string asset_symbol;\n      share_type amount;\n   };\n   struct initial_vesting_balance_type {\n      address owner;\n      string asset_symbol;\n      share_type amount;\n      time_point_sec begin_timestamp;\n      uint32_t vesting_duration_seconds = 0;\n      share_type begin_balance;\n   };\n   struct initial_witness_type {\n      /// Must correspond to one of the initial accounts\n      string owner_name;\n      public_key_type block_signing_key;\n   };\n   struct initial_committee_member_type {\n      /// Must correspond to one of the initial accounts\n      string owner_name;\n   };\n   struct initial_worker_type {\n      /// Must correspond to one of the initial accounts\n      string owner_name;\n      share_type daily_pay;\n   };\n\n   time_point_sec                           initial_timestamp;\n   share_type                               max_core_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n   chain_parameters                         initial_parameters;\n   immutable_chain_parameters               immutable_parameters;\n   vector<initial_account_type>             initial_accounts;\n   vector<initial_asset_type>               initial_assets;\n   vector<initial_balance_type>             initial_balances;\n   vector<initial_vesting_balance_type>     initial_vesting_balances;\n   uint64_t                                 initial_active_witnesses = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT;\n   vector<initial_witness_type>             initial_witness_candidates;\n   vector<initial_committee_member_type>    initial_committee_candidates;\n   vector<initial_worker_type>              initial_worker_candidates;\n\n   /**\n    * Temporary, will be moved elsewhere.\n    */\n   chain_id_type                            initial_chain_id;\n\n   /**\n    * Get the chain_id corresponding to this genesis state.\n    *\n    * This is the SHA256 serialization of the genesis_state.\n    */\n   chain_id_type compute_chain_id() const;\n\n   /// Method to override initial witness signing keys for debug\n   void override_witness_signing_keys( const std::string& new_key );\n\n};\n\n} } // namespace graphene::chain\n\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_account_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_asset_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_asset_type::initial_collateral_position )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_balance_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_vesting_balance_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_witness_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_committee_member_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type::initial_worker_type )\nFC_REFLECT_TYPENAME( graphene::chain::genesis_state_type )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_account_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_asset_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_asset_type::initial_collateral_position )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_balance_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_vesting_balance_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_witness_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_committee_member_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type::initial_worker_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::genesis_state_type )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/get_config.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/variant_object.hpp>\n\nnamespace graphene { namespace chain {\n\nfc::variant_object get_config();\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/global_property_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/chain_parameters.hpp>\n#include <graphene/chain/types.hpp>\n#include <graphene/db/object.hpp>\n\nnamespace graphene { namespace chain {\n\n   /**\n    * @class global_property_object\n    * @brief Maintains global state information (committee_member list, current fees)\n    * @ingroup object\n    * @ingroup implementation\n    *\n    * This is an implementation detail. The values here are set by committee_members to tune the blockchain parameters.\n    */\n   class global_property_object : public graphene::db::abstract_object<global_property_object,\n                                            implementation_ids, impl_global_property_object_type>\n   {\n      public:\n         chain_parameters           parameters;\n         optional<chain_parameters> pending_parameters;\n\n         uint32_t                           next_available_vote_id = 0;\n         vector<committee_member_id_type>   active_committee_members; // updated once per maintenance interval\n         flat_set<witness_id_type>          active_witnesses; // updated once per maintenance interval\n         // n.b. witness scheduling is done by witness_schedule object\n   };\n\n   /**\n    * @class dynamic_global_property_object\n    * @brief Maintains global state information (committee_member list, current fees)\n    * @ingroup object\n    * @ingroup implementation\n    *\n    * This is an implementation detail. The values here are calculated during normal chain operations and reflect the\n    * current values of global blockchain properties.\n    */\n   class dynamic_global_property_object : public abstract_object<dynamic_global_property_object,\n                                                    implementation_ids, impl_dynamic_global_property_object_type>\n   {\n      public:\n         uint32_t          head_block_number = 0;\n         block_id_type     head_block_id;\n         time_point_sec    time;\n         witness_id_type   current_witness;\n         time_point_sec    next_maintenance_time;\n         time_point_sec    last_vote_tally_time;\n         time_point_sec    last_budget_time;\n         share_type        witness_budget;\n         share_type        total_pob;\n         share_type        total_inactive;\n         uint32_t          accounts_registered_this_interval = 0;\n         /**\n          *  Every time a block is missed this increases by\n          *  RECENTLY_MISSED_COUNT_INCREMENT,\n          *  every time a block is found it decreases by\n          *  RECENTLY_MISSED_COUNT_DECREMENT.  It is\n          *  never less than 0.\n          */\n         uint32_t          recently_missed_count = 0;\n\n         /**\n          * The current absolute slot number.  Equal to the total\n          * number of slots since genesis.  Also equal to the total\n          * number of missed slots plus head_block_number.\n          */\n         uint64_t                current_aslot = 0;\n\n         /**\n          * used to compute witness participation.\n          */\n         fc::uint128_t recent_slots_filled;\n\n         /**\n          * dynamic_flags specifies chain state properties that can be\n          * expressed in one bit.\n          */\n         uint32_t dynamic_flags = 0;\n\n         uint32_t last_irreversible_block_num = 0;\n\n         enum dynamic_flag_bits\n         {\n            /**\n             * If maintenance_flag is set, then the head block is a\n             * maintenance block.  This means\n             * get_time_slot(1) - head_block_time() will have a gap\n             * due to maintenance duration.\n             *\n             * This flag answers the question, \"Was maintenance\n             * performed in the last call to apply_block()?\"\n             */\n            maintenance_flag = 0x01\n         };\n   };\n}}\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::dynamic_global_property_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::global_property_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::dynamic_global_property_object )\nFC_REFLECT_TYPENAME( graphene::chain::global_property_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::dynamic_global_property_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::global_property_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/hardfork_visitor.hpp",
    "content": "/*\n * Copyright (c) 2019 Contributors\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/operations.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <fc/reflect/typelist.hpp>\n\n#include <type_traits>\n#include <functional>\n\nnamespace graphene { namespace chain {\n\n/**\n * @brief The hardfork_visitor struct checks whether a given operation type has been hardforked in or not\n *\n * This visitor can be invoked in several different ways, including operation::visit, typelist::runtime::dispatch, or\n * direct invocation by calling the visit() method passing an operation variant, narrow operation type, operation tag,\n * or templating on the narrow operation type\n */\nstruct hardfork_visitor {\n   using result_type = bool;\n   using first_unforked_op = protocol::custom_authority_create_operation;\n   using BSIP_40_ops = fc::typelist::list< protocol::custom_authority_create_operation,\n                                           protocol::custom_authority_update_operation,\n                                           protocol::custom_authority_delete_operation>;\n   using hf1604_ops = fc::typelist::list< protocol::limit_order_update_operation>;\n   using hf2103_ops = fc::typelist::list< protocol::ticket_create_operation,\n                                          protocol::ticket_update_operation>;\n   using liquidity_pool_ops = fc::typelist::list< protocol::liquidity_pool_create_operation,\n                                                  protocol::liquidity_pool_delete_operation,\n                                                  protocol::liquidity_pool_deposit_operation,\n                                                  protocol::liquidity_pool_withdraw_operation,\n                                                  protocol::liquidity_pool_exchange_operation >;\n   using liquidity_pool_update_op = fc::typelist::list< protocol::liquidity_pool_update_operation >;\n   using samet_fund_ops = fc::typelist::list< protocol::samet_fund_create_operation,\n                                              protocol::samet_fund_delete_operation,\n                                              protocol::samet_fund_update_operation,\n                                              protocol::samet_fund_borrow_operation,\n                                              protocol::samet_fund_repay_operation >;\n   using credit_offer_ops = fc::typelist::list< protocol::credit_offer_create_operation,\n                                                protocol::credit_offer_delete_operation,\n                                                protocol::credit_offer_update_operation,\n                                                protocol::credit_offer_accept_operation,\n                                                protocol::credit_deal_repay_operation,\n                                                protocol::credit_deal_expired_operation >;\n   using credit_deal_update_op = fc::typelist::list< protocol::credit_deal_update_operation >;\n\n   fc::time_point_sec now;\n\n   /// @note using head block time for all operations\n   explicit hardfork_visitor(const fc::time_point_sec& head_block_time) : now(head_block_time) {}\n\n   /// The real visitor implementations. Future operation types get added in here.\n   /// @{\n   template<typename Op>\n   std::enable_if_t<operation::tag<Op>::value < protocol::operation::tag<first_unforked_op>::value, bool>\n   visit() { return true; }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<BSIP_40_ops, Op>(), bool>\n   visit() { return HARDFORK_BSIP_40_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<hf1604_ops, Op>(), bool>\n   visit() { return HARDFORK_CORE_1604_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<hf2103_ops, Op>(), bool>\n   visit() { return HARDFORK_CORE_2103_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<liquidity_pool_ops, Op>(), bool>\n   visit() { return HARDFORK_LIQUIDITY_POOL_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<samet_fund_ops, Op>(), bool>\n   visit() { return HARDFORK_CORE_2351_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<credit_offer_ops, Op>(), bool>\n   visit() { return HARDFORK_CORE_2362_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<credit_deal_update_op, Op>(), bool>\n   visit() { return HARDFORK_CORE_2595_PASSED(now); }\n   template<typename Op>\n   std::enable_if_t<fc::typelist::contains<liquidity_pool_update_op, Op>(), bool>\n   visit() { return HARDFORK_CORE_2604_PASSED(now); }\n   /// @}\n\n   /// typelist::runtime::dispatch adaptor\n   template<class W, class Op=typename W::type>\n   std::enable_if_t<fc::typelist::contains<protocol::operation::list, Op>(), bool>\n   operator()(W) { return visit<Op>(); }\n   /// static_variant::visit adaptor\n   template<class Op>\n   std::enable_if_t<fc::typelist::contains<protocol::operation::list, Op>(), bool>\n   operator()(const Op&) { return visit<Op>(); }\n   /// Tag adaptor\n   bool visit(protocol::operation::tag_type tag) const {\n      return fc::typelist::runtime::dispatch(protocol::operation::list(), (size_t)tag, *this);\n   }\n   /// operation adaptor\n   bool visit(const protocol::operation& op) const {\n      return visit(op.which());\n   }\n};\n\n} } // namespace graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/htlc_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2018 jmjatlanta and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/htlc_object.hpp>\n\nnamespace graphene { \n   namespace chain {\n\n      class htlc_create_evaluator : public evaluator<htlc_create_evaluator>\n      {\n         public:\n    \t      typedef htlc_create_operation operation_type;\n\n    \t      void_result do_evaluate( const htlc_create_operation& o);\n    \t      object_id_type do_apply( const htlc_create_operation& o);\n      };\n\n      class htlc_redeem_evaluator : public evaluator<htlc_redeem_evaluator>\n      {\n         public:\n    \t      typedef htlc_redeem_operation operation_type;\n\n    \t      void_result do_evaluate( const htlc_redeem_operation& o);\n    \t      void_result do_apply( const htlc_redeem_operation& o);\n    \t      const htlc_object* htlc_obj = nullptr;\n      };\n\n      class htlc_extend_evaluator : public evaluator<htlc_extend_evaluator>\n      {\n         public:\n    \t      typedef htlc_extend_operation operation_type;\n\n    \t      void_result do_evaluate( const htlc_extend_operation& o);\n    \t      void_result do_apply( const htlc_extend_operation& o);\n    \t      const htlc_object* htlc_obj = nullptr;\n      };\n   } // namespace graphene\n} // namespace graphene\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/htlc_object.hpp",
    "content": "/*\n * Copyright (c) 2018 jmjatlanta and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/htlc.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace protocol;\n\n   /**\n    * @brief database object to store HTLCs\n    *\n    * This object is stored in the database while an HTLC is active. The HTLC will\n    * become inactive at expiration or when unlocked via the preimage.\n    */\n   class htlc_object : public graphene::db::abstract_object<htlc_object, protocol_ids, htlc_object_type>\n   {\n   public:\n      struct transfer_info\n      {\n         account_id_type from;\n         account_id_type to;\n         share_type amount;\n         asset_id_type asset_id;\n      };\n      struct condition_info\n      {\n         struct hash_lock_info\n         {\n            htlc_hash preimage_hash;\n            uint16_t preimage_size;\n         };\n         struct time_lock_info\n         {\n            fc::time_point_sec expiration;\n         };\n         hash_lock_info hash_lock;\n         time_lock_info time_lock;\n      };\n\n      transfer_info transfer;\n      condition_info conditions;\n      fc::optional<memo_data> memo;\n\n      /****\n       * Index helper for timelock\n       */\n      struct timelock_extractor {\n         using result_type = fc::time_point_sec;\n         const result_type& operator()(const htlc_object& o)const { return o.conditions.time_lock.expiration; }\n      };\n\n      /*****\n       * Index helper for from\n       */\n      struct from_extractor {\n         using result_type = account_id_type;\n         const result_type& operator()(const htlc_object& o)const { return o.transfer.from; }\n      };\n\n      /*****\n       * Index helper for to\n       */\n      struct to_extractor {\n         using result_type = account_id_type;\n         const result_type& operator()(const htlc_object& o)const { return o.transfer.to; }\n      };\n   };\n\n   struct by_from_id;\n   struct by_expiration;\n   struct by_to_id;\n   using htlc_object_multi_index_type = multi_index_container<\n         htlc_object,\n         indexed_by<\n            ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >,\n            ordered_unique< tag< by_expiration >,\n               composite_key< htlc_object,\n                  htlc_object::timelock_extractor,\n                  member< object, object_id_type, &object::id > > >,\n            ordered_unique< tag< by_from_id >,\n               composite_key< htlc_object,\n                  htlc_object::from_extractor,\n                  member< object, object_id_type, &object::id > > >,\n            ordered_unique< tag< by_to_id >,\n               composite_key< htlc_object,\n                  htlc_object::to_extractor,\n                  member< object, object_id_type, &object::id > > >\n         >\n   >;\n\n   using htlc_index = generic_index< htlc_object, htlc_object_multi_index_type >;\n\n} } // namespace graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::htlc_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::htlc_object::condition_info::hash_lock_info )\nFC_REFLECT_TYPENAME( graphene::chain::htlc_object::condition_info::time_lock_info )\nFC_REFLECT_TYPENAME( graphene::chain::htlc_object::condition_info )\nFC_REFLECT_TYPENAME( graphene::chain::htlc_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::htlc_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/immutable_chain_parameters.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/config.hpp>\n#include <graphene/chain/types.hpp>\n\nnamespace graphene { namespace chain {\n\nstruct immutable_chain_parameters\n{\n   uint16_t min_committee_member_count = GRAPHENE_DEFAULT_MIN_COMMITTEE_MEMBER_COUNT;\n   uint16_t min_witness_count = GRAPHENE_DEFAULT_MIN_WITNESS_COUNT;\n   uint32_t num_special_accounts = 0;\n   uint32_t num_special_assets = 0;\n};\n\n} } // graphene::chain\n\nFC_REFLECT_TYPENAME( graphene::chain::immutable_chain_parameters )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::immutable_chain_parameters )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/impacted.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/container/flat.hpp>\n#include <graphene/protocol/operations.hpp>\n#include <graphene/protocol/transaction.hpp>\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid operation_get_impacted_accounts( const graphene::chain::operation& op,\n                                      fc::flat_set<graphene::chain::account_id_type>& result,\n                                      bool ignore_custom_operation_required_auths );\n\nvoid transaction_get_impacted_accounts( const graphene::chain::transaction& tx,\n                                        fc::flat_set<graphene::chain::account_id_type>& result,\n                                        bool ignore_custom_operation_required_auths );\n\n} } // graphene::app\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/internal_exceptions.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/exceptions.hpp>\n\n#define GRAPHENE_DECLARE_INTERNAL_EXCEPTION( exc_name, seqnum, msg )  \\\n   FC_DECLARE_DERIVED_EXCEPTION(                                      \\\n      internal_ ## exc_name,                                          \\\n      graphene::chain::internal_exception,                            \\\n      3990000 + seqnum                                                \\\n      )\n\n#define GRAPHENE_IMPLEMENT_INTERNAL_EXCEPTION( exc_name, seqnum, msg )  \\\n   FC_IMPLEMENT_DERIVED_EXCEPTION(                                      \\\n      internal_ ## exc_name,                                          \\\n      graphene::chain::internal_exception,                            \\\n      3990000 + seqnum,                                               \\\n      msg                                                             \\\n      )\n\nnamespace graphene { namespace chain {\n\nFC_DECLARE_DERIVED_EXCEPTION( internal_exception, graphene::chain::chain_exception, 3990000 )\n\nGRAPHENE_DECLARE_INTERNAL_EXCEPTION( verify_auth_max_auth_exceeded, 1, \"Exceeds max authority fan-out\" )\nGRAPHENE_DECLARE_INTERNAL_EXCEPTION( verify_auth_account_not_found, 2, \"Auth account not found\" )\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/is_authorized_asset.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\nnamespace graphene { namespace chain {\n\nclass account_object;\nclass asset_object;\nclass database;\n\nnamespace detail {\n\nbool _is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj);\n\n}\n\n/**\n * @return true if the account is whitelisted and not blacklisted to transact in the provided asset; false\n * otherwise.\n */\n\ninline bool is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj)\n{\n   bool fast_check = ( ( 0 == (asset_obj.options.flags & white_list) ) && !(acct.allowed_assets.valid()) );\n\n   if( fast_check )\n      return true;\n\n   bool slow_check = detail::_is_authorized_asset( d, acct, asset_obj );\n   return slow_check;\n}\n\n} }\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/liquidity_pool_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n\n#include <graphene/protocol/liquidity_pool.hpp>\n\nnamespace graphene { namespace chain {\n\n   class asset_object;\n   class asset_dynamic_data_object;\n   class liquidity_pool_object;\n\n   class liquidity_pool_create_evaluator : public evaluator<liquidity_pool_create_evaluator>\n   {\n      public:\n         using operation_type = liquidity_pool_create_operation;\n\n         void_result do_evaluate( const liquidity_pool_create_operation& op );\n         generic_operation_result do_apply( const liquidity_pool_create_operation& op );\n\n      private:\n         const asset_object* _share_asset = nullptr;\n   };\n\n   class liquidity_pool_delete_evaluator : public evaluator<liquidity_pool_delete_evaluator>\n   {\n      public:\n         using operation_type = liquidity_pool_delete_operation;\n\n         void_result do_evaluate( const liquidity_pool_delete_operation& op );\n         generic_operation_result do_apply( const liquidity_pool_delete_operation& op ) const;\n\n      private:\n         const liquidity_pool_object* _pool = nullptr;\n         const asset_object* _share_asset = nullptr;\n   };\n\n   class liquidity_pool_update_evaluator : public evaluator<liquidity_pool_update_evaluator>\n   {\n      public:\n         using operation_type = liquidity_pool_update_operation;\n\n         void_result do_evaluate( const liquidity_pool_update_operation& op );\n         void_result do_apply( const liquidity_pool_update_operation& op ) const;\n\n      private:\n         const liquidity_pool_object* _pool = nullptr;\n   };\n\n   class liquidity_pool_deposit_evaluator : public evaluator<liquidity_pool_deposit_evaluator>\n   {\n      public:\n         using operation_type = liquidity_pool_deposit_operation;\n\n         void_result do_evaluate( const liquidity_pool_deposit_operation& op );\n         generic_exchange_operation_result do_apply( const liquidity_pool_deposit_operation& op );\n\n      private:\n         const liquidity_pool_object* _pool = nullptr;\n         const asset_dynamic_data_object* _share_asset_dyn_data = nullptr;\n         asset _account_receives;\n         asset _pool_receives_a;\n         asset _pool_receives_b;\n   };\n\n   class liquidity_pool_withdraw_evaluator : public evaluator<liquidity_pool_withdraw_evaluator>\n   {\n      public:\n         using operation_type = liquidity_pool_withdraw_operation;\n\n         void_result do_evaluate( const liquidity_pool_withdraw_operation& op );\n         generic_exchange_operation_result do_apply( const liquidity_pool_withdraw_operation& op );\n\n      private:\n         const liquidity_pool_object* _pool = nullptr;\n         const asset_dynamic_data_object* _share_asset_dyn_data = nullptr;\n         asset _pool_pays_a;\n         asset _pool_pays_b;\n         asset _fee_a;\n         asset _fee_b;\n   };\n\n   class liquidity_pool_exchange_evaluator : public evaluator<liquidity_pool_exchange_evaluator>\n   {\n      public:\n         using operation_type = liquidity_pool_exchange_operation;\n\n         void_result do_evaluate( const liquidity_pool_exchange_operation& op );\n         generic_exchange_operation_result do_apply( const liquidity_pool_exchange_operation& op );\n\n      private:\n         const liquidity_pool_object* _pool = nullptr;\n         const asset_object* _pool_pays_asset = nullptr;\n         const asset_object* _pool_receives_asset = nullptr;\n         asset _pool_pays;\n         asset _pool_receives;\n         asset _account_receives;\n         asset _maker_market_fee;\n         asset _taker_market_fee;\n         asset _pool_taker_fee;\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/liquidity_pool_object.hpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/liquidity_pool.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\nusing namespace graphene::db;\n\n/**\n *  @brief A liquidity pool\n *  @ingroup object\n *  @ingroup protocol\n *\n */\nclass liquidity_pool_object : public abstract_object<liquidity_pool_object, protocol_ids, liquidity_pool_object_type>\n{\n   public:\n      asset_id_type   asset_a;                     ///< Type of the first asset in the pool\n      asset_id_type   asset_b;                     ///< Type of the second asset in the pool\n      share_type      balance_a;                   ///< The balance of the first asset in the pool\n      share_type      balance_b;                   ///< The balance of the second asset in the pool\n      asset_id_type   share_asset;                 ///< Type of the share asset aka the LP token\n      uint16_t        taker_fee_percent = 0;       ///< Taker fee percent\n      uint16_t        withdrawal_fee_percent = 0;  ///< Withdrawal fee percent\n      fc::uint128_t   virtual_value = 0;           ///< Virtual value of the pool\n\n      void update_virtual_value()\n      {\n         virtual_value = fc::uint128_t( balance_a.value ) * balance_b.value;\n      }\n};\n\nstruct by_share_asset;\nstruct by_asset_a;\nstruct by_asset_b;\nstruct by_asset_ab;\n\n/**\n* @ingroup object_index\n*/\ntypedef multi_index_container<\n   liquidity_pool_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_share_asset>,\n                      member< liquidity_pool_object, asset_id_type, &liquidity_pool_object::share_asset > >,\n      ordered_unique< tag<by_asset_a>,\n         composite_key< liquidity_pool_object,\n            member< liquidity_pool_object, asset_id_type, &liquidity_pool_object::asset_a >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_asset_b>,\n         composite_key< liquidity_pool_object,\n            member< liquidity_pool_object, asset_id_type, &liquidity_pool_object::asset_b >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_asset_ab>,\n         composite_key< liquidity_pool_object,\n            member< liquidity_pool_object, asset_id_type, &liquidity_pool_object::asset_a >,\n            member< liquidity_pool_object, asset_id_type, &liquidity_pool_object::asset_b >,\n            member< object, object_id_type, &object::id>\n         >\n      >\n   >\n> liquidity_pool_multi_index_type;\n\n/**\n* @ingroup object_index\n*/\ntypedef generic_index<liquidity_pool_object, liquidity_pool_multi_index_type> liquidity_pool_index;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE( graphene::chain::liquidity_pool_object )\n\n// Note: this is left here but not moved to a cpp file due to the extended_liquidity_pool_object struct in API.\nFC_REFLECT_DERIVED( graphene::chain::liquidity_pool_object, (graphene::db::object),\n                    (asset_a)\n                    (asset_b)\n                    (balance_a)\n                    (balance_b)\n                    (share_asset)\n                    (taker_fee_percent)\n                    (withdrawal_fee_percent)\n                    (virtual_value)\n                  )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::liquidity_pool_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/market_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/types.hpp>\n\nnamespace graphene { namespace chain {\n\n   class account_object;\n   class asset_object;\n   class asset_bitasset_data_object;\n   class call_order_object;\n   class limit_order_object;\n   class collateral_bid_object;\n\n   class limit_order_create_evaluator : public evaluator<limit_order_create_evaluator>\n   {\n      public:\n         using operation_type = limit_order_create_operation;\n\n         void_result do_evaluate( const limit_order_create_operation& o );\n         object_id_type do_apply( const limit_order_create_operation& o ) const;\n\n         /** override the default behavior defined by generic_evaluator\n          */\n         void convert_fee() override;\n\n         /** override the default behavior defined by generic_evaluator which is to\n          * post the fee to fee_paying_account_stats.pending_fees\n          */\n         void pay_fee() override;\n\n      private:\n         share_type                          _deferred_fee  = 0;\n         asset                               _deferred_paid_fee;\n         const account_object*               _seller        = nullptr;\n         const asset_object*                 _sell_asset    = nullptr;\n         const asset_object*                 _receive_asset = nullptr;\n   };\n\n   class limit_order_update_evaluator : public evaluator<limit_order_update_evaluator>\n   {\n   public:\n       using operation_type = limit_order_update_operation;\n\n       void_result do_evaluate(const limit_order_update_operation& o);\n       void_result do_apply(const limit_order_update_operation& o);\n\n       /** override the default behavior defined by generic_evaluator\n        */\n       void convert_fee() override;\n\n       /** override the default behavior defined by generic_evaluator which is to\n        * post the fee to fee_paying_account_stats.pending_fees\n        */\n       void pay_fee() override;\n\n   private:\n       void process_deferred_fee();\n       /// Check if the linked take profit order is still compatible with the current order after update\n       bool is_linked_tp_order_compatible( const limit_order_update_operation& o ) const;\n\n       share_type                _deferred_fee;\n       asset                     _deferred_paid_fee;\n       const limit_order_object* _order = nullptr;\n       const account_statistics_object* _seller_acc_stats = nullptr;\n   };\n\n   class limit_order_cancel_evaluator : public evaluator<limit_order_cancel_evaluator>\n   {\n      public:\n         using operation_type = limit_order_cancel_operation;\n\n         void_result do_evaluate( const limit_order_cancel_operation& o );\n         asset do_apply( const limit_order_cancel_operation& o ) const;\n\n      private:\n         const limit_order_object* _order = nullptr;\n   };\n\n   class call_order_update_evaluator : public evaluator<call_order_update_evaluator>\n   {\n      public:\n         using operation_type = call_order_update_operation;\n\n         void_result do_evaluate( const call_order_update_operation& o );\n         object_id_type do_apply( const call_order_update_operation& o );\n\n      private:\n         bool _closing_order = false;\n         const asset_object* _debt_asset = nullptr;\n         const call_order_object* call_ptr = nullptr;\n         share_type new_debt;\n         share_type new_collateral;\n         const asset_bitasset_data_object* _bitasset_data = nullptr;\n         const asset_dynamic_data_object*  _dynamic_data_obj = nullptr;\n   };\n\n   class bid_collateral_evaluator : public evaluator<bid_collateral_evaluator>\n   {\n      public:\n         using operation_type = bid_collateral_operation;\n\n         void_result do_evaluate( const bid_collateral_operation& o );\n         void_result do_apply( const bid_collateral_operation& o ) const;\n\n      private:\n         const asset_object* _debt_asset = nullptr;\n         const asset_bitasset_data_object* _bitasset_data = nullptr;\n         const collateral_bid_object* _bid = nullptr;\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/market_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/market.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\nusing namespace graphene::db;\n\n/**\n *  @brief an offer to sell an amount of an asset at a specified exchange rate by a certain time\n *  @ingroup object\n *  @ingroup protocol\n *  @ingroup market\n *\n *  The objects are indexed by @ref expiration and are automatically deleted on the first block after expiration.\n */\nclass limit_order_object : public abstract_object<limit_order_object, protocol_ids, limit_order_object_type>\n{\n   public:\n      time_point_sec   expiration; ///< When this limit order will expire\n      account_id_type  seller; ///< Who is selling\n      share_type       for_sale; ///< The amount for sale, asset id is sell_price.base.asset_id\n      price            sell_price; ///< The seller's asking price\n      fc::uint128_t    filled_amount = 0; ///< The amount that has been sold, asset id is sell_price.base.asset_id\n      share_type       deferred_fee; ///< fee converted to CORE\n      asset            deferred_paid_fee; ///< originally paid fee\n      bool             is_settled_debt = false; ///< Whether this order is an individual settlement fund\n\n      /// Automatic actions when the limit order is filled or partially filled\n      vector< limit_order_auto_action > on_fill;\n\n      /// ID of the take profit limit order linked to this limit order\n      optional<limit_order_id_type> take_profit_order_id;\n\n      /// Returns the configured automatic action that will create a take profit order when this limit order is filled\n      const create_take_profit_order_action& get_take_profit_action() const\n      {\n         FC_ASSERT( !on_fill.empty() ); // Normally it should not fail // GCOVR_EXCL_LINE\n         return on_fill.front().get<create_take_profit_order_action>();\n      }\n\n      pair<asset_id_type,asset_id_type> get_market()const\n      {\n         auto tmp = std::make_pair( sell_price.base.asset_id, sell_price.quote.asset_id );\n         if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );\n         return tmp;\n      }\n\n      asset amount_for_sale()const   { return asset( for_sale, sell_price.base.asset_id ); }\n      asset amount_to_receive()const { return amount_for_sale() * sell_price; }\n      asset_id_type sell_asset_id()const    { return sell_price.base.asset_id;  }\n      asset_id_type receive_asset_id()const { return sell_price.quote.asset_id; }\n};\n\nstruct by_price;\nstruct by_expiration;\nstruct by_account;\nstruct by_account_price;\nstruct by_is_settled_debt;\ntypedef multi_index_container<\n   limit_order_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_expiration>,\n         composite_key< limit_order_object,\n            member< limit_order_object, time_point_sec, &limit_order_object::expiration>,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_price>,\n         composite_key< limit_order_object,\n            member< limit_order_object, price, &limit_order_object::sell_price>,\n            member< object, object_id_type, &object::id>\n         >,\n         composite_key_compare< std::greater<price>, std::less<object_id_type> >\n      >,\n      ordered_unique< tag<by_is_settled_debt>,\n         composite_key< limit_order_object,\n            member< limit_order_object, bool, &limit_order_object::is_settled_debt >,\n            const_mem_fun< limit_order_object, asset_id_type, &limit_order_object::receive_asset_id >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      // index used by APIs\n      ordered_unique< tag<by_account>,\n         composite_key< limit_order_object,\n            member<limit_order_object, account_id_type, &limit_order_object::seller>,\n            member<object, object_id_type, &object::id>\n         >\n      >,\n      // index used by APIs\n      ordered_unique< tag<by_account_price>,\n         composite_key< limit_order_object,\n            member<limit_order_object, account_id_type, &limit_order_object::seller>,\n            member<limit_order_object, price, &limit_order_object::sell_price>,\n            member<object, object_id_type, &object::id>\n         >,\n         composite_key_compare<std::less<account_id_type>, std::greater<price>, std::less<object_id_type>>\n      >\n   >\n> limit_order_multi_index_type;\n\ntypedef generic_index<limit_order_object, limit_order_multi_index_type> limit_order_index;\n\n/**\n * @class call_order_object\n * @brief tracks debt and call price information\n *\n * There should only be one call_order_object per asset pair per account and\n * they will all have the same call price.\n */\nclass call_order_object : public abstract_object<call_order_object, protocol_ids,  call_order_object_type>\n{\n   public:\n      asset get_collateral()const { return asset( collateral, call_price.base.asset_id ); }\n      asset get_debt()const { return asset( debt, debt_type() ); }\n      asset amount_to_receive()const { return get_debt(); }\n      asset_id_type debt_type()const { return call_price.quote.asset_id; }\n      asset_id_type collateral_type()const { return call_price.base.asset_id; }\n      price collateralization()const { return get_collateral() / get_debt(); }\n\n      account_id_type  borrower;\n      share_type       collateral;  ///< call_price.base.asset_id, access via get_collateral\n      share_type       debt;        ///< call_price.quote.asset_id, access via get_debt\n      price            call_price;  ///< Collateral / Debt\n\n      optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call\n\n      pair<asset_id_type,asset_id_type> get_market()const\n      {\n         auto tmp = std::make_pair( call_price.base.asset_id, call_price.quote.asset_id );\n         if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );\n         return tmp;\n      }\n\n      /**\n       *  Calculate maximum quantity of debt to cover to satisfy @ref target_collateral_ratio.\n       *\n       *  @param match_price the matching price if this call order is margin called\n       *  @param feed_price median settlement price of debt asset\n       *  @param maintenance_collateral_ratio median maintenance collateral ratio of debt asset\n       *  @param maintenance_collateralization maintenance collateralization of debt asset,\n       *                                       should only be valid after core-1270 hard fork\n       *  @return maximum amount of debt that can be called\n       */\n      share_type get_max_debt_to_cover( price match_price,\n                                        price feed_price,\n                                        const uint16_t maintenance_collateral_ratio,\n                                        const optional<price>& maintenance_collateralization = optional<price>()\n                                      )const;\n};\n\n/**\n *  @brief tracks bitassets scheduled for force settlement at some point in the future.\n *\n *  On the @ref settlement_date the @ref balance will be converted to the collateral asset\n *  and paid to @ref owner and then this object will be deleted.\n */\nclass force_settlement_object : public abstract_object<force_settlement_object,\n                                          protocol_ids, force_settlement_object_type>\n{\n   public:\n      account_id_type   owner;\n      asset             balance;\n      time_point_sec    settlement_date;\n\n      asset_id_type settlement_asset_id()const\n      { return balance.asset_id; }\n};\n\n/**\n * @class collateral_bid_object\n * @brief bids of collateral for debt after a black swan\n *\n * There should only be one collateral_bid_object per asset per account, and\n * only for smartcoin assets that have a global settlement_price.\n */\nclass collateral_bid_object : public abstract_object<collateral_bid_object,\n                                        implementation_ids, impl_collateral_bid_object_type>\n{\n   public:\n      asset get_additional_collateral()const { return inv_swan_price.base; }\n      asset get_debt_covered()const { return inv_swan_price.quote; }\n      asset_id_type debt_type()const { return inv_swan_price.quote.asset_id; }\n\n      account_id_type  bidder;\n      price            inv_swan_price;  // Collateral / Debt\n};\n\nstruct by_collateral;\nstruct by_account;\nstruct by_price;\ntypedef multi_index_container<\n   call_order_object,\n   indexed_by<\n      ordered_unique< tag<by_id>,\n         member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_price>,\n         composite_key< call_order_object,\n            member< call_order_object, price, &call_order_object::call_price>,\n            member< object, object_id_type, &object::id>\n         >,\n         composite_key_compare< std::less<price>, std::less<object_id_type> >\n      >,\n      ordered_unique< tag<by_account>,\n         composite_key< call_order_object,\n            member< call_order_object, account_id_type, &call_order_object::borrower >,\n            const_mem_fun< call_order_object, asset_id_type, &call_order_object::debt_type>\n         >\n      >,\n      ordered_unique< tag<by_collateral>,\n         composite_key< call_order_object,\n            const_mem_fun< call_order_object, price, &call_order_object::collateralization >,\n            member< object, object_id_type, &object::id >\n         >\n      >\n   >\n> call_order_multi_index_type;\n\nstruct by_expiration;\ntypedef multi_index_container<\n   force_settlement_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_account>,\n         composite_key< force_settlement_object,\n            member<force_settlement_object, account_id_type, &force_settlement_object::owner>,\n            member< object, object_id_type, &object::id >\n         >\n      >,\n      ordered_unique< tag<by_expiration>,\n         composite_key< force_settlement_object,\n            const_mem_fun<force_settlement_object, asset_id_type, &force_settlement_object::settlement_asset_id>,\n            member<force_settlement_object, time_point_sec, &force_settlement_object::settlement_date>,\n            member< object, object_id_type, &object::id >\n         >\n      >\n   >\n> force_settlement_object_multi_index_type;\n\ntypedef multi_index_container<\n   collateral_bid_object,\n   indexed_by<\n      ordered_unique< tag<by_id>,\n         member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_account>,\n         composite_key< collateral_bid_object,\n            const_mem_fun< collateral_bid_object, asset_id_type, &collateral_bid_object::debt_type>,\n            member< collateral_bid_object, account_id_type, &collateral_bid_object::bidder>\n         >\n      >,\n      ordered_unique< tag<by_price>,\n         composite_key< collateral_bid_object,\n            const_mem_fun< collateral_bid_object, asset_id_type, &collateral_bid_object::debt_type>,\n            member< collateral_bid_object, price, &collateral_bid_object::inv_swan_price >,\n            member< object, object_id_type, &object::id >\n         >,\n         composite_key_compare< std::less<asset_id_type>, std::greater<price>, std::less<object_id_type> >\n      >\n   >\n> collateral_bid_object_multi_index_type;\n\ntypedef generic_index<call_order_object, call_order_multi_index_type>                      call_order_index;\ntypedef generic_index<force_settlement_object, force_settlement_object_multi_index_type>   force_settlement_index;\ntypedef generic_index<collateral_bid_object, collateral_bid_object_multi_index_type>       collateral_bid_index;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::limit_order_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::call_order_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::force_settlement_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::collateral_bid_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::limit_order_object )\nFC_REFLECT_TYPENAME( graphene::chain::call_order_object )\nFC_REFLECT_TYPENAME( graphene::chain::force_settlement_object )\nFC_REFLECT_TYPENAME( graphene::chain::collateral_bid_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::limit_order_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::call_order_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::force_settlement_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::collateral_bid_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/node_property_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/db/object.hpp>\n\nnamespace graphene { namespace chain {\n\n   /**\n    * @brief Contains per-node database configuration.\n    *\n    *  Transactions are evaluated differently based on per-node state.\n    *  Settings here may change based on whether the node is syncing or up-to-date.\n    *  Or whether the node is a witness node. Or if we're processing a\n    *  transaction in a witness-signed block vs. a fresh transaction\n    *  from the p2p network.  Or configuration-specified tradeoffs of\n    *  performance/hardfork resilience vs. paranoia.\n    */\n   class node_property_object\n   {\n      public:\n         node_property_object(){}\n         ~node_property_object(){}\n\n         uint32_t skip_flags = 0;\n         std::map< block_id_type, std::vector< fc::variant_object > > debug_updates;\n   };\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/operation_history_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/operations.hpp>\n#include <graphene/db/object.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\n   /**\n    * @brief tracks the history of all logical operations on blockchain state\n    * @ingroup object\n    * @ingroup implementation\n    *\n    *  All operations and virtual operations result in the creation of an\n    *  operation_history_object that is maintained on disk as a stack.  Each\n    *  real or virtual operation is assigned a unique ID / sequence number that\n    *  it can be referenced by.\n    *\n    *  @note  by default these objects are not tracked, the account_history_plugin must\n    *  be loaded fore these objects to be maintained.\n    *\n    *  @note  this object is READ ONLY it can never be modified\n    */\n   class operation_history_object : public abstract_object<operation_history_object,\n                                              protocol_ids, operation_history_object_type>\n   {\n      public:\n         explicit operation_history_object( const operation& o ):op(o){}\n         operation_history_object() = default;\n         operation_history_object( const operation& o, uint32_t bn, uint16_t tib, uint16_t oit, uint32_t vo, bool iv,\n                                   const time_point_sec& bt )\n         : op(o), block_num(bn), trx_in_block(tib), op_in_trx(oit), virtual_op(vo), is_virtual(iv), block_time(bt) {}\n\n         operation         op;\n         operation_result  result;\n         /** the block that caused this operation */\n         uint32_t          block_num = 0;\n         /** the transaction in the block */\n         uint16_t          trx_in_block = 0;\n         /** the operation within the transaction */\n         uint16_t          op_in_trx = 0;\n         /** any virtual operations implied by operation in block */\n         uint32_t          virtual_op = 0;\n         /** Whether this is a virtual operation */\n         bool              is_virtual = false;\n         /** The timestamp of the block that caused this operation */\n         time_point_sec    block_time;\n   };\n\n   /**\n    *  @brief a node in a linked list of operation_history_objects\n    *  @ingroup implementation\n    *  @ingroup object\n    *\n    *  Account history is important for users and wallets even though it is\n    *  not part of \"core validation\".   Account history is maintained as\n    *  a linked list stored on disk in a stack.  Each account will point to the\n    *  most recent account history object by ID.  When a new operation relativent\n    *  to that account is processed a new account history object is allcoated at\n    *  the end of the stack and intialized to point to the prior object.\n    *\n    *  This data is never accessed as part of chain validation and therefore\n    *  can be kept on disk as a memory mapped file.  Using a memory mapped file\n    *  will help the operating system better manage / cache / page files and\n    *  also accelerates load time.\n    *\n    *  When the transaction history for a particular account is requested the\n    *  linked list can be traversed with relatively effecient disk access because\n    *  of the use of a memory mapped stack.\n    */\n   class account_history_object :  public abstract_object<account_history_object,\n                                             implementation_ids, impl_account_history_object_type>\n   {\n      public:\n         account_id_type                      account; /// the account this operation applies to\n         operation_history_id_type            operation_id;\n         uint64_t                             sequence = 0; /// the operation position within the given account\n         account_history_id_type              next;\n   };\n\n   struct by_block;\n   struct by_time;\n\n   using operation_history_mlti_idx_type = multi_index_container<\n      operation_history_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_unique< tag<by_block>,\n            composite_key< operation_history_object,\n               member< operation_history_object, uint32_t, &operation_history_object::block_num>,\n               member< operation_history_object, uint16_t, &operation_history_object::trx_in_block>,\n               member< operation_history_object, uint16_t, &operation_history_object::op_in_trx>,\n               member< operation_history_object, uint32_t, &operation_history_object::virtual_op>\n            >\n         >,\n         ordered_unique< tag<by_time>,\n            composite_key< operation_history_object,\n               member< operation_history_object, time_point_sec, &operation_history_object::block_time>,\n               member< object, object_id_type, &object::id >\n            >,\n            composite_key_compare<\n               std::greater< time_point_sec >,\n               std::greater< object_id_type >\n            >\n         >\n      >\n   >;\n\n   using operation_history_index = generic_index< operation_history_object, operation_history_mlti_idx_type >;\n\n   struct by_seq;\n   struct by_op;\n   struct by_opid;\n\n   using account_history_multi_idx_type = multi_index_container<\n      account_history_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_unique< tag<by_seq>,\n            composite_key< account_history_object,\n               member< account_history_object, account_id_type, &account_history_object::account>,\n               member< account_history_object, uint64_t, &account_history_object::sequence>\n            >\n         >,\n         ordered_unique< tag<by_op>,\n            composite_key< account_history_object,\n               member< account_history_object, account_id_type, &account_history_object::account>,\n               member< account_history_object, operation_history_id_type, &account_history_object::operation_id>\n            >,\n            composite_key_compare<\n               std::less< account_id_type >,\n               std::greater< operation_history_id_type >\n            >\n         >,\n         ordered_non_unique< tag<by_opid>,\n            member< account_history_object, operation_history_id_type, &account_history_object::operation_id>\n         >\n      >\n   >;\n\n   using account_history_index = generic_index< account_history_object, account_history_multi_idx_type >;\n\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::operation_history_object)\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::account_history_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::operation_history_object )\nFC_REFLECT_TYPENAME( graphene::chain::account_history_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::operation_history_object )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::account_history_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/proposal_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015-2018 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/evaluator.hpp>\n\nnamespace graphene { namespace chain {\n\n   class hardfork_visitor_1479\n   {\n   public:\n      typedef void result_type;\n\n      uint64_t max_update_instance = 0;\n      uint64_t nested_update_count = 0;\n\n      template<typename T>\n      void operator()(const T &v) const {}\n\n      void operator()(const proposal_update_operation &v);\n\n      void operator()(const proposal_delete_operation &v);\n\n      // loop and self visit in proposals\n      void operator()(const graphene::chain::proposal_create_operation &v);\n   };\n\n   class proposal_create_evaluator : public evaluator<proposal_create_evaluator>\n   {\n      public:\n         typedef proposal_create_operation operation_type;\n\n         void_result do_evaluate( const proposal_create_operation& o );\n         object_id_type do_apply( const proposal_create_operation& o );\n\n         transaction _proposed_trx;\n         flat_set<account_id_type> _required_active_auths;\n         flat_set<account_id_type> _required_owner_auths;\n\n         hardfork_visitor_1479 vtor_1479;\n   };\n\n   class proposal_update_evaluator : public evaluator<proposal_update_evaluator>\n   {\n      public:\n         typedef proposal_update_operation operation_type;\n\n         void_result do_evaluate( const proposal_update_operation& o );\n         void_result do_apply( const proposal_update_operation& o );\n\n         const proposal_object* _proposal = nullptr;\n         processed_transaction _processed_transaction;\n         bool _executed_proposal = false;\n         bool _proposal_failed = false;\n   };\n\n   class proposal_delete_evaluator : public evaluator<proposal_delete_evaluator>\n   {\n      public:\n         typedef proposal_delete_operation operation_type;\n\n         void_result do_evaluate( const proposal_delete_operation& o );\n         void_result do_apply(const proposal_delete_operation&);\n\n         const proposal_object* _proposal = nullptr;\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/proposal_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n#include <graphene/protocol/transaction.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n   class database;\n\n/**\n *  @brief tracks the approval of a partially approved transaction\n *  @ingroup object\n *  @ingroup protocol\n */\nclass proposal_object : public abstract_object<proposal_object, protocol_ids, proposal_object_type>\n{\n   public:\n      time_point_sec                expiration_time;\n      optional<time_point_sec>      review_period_time;\n      transaction                   proposed_transaction;\n      flat_set<account_id_type>     required_active_approvals;\n      flat_set<account_id_type>     available_active_approvals;\n      flat_set<account_id_type>     required_owner_approvals;\n      flat_set<account_id_type>     available_owner_approvals;\n      flat_set<public_key_type>     available_key_approvals;\n      account_id_type               proposer;\n      std::string                   fail_reason;\n\n      bool is_authorized_to_execute(database& db) const;\n};\n\n/**\n *  @brief tracks all of the proposal objects that requrie approval of\n *  an individual account.\n *\n *  @ingroup object\n *  @ingroup protocol\n *\n *  This is a secondary index on the proposal_index\n *\n *  @note the set of required approvals is constant\n */\nclass required_approval_index : public secondary_index\n{\n   public:\n      virtual void object_inserted( const object& obj ) override;\n      virtual void object_removed( const object& obj ) override;\n      virtual void about_to_modify( const object& before ) override;\n      virtual void object_modified( const object& after  ) override;\n\n      map<account_id_type, set<proposal_id_type> > _account_to_proposals;\n\n   private:\n      void remove( account_id_type a, proposal_id_type p );\n      void insert_or_remove_delta( proposal_id_type p, const flat_set<account_id_type>& before,\n                                   const flat_set<account_id_type>& after );\n      flat_set<account_id_type> available_active_before_modify;\n      flat_set<account_id_type> available_owner_before_modify;\n};\n\nstruct by_expiration{};\ntypedef boost::multi_index_container<\n   proposal_object,\n   indexed_by<\n      ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >,\n      ordered_unique<tag<by_expiration>,\n         composite_key<proposal_object,\n            member<proposal_object, time_point_sec, &proposal_object::expiration_time>,\n            member< object, object_id_type, &object::id >\n         >\n      >\n   >\n> proposal_multi_index_container;\ntypedef generic_index<proposal_object, proposal_multi_index_container> proposal_index;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::proposal_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::proposal_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::proposal_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n\n#include <graphene/protocol/samet_fund.hpp>\n\nnamespace graphene { namespace chain {\n\n   class samet_fund_object;\n\n   class samet_fund_create_evaluator : public evaluator<samet_fund_create_evaluator>\n   {\n      public:\n         using operation_type = samet_fund_create_operation;\n\n         void_result do_evaluate( const samet_fund_create_operation& op ) const;\n         object_id_type do_apply( const samet_fund_create_operation& op ) const;\n   };\n\n   class samet_fund_delete_evaluator : public evaluator<samet_fund_delete_evaluator>\n   {\n      public:\n         using operation_type = samet_fund_delete_operation;\n\n         void_result do_evaluate( const samet_fund_delete_operation& op );\n         asset do_apply( const samet_fund_delete_operation& op ) const;\n\n         const samet_fund_object* _fund = nullptr;\n   };\n\n   class samet_fund_update_evaluator : public evaluator<samet_fund_update_evaluator>\n   {\n      public:\n         using operation_type = samet_fund_update_operation;\n\n         void_result do_evaluate( const samet_fund_update_operation& op );\n         void_result do_apply( const samet_fund_update_operation& op ) const;\n\n         const samet_fund_object* _fund = nullptr;\n   };\n\n   class samet_fund_borrow_evaluator : public evaluator<samet_fund_borrow_evaluator>\n   {\n      public:\n         using operation_type = samet_fund_borrow_operation;\n\n         void_result do_evaluate( const samet_fund_borrow_operation& op );\n         extendable_operation_result do_apply( const samet_fund_borrow_operation& op ) const;\n\n         const samet_fund_object* _fund = nullptr;\n   };\n\n   class samet_fund_repay_evaluator : public evaluator<samet_fund_repay_evaluator>\n   {\n      public:\n         using operation_type = samet_fund_repay_operation;\n\n         void_result do_evaluate( const samet_fund_repay_operation& op );\n         extendable_operation_result do_apply( const samet_fund_repay_operation& op ) const;\n\n         const samet_fund_object* _fund = nullptr;\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/samet_fund_object.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n *  @brief A SameT Fund is a fund which can be used by a borrower and have to be repaid in the same transaction\n *  @ingroup object\n *  @ingroup protocol\n *\n */\nclass samet_fund_object : public abstract_object<samet_fund_object, protocol_ids, samet_fund_object_type>\n{\n   public:\n      account_id_type owner_account;         ///< Owner of the fund\n      asset_id_type   asset_type;            ///< Asset type in the fund\n      share_type      balance;               ///< Usable amount in the fund\n      uint32_t        fee_rate = 0;          ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM\n      share_type      unpaid_amount;         ///< Unpaid amount\n};\n\nstruct by_unpaid;      // for protocol\nstruct by_owner;       // for API\nstruct by_asset_type;  // for API\n\n/**\n* @ingroup object_index\n*/\nusing samet_fund_multi_index_type = multi_index_container<\n   samet_fund_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_unpaid>,\n         composite_key< samet_fund_object,\n            member< samet_fund_object, share_type, &samet_fund_object::unpaid_amount >,\n            member< object, object_id_type, &object::id>\n         >,\n         composite_key_compare< std::greater<share_type>, std::less<object_id_type> >\n      >,\n      ordered_unique< tag<by_owner>,\n         composite_key< samet_fund_object,\n            member< samet_fund_object, account_id_type, &samet_fund_object::owner_account >,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_asset_type>,\n         composite_key< samet_fund_object,\n            member< samet_fund_object, asset_id_type, &samet_fund_object::asset_type >,\n            member< object, object_id_type, &object::id>\n         >\n      >\n   >\n>;\n\n/**\n* @ingroup object_index\n*/\nusing samet_fund_index = generic_index<samet_fund_object, samet_fund_multi_index_type>;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE( graphene::chain::samet_fund_object )\n\nFC_REFLECT_TYPENAME( graphene::chain::samet_fund_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::samet_fund_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/special_authority_evaluation.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/special_authority.hpp>\n\nnamespace graphene { namespace chain {\n\nclass database;\nusing namespace protocol;\n\nvoid evaluate_special_authority( const database& db, const special_authority& auth );\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/special_authority_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n * special_authority_object only exists to help with a specific indexing problem.\n * We want to be able to iterate over all accounts that contain a special authority.\n * However, accounts which have a special_authority are very rare.  So rather\n * than indexing account_object by the special_authority fields (requiring additional\n * bookkeeping for every account), we instead maintain a special_authority_object\n * pointing to each account which has special_authority (requiring additional\n * bookkeeping only for every account which has special_authority).\n *\n * This class is an implementation detail.\n */\n\nclass special_authority_object : public graphene::db::abstract_object<special_authority_object,\n                                           implementation_ids, impl_special_authority_object_type>\n{\n   public:\n      account_id_type account;\n};\n\nstruct by_account;\n\nusing special_authority_multi_idx_typ = multi_index_container<\n   special_authority_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_account>,\n         member< special_authority_object, account_id_type, &special_authority_object::account> >\n   >\n>;\n\nusing special_authority_index = generic_index< special_authority_object, special_authority_multi_idx_typ >;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::special_authority_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::special_authority_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::special_authority_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/ticket_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n\n#include <graphene/protocol/ticket.hpp>\n\nnamespace graphene { namespace chain {\n\n   class ticket_object;\n\n   class ticket_create_evaluator : public evaluator<ticket_create_evaluator>\n   {\n      public:\n         typedef ticket_create_operation operation_type;\n\n         void_result do_evaluate( const ticket_create_operation& op );\n         object_id_type do_apply( const ticket_create_operation& op );\n   };\n\n   class ticket_update_evaluator : public evaluator<ticket_update_evaluator>\n   {\n      public:\n         typedef ticket_update_operation operation_type;\n\n         void_result do_evaluate( const ticket_update_operation& op );\n         generic_operation_result do_apply( const ticket_update_operation& op );\n\n         const ticket_object* _ticket = nullptr;\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/ticket_object.hpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/ticket.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n\nusing namespace graphene::db;\n\nusing graphene::protocol::ticket_type;\n\n/// Status of a ticket\nenum ticket_status\n{\n   charging,\n   stable,\n   withdrawing,\n   TICKET_STATUS_COUNT\n};\n\n/// Version of a ticket\nenum ticket_version\n{\n   ticket_v1 = 1,\n   ticket_v2 = 2\n};\n\n/**\n *  @brief a ticket for governance voting\n *  @ingroup object\n *  @ingroup protocol\n *\n */\nclass ticket_object : public abstract_object<ticket_object, protocol_ids, ticket_object_type>\n{\n   public:\n      account_id_type  account;      ///< The account who owns the ticket\n      ticket_type      target_type;  ///< The target type of the ticket\n      asset            amount;       ///< The token type and amount in the ticket\n\n      ticket_type      current_type; ///< The current type of the ticket\n      ticket_status    status;       ///< The status of the ticket\n      share_type       value;        ///< The current value of the ticket\n      time_point_sec   next_auto_update_time; ///< The next time that the ticket will be automatically updated\n\n      /// When the account has ever started a downgrade or withdrawal, the scheduled auto-update time is stored here\n      time_point_sec   next_type_downgrade_time = time_point_sec::maximum();\n\n      // Configurations\n      static constexpr uint32_t lock_forever_update_steps = 4;\n      static constexpr uint32_t seconds_per_lock_forever_update_step = 180 * 86400;\n      static constexpr uint32_t seconds_per_charging_step = 15 * 86400;\n      static constexpr uint32_t seconds_to_cancel_charging = 7 * 86400;\n      static uint32_t seconds_to_downgrade( ticket_type i ) {\n         static constexpr uint32_t _seconds_to_downgrade[] = { 180 * 86400, 180 * 86400, 360 * 86400 };\n         return _seconds_to_downgrade[ static_cast<uint8_t>(i) ];\n      }\n      static uint8_t value_multiplier( ticket_type i, ticket_version version ) {\n         static constexpr uint8_t _value_multiplier_v1[] = { 1, 2, 4, 8, 8, 0 };\n         static constexpr uint8_t _value_multiplier_v2[] = { 0, 2, 4, 8, 8, 0 };\n         return ( version == ticket_v1 ? _value_multiplier_v1[ static_cast<uint8_t>(i) ]\n                                       : _value_multiplier_v2[ static_cast<uint8_t>(i) ] );\n      }\n\n      /// Initialize member variables for a ticket newly created from account balance\n      void init_new( time_point_sec now, account_id_type new_account,\n                     ticket_type new_target_type, const asset& new_amount, ticket_version version );\n\n      /// Initialize member variables for a ticket split from another ticket\n      void init_split( time_point_sec now, const ticket_object& old_ticket,\n                       ticket_type new_target_type, const asset& new_amount, ticket_version version );\n\n      /// Set a new target type and update member variables accordingly\n      void update_target_type( time_point_sec now, ticket_type new_target_type, ticket_version version );\n\n      /// Adjust amount and update member variables accordingly\n      void adjust_amount( const asset& delta_amount, ticket_version version );\n\n      /// Update the ticket when it's time\n      void auto_update( ticket_version version );\n\n   private:\n      /// Recalculate value of the ticket\n      void update_value( ticket_version version );\n\n};\n\nstruct by_next_update;\nstruct by_account;\n\n/**\n* @ingroup object_index\n*/\ntypedef multi_index_container<\n   ticket_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_next_update>,\n         composite_key< ticket_object,\n            member< ticket_object, time_point_sec, &ticket_object::next_auto_update_time>,\n            member< object, object_id_type, &object::id>\n         >\n      >,\n      ordered_unique< tag<by_account>,\n         composite_key< ticket_object,\n            member< ticket_object, account_id_type, &ticket_object::account>,\n            member< object, object_id_type, &object::id>\n         >\n      >\n   >\n> ticket_multi_index_type;\n\n/**\n* @ingroup object_index\n*/\ntypedef generic_index<ticket_object, ticket_multi_index_type> ticket_index;\n\n} } // graphene::chain\n\nFC_REFLECT_ENUM( graphene::chain::ticket_status,\n                 (charging)(stable)(withdrawing)(TICKET_STATUS_COUNT) )\n\nMAP_OBJECT_ID_TO_TYPE( graphene::chain::ticket_object )\n\nFC_REFLECT_TYPENAME( graphene::chain::ticket_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::ticket_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n\nnamespace graphene {\nnamespace protocol { class signed_transaction; }\nnamespace chain {\n   class database;\n   using protocol::signed_transaction;\n\n   /**\n    *  Place holder for state tracked while processing a transaction. This class provides helper methods that are\n    *  common to many different operations and also tracks which keys have signed the transaction\n    */\n   class transaction_evaluation_state\n   {\n      public:\n         transaction_evaluation_state( database* db = nullptr )\n         :_db(db){}\n\n\n         database& db()const { assert( _db ); return *_db; }\n         vector<operation_result> operation_results;\n\n         const signed_transaction*        _trx = nullptr;\n         database*                        _db = nullptr;\n         bool                             _is_proposed_trx = false;\n         bool                             skip_fee_schedule_check = false;\n         bool                             skip_limit_order_price_check = false; // Used in limit_order_update_op\n   };\n} } // namespace graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/transaction_history_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/transaction.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace graphene::db;\n   using boost::multi_index_container;\n   using namespace boost::multi_index;\n   /**\n    * The purpose of this object is to enable the detection of duplicate transactions. When a transaction is included\n    * in a block a transaction_history_object is added. At the end of block processing all transaction_history_objects that\n    * have expired can be removed from the index.\n    */\n   class transaction_history_object : public abstract_object<transaction_history_object,\n                                                implementation_ids, impl_transaction_history_object_type>\n   {\n      public:\n         signed_transaction  trx;\n         transaction_id_type trx_id;\n\n         time_point_sec get_expiration()const { return trx.expiration; }\n   };\n\n   struct by_expiration;\n   struct by_trx_id;\n   typedef multi_index_container<\n      transaction_history_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         hashed_unique< tag<by_trx_id>, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, transaction_id_type, trx_id),\n                        std::hash<transaction_id_type> >,\n         ordered_non_unique< tag<by_expiration>, const_mem_fun< transaction_history_object, time_point_sec,\n                                                                &transaction_history_object::get_expiration > >\n      >\n   > transaction_multi_index_type;\n\n   typedef generic_index<transaction_history_object, transaction_multi_index_type> transaction_index;\n} }\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::transaction_history_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::transaction_history_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::transaction_history_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/transfer_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace chain {\n\n   class transfer_evaluator : public evaluator<transfer_evaluator>\n   {\n      public:\n         typedef transfer_operation operation_type;\n\n         void_result do_evaluate( const transfer_operation& o );\n         void_result do_apply( const transfer_operation& o );\n   };\n\n   class override_transfer_evaluator : public evaluator<override_transfer_evaluator>\n   {\n      public:\n         typedef override_transfer_operation operation_type;\n\n         void_result do_evaluate( const override_transfer_operation& o );\n         void_result do_apply( const override_transfer_operation& o );\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/types.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace chain { using namespace protocol; } }\n\n/// Object types in the Implementation Space (enum impl_object_type (2.x.x))\nGRAPHENE_DEFINE_IDS(chain, implementation_ids, impl_,\n                    /* 2.0.x  */ (global_property)\n                    /* 2.1.x  */ (dynamic_global_property)\n                    /* 2.2.x  */ (reserved0) // unused, but can not be simply deleted due to API compatibility\n                    /* 2.3.x  */ (asset_dynamic_data)\n                    /* 2.4.x  */ (asset_bitasset_data)\n                    /* 2.5.x  */ (account_balance)\n                    /* 2.6.x  */ (account_statistics)\n                    /* 2.7.x  */ (transaction_history)\n                    /* 2.8.x  */ (block_summary)\n                    /* 2.9.x  */ (account_history)\n                    /* 2.10.x */ (blinded_balance)\n                    /* 2.11.x */ (chain_property)\n                    /* 2.12.x */ (witness_schedule)\n                    /* 2.13.x */ (budget_record)\n                    /* 2.14.x */ (special_authority)\n                    /* 2.15.x */ (buyback)\n                    /* 2.16.x */ (fba_accumulator)\n                    /* 2.17.x */ (collateral_bid)\n                    /* 2.18.x */ (credit_deal_summary)\n                   )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/vesting_balance_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/evaluator.hpp>\n\nnamespace graphene { namespace chain {\n\nclass vesting_balance_create_evaluator;\nclass vesting_balance_withdraw_evaluator;\n\nclass vesting_balance_create_evaluator : public evaluator<vesting_balance_create_evaluator>\n{\n    public:\n        typedef vesting_balance_create_operation operation_type;\n\n        void_result do_evaluate( const vesting_balance_create_operation& op );\n        object_id_type do_apply( const vesting_balance_create_operation& op );\n};\n\nclass vesting_balance_withdraw_evaluator : public evaluator<vesting_balance_withdraw_evaluator>\n{\n    public:\n        typedef vesting_balance_withdraw_operation operation_type;\n\n        void_result do_evaluate( const vesting_balance_withdraw_operation& op );\n        void_result do_apply( const vesting_balance_withdraw_operation& op );\n};\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/vesting_balance_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/db/generic_index.hpp>\n#include <graphene/protocol/asset.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n#include <boost/multi_index/identity.hpp>\n\n#include <fc/static_variant.hpp>\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace graphene::db;\n   using namespace graphene::protocol;\n\n   struct vesting_policy_context\n   {\n      vesting_policy_context(\n         asset _balance,\n         fc::time_point_sec _now,\n         asset _amount)\n         : balance(_balance), now(_now), amount(_amount) {}\n\n      asset              balance;\n      fc::time_point_sec now;\n      asset              amount;\n   };\n\n   /**\n    * @brief Linear vesting balance with cliff\n    *\n    * This vesting balance type is used to mimic traditional stock vesting contracts where\n    * each day a certain amount vests until it is fully matured.\n    *\n    * @note New funds may not be added to a linear vesting balance.\n    */\n   struct linear_vesting_policy\n   {\n      /// This is the time at which funds begin vesting.\n      fc::time_point_sec begin_timestamp;\n      /// No amount may be withdrawn before this many seconds of the vesting period have elapsed.\n      uint32_t vesting_cliff_seconds = 0;\n      /// Duration of the vesting period, in seconds. Must be greater than 0 and greater than vesting_cliff_seconds.\n      uint32_t vesting_duration_seconds = 0;\n      /// The total amount of asset to vest.\n      share_type begin_balance;\n\n      asset get_allowed_withdraw(const vesting_policy_context& ctx)const;\n      bool is_deposit_allowed(const vesting_policy_context& ctx)const;\n      bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; }\n      bool is_withdraw_allowed(const vesting_policy_context& ctx)const;\n      void on_deposit(const vesting_policy_context& ctx);\n      void on_deposit_vested(const vesting_policy_context&)\n      { FC_THROW( \"May not deposit vested into a linear vesting balance.\" ); }\n      void on_withdraw(const vesting_policy_context& ctx);\n   };\n\n   /**\n    * @brief defines vesting in terms of coin-days accrued which allows for dynamic deposit/withdraw\n    *\n    * The economic effect of this vesting policy is to require a certain amount of \"interest\" to accrue\n    * before the full balance may be withdrawn.  Interest accrues as coindays (balance * length held).  If\n    * some of the balance is withdrawn, the remaining balance must be held longer.\n    */\n   struct cdd_vesting_policy\n   {\n      uint32_t                         vesting_seconds = 0;\n      fc::uint128_t                    coin_seconds_earned;\n      /** while coindays may accrue over time, none may be claimed before first_claim date */\n      fc::time_point_sec               start_claim;\n      fc::time_point_sec               coin_seconds_earned_last_update;\n\n      /**\n       * Compute coin_seconds_earned.  Used to\n       * non-destructively figure out how many coin seconds\n       * are available.\n       */\n      fc::uint128_t compute_coin_seconds_earned(const vesting_policy_context& ctx)const;\n\n      /**\n       * Update coin_seconds_earned and\n       * coin_seconds_earned_last_update fields; called by both\n       * on_deposit() and on_withdraw().\n       */\n      void update_coin_seconds_earned(const vesting_policy_context& ctx);\n\n      asset get_allowed_withdraw(const vesting_policy_context& ctx)const;\n      bool is_deposit_allowed(const vesting_policy_context& ctx)const;\n      bool is_deposit_vested_allowed(const vesting_policy_context& ctx)const;\n      bool is_withdraw_allowed(const vesting_policy_context& ctx)const;\n      void on_deposit(const vesting_policy_context& ctx);\n      void on_deposit_vested(const vesting_policy_context& ctx);\n      void on_withdraw(const vesting_policy_context& ctx);\n   };\n\n     /**\n    * @brief instant vesting policy\n    *\n    * This policy allows to withdraw everything that is on a balance immediately\n    *\n    */\n   struct instant_vesting_policy\n   {\n      asset get_allowed_withdraw(const vesting_policy_context& ctx)const;\n      bool is_deposit_allowed(const vesting_policy_context& ctx)const;\n      bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; }\n      bool is_withdraw_allowed(const vesting_policy_context& ctx)const;\n      void on_deposit(const vesting_policy_context& ctx);\n      void on_deposit_vested(const vesting_policy_context&);\n      void on_withdraw(const vesting_policy_context& ctx);\n   };\n\n   typedef fc::static_variant<\n      linear_vesting_policy,\n      cdd_vesting_policy,\n      instant_vesting_policy\n      > vesting_policy;\n\n   enum class vesting_balance_type {   unspecified,\n                                       cashback,\n                                       worker,\n                                       witness,\n                                       market_fee_sharing };\n   /**\n    * Vesting balance object is a balance that is locked by the blockchain for a period of time.\n    */\n   class vesting_balance_object : public abstract_object<vesting_balance_object,\n                                            protocol_ids, vesting_balance_object_type>\n   {\n      public:\n         /// Account which owns and may withdraw from this vesting balance\n         account_id_type owner;\n         /// Total amount remaining in this vesting balance\n         /// Includes the unvested funds, and the vested funds which have not yet been withdrawn\n         asset balance;\n         /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn\n         vesting_policy policy;\n         /// type of the vesting balance\n         vesting_balance_type balance_type = vesting_balance_type::unspecified;\n\n         vesting_balance_object() {}\n\n         ///@brief Deposit amount into vesting balance, requiring it to vest before withdrawal\n         void deposit(const fc::time_point_sec& now, const asset& amount);\n         bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const;\n\n         /// @brief Deposit amount into vesting balance, making the new funds vest immediately\n         void deposit_vested(const fc::time_point_sec& now, const asset& amount);\n         bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const;\n\n         /**\n          * Used to remove a vesting balance from the VBO. As well as the\n          * balance field, coin_seconds_earned and\n          * coin_seconds_earned_last_update fields are updated.\n          *\n          * The money doesn't \"go\" anywhere; the caller is responsible for\n          * crediting it to the proper account.\n          */\n         void withdraw(const fc::time_point_sec& now, const asset& amount);\n         bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const;\n\n         /**\n          * Get amount of allowed withdrawal.\n          */\n         asset get_allowed_withdraw(const time_point_sec& now)const;\n   };\n   /**\n    * @ingroup object_index\n    */\n   struct by_account;\n   // by_vesting_type index MUST NOT be used for iterating because order is not well-defined.\n   struct by_vesting_type;\n\nnamespace detail {\n\n   /**\n      Calculate a hash for account_id_type and asset_id.\n      Use 48 bit value (see object_id.hpp) for account_id and XOR it with 24 bit for asset_id\n   */\n   inline uint64_t vbo_mfs_hash(const account_id_type& account_id, const asset_id_type& asset_id)\n   {\n      return (asset_id.instance.value << 40) ^ account_id.instance.value;\n   }\n\n   /**\n    * Used as CompatibleHash\n      Calculate a hash vesting_balance_object\n      if vesting_balance_object.balance_type is market_fee_sharing\n         calculate has as vbo_mfs_hash(vesting_balance_object.owner, hash(vbo.balance.asset_id) (see vbo_mfs_hash)\n      otherwise: hash_value(vesting_balance_object.id);\n   */\n   struct vesting_balance_object_hash\n   {\n      uint64_t operator()(const vesting_balance_object& vbo) const\n      {\n         if ( vbo.balance_type == vesting_balance_type::market_fee_sharing )\n         {\n            return vbo_mfs_hash(vbo.owner, vbo.balance.asset_id);\n         }\n         return hash_value(vbo.id);\n      }\n   };\n\n   /**\n    * Used as CompatiblePred\n    * Compares two vesting_balance_objects\n    * if vesting_balance_object.balance_type is a market_fee_sharing\n    *    compare owners' ids and assets' ids\n    * otherwise: vesting_balance_object.id\n   */\n   struct vesting_balance_object_equal\n   {\n      bool operator() (const vesting_balance_object& lhs, const vesting_balance_object& rhs) const\n      {\n         if ( ( lhs.balance_type == vesting_balance_type::market_fee_sharing ) &&\n              ( lhs.balance_type == rhs.balance_type ) &&\n              ( lhs.owner == rhs.owner ) &&\n              ( lhs.balance.asset_id == rhs.balance.asset_id)\n            )\n         {\n               return true;\n         }\n         return ( lhs.id == rhs.id );\n      }\n   };\n} // detail\n\n   typedef multi_index_container<\n      vesting_balance_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id >\n         >,\n         ordered_non_unique< tag<by_account>,\n            member<vesting_balance_object, account_id_type, &vesting_balance_object::owner>\n         >,\n         hashed_unique< tag<by_vesting_type>,\n            identity<vesting_balance_object>,\n            detail::vesting_balance_object_hash,\n            detail::vesting_balance_object_equal\n         >\n      >\n   > vesting_balance_multi_index_type;\n   /**\n    * @ingroup object_index\n    */\n   typedef generic_index<vesting_balance_object, vesting_balance_multi_index_type> vesting_balance_index;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::vesting_balance_object)\n\nFC_REFLECT(graphene::chain::linear_vesting_policy,\n           (begin_timestamp)\n           (vesting_cliff_seconds)\n           (vesting_duration_seconds)\n           (begin_balance)\n          )\n\nFC_REFLECT(graphene::chain::cdd_vesting_policy,\n           (vesting_seconds)\n           (start_claim)\n           (coin_seconds_earned)\n           (coin_seconds_earned_last_update)\n          )\n\nFC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy )\n\nFC_REFLECT_TYPENAME( graphene::chain::vesting_policy )\n\nFC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object),\n                   (owner)\n                   (balance)\n                   (policy)\n                   (balance_type)\n                  )\n\nFC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (unspecified)(cashback)(worker)(witness)(market_fee_sharing) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::linear_vesting_policy )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::cdd_vesting_policy )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::vesting_balance_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/vote_count.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#pragma once\n\n#include <graphene/protocol/authority.hpp>\n\nnamespace graphene { namespace chain {\n\n/**\n * Keep track of vote totals in internal authority object.  See #533.\n */\nstruct vote_counter\n{\n   template< typename Component >\n   void add( Component who, uint64_t votes )\n   {\n      if( votes == 0 )\n         return;\n      assert( votes <= last_votes );\n      last_votes = votes;\n      if( bitshift == -1 )\n         bitshift = std::max(int8_t(boost::multiprecision::detail::find_msb( votes )) - 15, 0);\n      uint64_t scaled_votes = std::max( votes >> (uint8_t)bitshift, uint64_t(1) );\n      assert( scaled_votes <= std::numeric_limits<weight_type>::max() );\n      total_votes += scaled_votes;\n      assert( total_votes <= std::numeric_limits<uint32_t>::max() );\n      auth.add_authority( who, weight_type( scaled_votes ) );\n   }\n\n   /**\n    * Write into out_auth, but only if we have at least one member.\n    */\n   void finish( authority& out_auth )\n   {\n      if( total_votes == 0 )\n         return;\n      assert( total_votes <= std::numeric_limits<uint32_t>::max() );\n      uint32_t weight = uint32_t( total_votes );\n      weight = (weight >> 1)+1;\n      auth.weight_threshold = weight;\n      out_auth = auth;\n   }\n\n   bool is_empty()const\n   {\n      return (total_votes == 0);\n   }\n\n   uint64_t last_votes = std::numeric_limits<uint64_t>::max();\n   uint64_t total_votes = 0;\n   int8_t bitshift = -1;\n   authority auth;\n};\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/withdraw_permission_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/evaluator.hpp>\n\nnamespace graphene { namespace chain {\n\nclass withdraw_permission_create_evaluator : public evaluator<withdraw_permission_create_evaluator>\n{\npublic:\n   using operation_type = withdraw_permission_create_operation;\n\n   void_result do_evaluate( const operation_type& op ) const;\n   object_id_type do_apply( const operation_type& op ) const;\n};\n\nclass withdraw_permission_claim_evaluator : public evaluator<withdraw_permission_claim_evaluator>\n{\npublic:\n   using operation_type = withdraw_permission_claim_operation;\n\n   void_result do_evaluate( const operation_type& op ) const;\n   void_result do_apply( const operation_type& op ) const;\n};\n\nclass withdraw_permission_update_evaluator : public evaluator<withdraw_permission_update_evaluator>\n{\npublic:\n   using operation_type = withdraw_permission_update_operation;\n\n   void_result do_evaluate( const operation_type& op ) const;\n   void_result do_apply( const operation_type& op ) const;\n};\n\nclass withdraw_permission_delete_evaluator : public evaluator<withdraw_permission_delete_evaluator>\n{\npublic:\n   using operation_type = withdraw_permission_delete_operation;\n\n   void_result do_evaluate( const operation_type& op ) const;\n   void_result do_apply( const operation_type& op ) const;\n};\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/withdraw_permission_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/asset.hpp>\n#include <graphene/db/generic_index.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace graphene::protocol;\n\n  /**\n   * @class withdraw_permission_object\n   * @brief Grants another account authority to withdraw a limited amount of funds per interval\n   *\n   * The primary purpose of this object is to enable recurring payments on the blockchain. An account which wishes to\n   * process a recurring payment may use a @ref graphene::protocol::withdraw_permission_claim_operation\n   * to reference an object of this type\n   * and withdraw up to @ref withdrawal_limit from @ref withdraw_from_account. Only @ref authorized_account may do\n   * this. Any number of withdrawals may be made so long as the total amount withdrawn per period does not exceed the\n   * limit for any given period.\n   */\n  class withdraw_permission_object : public graphene::db::abstract_object<withdraw_permission_object,\n                                               protocol_ids, withdraw_permission_object_type>\n  {\n     public:\n        /// The account authorizing @ref authorized_account to withdraw from it\n        account_id_type    withdraw_from_account;\n        /// The account authorized to make withdrawals from @ref withdraw_from_account\n        account_id_type    authorized_account;\n        /// The maximum amount which may be withdrawn per period. All withdrawals must be of this asset type\n        asset              withdrawal_limit;\n        /// The duration of a withdrawal period in seconds\n        uint32_t           withdrawal_period_sec = 0;\n        /**\n         * The beginning of the next withdrawal period.\n         * WARNING: Due to caching, this value does not always represent the start of the next or current period,\n         *   because it is only updated after a withdrawal operation such as claim.\n         * For the latest current period, use current_period().\n         */\n        time_point_sec     period_start_time;\n        /// The time at which this withdraw permission expires\n        time_point_sec     expiration;\n\n        /**\n         * Tracks the total amount\n         * WARNING: Due to caching, this value does not always represent the total amount claimed during the\n         *   current period, it may represent what was claimed during the last claimed period, because it is only\n         *   updated after a withdrawal operation such as claim.\n         * For the latest current period, use current_period().\n         */\n        share_type         claimed_this_period;\n\n        /***\n         * Determine how much is still available to be claimed during the period that contains a time of interest.\n         * This object and function is mainly intended to be used with the \"current\" time as a parameter.\n         * The current time can be obtained from the time of the current head of the blockchain.\n         */\n        asset              available_this_period( fc::time_point_sec current_time )const\n        {\n           if( current_time >= period_start_time + withdrawal_period_sec )\n              return withdrawal_limit;\n           return asset( ( withdrawal_limit.amount > claimed_this_period ) ?\n                            ( withdrawal_limit.amount - claimed_this_period ) : 0,\n                         withdrawal_limit.asset_id );\n        }\n   };\n\n   struct by_from;\n   struct by_authorized;\n   struct by_expiration;\n\n   using withdraw_permission_obj_mlt_idx = multi_index_container<\n      withdraw_permission_object,\n      indexed_by<\n         ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n         ordered_unique< tag<by_from>,\n            composite_key< withdraw_permission_object,\n               member< withdraw_permission_object, account_id_type,\n                       &withdraw_permission_object::withdraw_from_account >,\n               member< object, object_id_type, &object::id >\n            >\n         >,\n         ordered_unique< tag<by_authorized>,\n            composite_key< withdraw_permission_object,\n               member< withdraw_permission_object, account_id_type, &withdraw_permission_object::authorized_account >,\n               member< object, object_id_type, &object::id >\n            >\n         >,\n         ordered_unique< tag<by_expiration>,\n            composite_key< withdraw_permission_object,\n               member< withdraw_permission_object, time_point_sec, &withdraw_permission_object::expiration >,\n               member< object, object_id_type, &object::id >\n            >\n         >\n      >\n   >;\n\n   using withdraw_permission_index = generic_index<withdraw_permission_object, withdraw_permission_obj_mlt_idx>;\n\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::withdraw_permission_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::withdraw_permission_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::withdraw_permission_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/witness_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/witness_object.hpp>\n\nnamespace graphene { namespace chain {\n\n   class witness_create_evaluator : public evaluator<witness_create_evaluator>\n   {\n      public:\n         typedef witness_create_operation operation_type;\n\n         void_result do_evaluate( const witness_create_operation& o );\n         object_id_type do_apply( const witness_create_operation& o );\n   };\n\n   class witness_update_evaluator : public evaluator<witness_update_evaluator>\n   {\n      public:\n         typedef witness_update_operation operation_type;\n\n         void_result do_evaluate( const witness_update_operation& o );\n         void_result do_apply( const witness_update_operation& o );\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/witness_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/asset.hpp>\n#include <graphene/db/generic_index.hpp>\n\nnamespace graphene { namespace chain {\n   using namespace graphene::db;\n\n   class witness_object : public abstract_object<witness_object, protocol_ids, witness_object_type>\n   {\n      public:\n         account_id_type  witness_account;\n         uint64_t         last_aslot = 0;\n         public_key_type  signing_key;\n         optional< vesting_balance_id_type > pay_vb;\n         vote_id_type     vote_id { vote_id_type::witness };\n         uint64_t         total_votes = 0;\n         string           url;\n         int64_t          total_missed = 0;\n         uint32_t         last_confirmed_block_num = 0;\n   };\n\n   struct by_account;\n   struct by_vote_id;\n   struct by_last_block;\n   using witness_multi_index_type = multi_index_container<\n      witness_object,\n      indexed_by<\n         ordered_unique< tag<by_id>,\n            member<object, object_id_type, &object::id>\n         >,\n         ordered_unique< tag<by_account>,\n            member<witness_object, account_id_type, &witness_object::witness_account>\n         >,\n         ordered_unique< tag<by_vote_id>,\n            member<witness_object, vote_id_type, &witness_object::vote_id>\n         >\n      >\n   >;\n   using witness_index = generic_index<witness_object, witness_multi_index_type>;\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::witness_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::witness_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::witness_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/witness_schedule_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/types.hpp>\n#include <graphene/db/generic_index.hpp>\n\nnamespace graphene { namespace chain {\n\nclass witness_schedule_object : public graphene::db::abstract_object<witness_schedule_object,\n                                          implementation_ids, impl_witness_schedule_object_type>\n{\n   public:\n      vector< witness_id_type > current_shuffled_witnesses;\n};\n\n} }\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::witness_schedule_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::witness_schedule_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::witness_schedule_object )\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/worker_evaluator.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/evaluator.hpp>\n\nnamespace graphene { namespace chain {\n\n   class worker_create_evaluator : public evaluator<worker_create_evaluator>\n   {\n      public:\n         typedef worker_create_operation operation_type;\n\n         void_result do_evaluate( const operation_type& o );\n         object_id_type do_apply( const operation_type& o );\n   };\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/include/graphene/chain/worker_object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/chain/types.hpp>\n#include <graphene/db/generic_index.hpp>\n#include <graphene/protocol/vote.hpp>\n\nnamespace graphene { namespace chain {\nclass database;\n\n/**\n  * @defgroup worker_types Implementations of the various worker types in the system\n  *\n  * The system has various worker types, which do different things with the money they are paid. These worker types\n  * and their semantics are specified here.\n  *\n  * All worker types exist as a struct containing the data this worker needs to evaluate, as well as a method\n  * pay_worker, which takes a pay amount and a non-const database reference, and applies the worker's specific pay\n  * semantics to the worker_type struct and/or the database. Furthermore, all worker types have an initializer,\n  * which is a struct containing the data needed to create that kind of worker.\n  *\n  * Each initializer type has a method, init, which takes a non-const database reference, a const reference to the\n  * worker object being created, and a non-const reference to the specific *_worker_type object to initialize. The\n  * init method creates any further objects, and initializes the worker_type object as necessary according to the\n  * semantics of that particular worker type.\n  *\n  * To create a new worker type, define a my_new_worker_type struct with a pay_worker method which updates the\n  * my_new_worker_type object and/or the database. Create a my_new_worker_type::initializer struct with an init\n  * method and any data members necessary to create a new worker of this type. Reflect my_new_worker_type and\n  * my_new_worker_type::initializer into FC's type system, and add them to @ref worker_type and @c\n  * worker_initializer respectively. Make sure the order of types in @ref worker_type and @c worker_initializer\n  * remains the same.\n  * @{\n  */\n/**\n * @brief A worker who returns all of his pay to the reserve\n *\n * This worker type pays everything he receives back to the network's reserve funds pool.\n */\nstruct refund_worker_type\n{\n   /// Record of how much this worker has burned in his lifetime\n   share_type total_burned;\n\n   void pay_worker(share_type pay, database&);\n};\n\n/**\n * @brief A worker who sends his pay to a vesting balance\n *\n * This worker type takes all of his pay and places it into a vesting balance\n */\nstruct vesting_balance_worker_type\n{\n   /// The balance this worker pays into\n   vesting_balance_id_type balance;\n\n   void pay_worker(share_type pay, database& db);\n};\n\n/**\n * @brief A worker who permanently destroys all of his pay\n *\n * This worker sends all pay he receives to the null account.\n */\nstruct burn_worker_type\n{\n   /// Record of how much this worker has burned in his lifetime\n   share_type total_burned;\n\n   void pay_worker(share_type pay, database&);\n};\n///@}\n\n// The ordering of types in these two static variants MUST be the same.\ntypedef static_variant<\n   refund_worker_type,\n   vesting_balance_worker_type,\n   burn_worker_type\n> worker_type;\n\n\n/**\n * @brief Worker object contains the details of a blockchain worker. See @ref workers for details.\n */\nclass worker_object : public abstract_object<worker_object, protocol_ids, worker_object_type>\n{\n   public:\n      /// ID of the account which owns this worker\n      account_id_type worker_account;\n      /// Time at which this worker begins receiving pay, if elected\n      time_point_sec work_begin_date;\n      /// Time at which this worker will cease to receive pay. Worker will be deleted at this time\n      time_point_sec work_end_date;\n      /// Amount in CORE this worker will be paid each day\n      share_type daily_pay;\n      /// ID of this worker's pay balance\n      worker_type worker;\n      /// Human-readable name for the worker\n      string name;\n      /// URL to a web page representing this worker\n      string url;\n\n      /// Voting ID which represents approval of this worker\n      vote_id_type vote_for;\n      /// Voting ID which represents disapproval of this worker\n      vote_id_type vote_against;\n\n      uint64_t total_votes_for = 0;\n      uint64_t total_votes_against = 0;\n\n      bool is_active(fc::time_point_sec now)const {\n         return now >= work_begin_date && now <= work_end_date;\n      }\n\n      share_type approving_stake()const {\n         return int64_t( total_votes_for ) - int64_t( total_votes_against );\n      }\n};\n\nstruct by_account;\nstruct by_vote_for;\nstruct by_vote_against;\nstruct by_end_date;\ntypedef multi_index_container<\n   worker_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_non_unique< tag<by_account>, member< worker_object, account_id_type, &worker_object::worker_account > >,\n      ordered_unique< tag<by_vote_for>, member< worker_object, vote_id_type, &worker_object::vote_for > >,\n      ordered_unique< tag<by_vote_against>, member< worker_object, vote_id_type, &worker_object::vote_against > >,\n      ordered_non_unique< tag<by_end_date>, member< worker_object, time_point_sec, &worker_object::work_end_date> >\n   >\n> worker_object_multi_index_type;\n\nusing worker_index = generic_index<worker_object, worker_object_multi_index_type>;\n\n} } // graphene::chain\n\nMAP_OBJECT_ID_TO_TYPE(graphene::chain::worker_object)\n\nFC_REFLECT_TYPENAME( graphene::chain::refund_worker_type )\nFC_REFLECT_TYPENAME( graphene::chain::vesting_balance_worker_type )\nFC_REFLECT_TYPENAME( graphene::chain::burn_worker_type )\nFC_REFLECT_TYPENAME( graphene::chain::worker_type )\nFC_REFLECT_TYPENAME( graphene::chain::worker_object )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::worker_object )\n"
  },
  {
    "path": "libraries/chain/is_authorized_asset.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n\nnamespace graphene { namespace chain {\n\nnamespace detail {\n\nbool _is_authorized_asset(\n   const database& d,\n   const account_object& acct,\n   const asset_object& asset_obj)\n{\n   // committee-account is always allowed to transact after BSIP 86\n   if( HARDFORK_BSIP_86_PASSED( d.head_block_time() ) )\n   {\n      static const object_id_type committee_account_id( GRAPHENE_COMMITTEE_ACCOUNT );\n      if( acct.id == committee_account_id )\n         return true;\n   }\n\n   if( acct.allowed_assets.valid() )\n   {\n      if( acct.allowed_assets->find( asset_obj.get_id() ) == acct.allowed_assets->end() )\n         return false;\n      // must still pass other checks even if it is in allowed_assets\n   }\n\n   for( const auto& id : acct.blacklisting_accounts )\n   {\n      if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() )\n         return false;\n   }\n\n   if( asset_obj.options.whitelist_authorities.size() == 0 )\n      return true;\n\n   for( const auto& id : acct.whitelisting_accounts )\n   {\n      if( asset_obj.options.whitelist_authorities.find(id) != asset_obj.options.whitelist_authorities.end() )\n         return true;\n   }\n\n   return false;\n}\n\n} // detail\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/liquidity_pool_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n\n#include <graphene/chain/liquidity_pool_evaluator.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <graphene/protocol/liquidity_pool.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result liquidity_pool_create_evaluator::do_evaluate(const liquidity_pool_create_operation& op)\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), \"Not allowed until the LP hardfork\" );\n\n   op.asset_a(d); // Make sure it exists\n   op.asset_b(d); // Make sure it exists\n   _share_asset = &op.share_asset(d);\n\n   FC_ASSERT( _share_asset->issuer == op.account,\n              \"Only the asset owner can set an asset as the share asset of a liquidity pool\" );\n\n   FC_ASSERT( !_share_asset->is_market_issued(),\n              \"Can not specify a market-issued asset as the share asset of a liquidity pool\" );\n\n   FC_ASSERT( !_share_asset->is_liquidity_pool_share_asset(),\n              \"The share asset is already bound to another liquidity pool\" );\n\n   FC_ASSERT( _share_asset->dynamic_data(d).current_supply == 0,\n              \"Current supply of the share asset needs to be zero\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\ngeneric_operation_result liquidity_pool_create_evaluator::do_apply(const liquidity_pool_create_operation& op)\n{ try {\n   database& d = db();\n   generic_operation_result result;\n\n   const auto& new_liquidity_pool_object = d.create<liquidity_pool_object>([&op](liquidity_pool_object& obj){\n      obj.asset_a = op.asset_a;\n      obj.asset_b = op.asset_b;\n      obj.share_asset = op.share_asset;\n      obj.taker_fee_percent = op.taker_fee_percent;\n      obj.withdrawal_fee_percent = op.withdrawal_fee_percent;\n   });\n   result.new_objects.insert( new_liquidity_pool_object.id );\n\n   result.updated_objects.insert( _share_asset->id );\n   d.modify( *_share_asset, [&new_liquidity_pool_object](asset_object& ao) {\n      ao.for_liquidity_pool = new_liquidity_pool_object.id;\n   });\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result liquidity_pool_delete_evaluator::do_evaluate(const liquidity_pool_delete_operation& op)\n{ try {\n   const database& d = db();\n\n   _pool = &op.pool(d);\n\n   FC_ASSERT( _pool->balance_a == 0 && _pool->balance_b == 0, \"Can not delete a non-empty pool\" );\n\n   _share_asset = &_pool->share_asset(d);\n\n   FC_ASSERT( _share_asset->issuer == op.account, \"The account is not the owner of the liquidity pool\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\ngeneric_operation_result liquidity_pool_delete_evaluator::do_apply(const liquidity_pool_delete_operation& op) const\n{ try {\n   database& d = db();\n   generic_operation_result result;\n\n   result.updated_objects.insert( _share_asset->id );\n   d.modify( *_share_asset, [](asset_object& ao) {\n      ao.for_liquidity_pool.reset();\n   });\n\n   result.removed_objects.insert( _pool->id );\n   d.remove( *_pool );\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result liquidity_pool_update_evaluator::do_evaluate(const liquidity_pool_update_operation& op)\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   FC_ASSERT( HARDFORK_CORE_2604_PASSED(block_time), \"Not allowed until the core-2604 hardfork\" );\n\n   _pool = &op.pool(d);\n\n   const asset_object* _share_asset = &_pool->share_asset(d);\n\n   FC_ASSERT( _share_asset->issuer == op.account, \"The account is not the owner of the liquidity pool\" );\n\n   if( op.taker_fee_percent.valid() )\n   {\n      FC_ASSERT( 0 == _pool->withdrawal_fee_percent\n                     || ( op.withdrawal_fee_percent.valid() && 0 == *op.withdrawal_fee_percent ),\n                 \"Taker fee percent can only be updated if withdrawal fee percent is zero or \"\n                 \"withdrawal fee percent is to be updated to zero at the same time\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result liquidity_pool_update_evaluator::do_apply(const liquidity_pool_update_operation& op) const\n{ try {\n   database& d = db();\n\n   d.modify( *_pool, [&op](liquidity_pool_object& obj) {\n      if( op.taker_fee_percent.valid() )\n         obj.taker_fee_percent = *op.taker_fee_percent;\n      if( op.withdrawal_fee_percent.valid() )\n         obj.withdrawal_fee_percent = *op.withdrawal_fee_percent;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result liquidity_pool_deposit_evaluator::do_evaluate(const liquidity_pool_deposit_operation& op)\n{ try {\n   const database& d = db();\n\n   _pool = &op.pool(d);\n\n   FC_ASSERT( op.amount_a.asset_id == _pool->asset_a, \"Asset type A mismatch\" );\n   FC_ASSERT( op.amount_b.asset_id == _pool->asset_b, \"Asset type B mismatch\" );\n\n   FC_ASSERT( (_pool->balance_a == 0) == (_pool->balance_b == 0), \"Internal error\" );\n\n   const asset_object& share_asset_obj = _pool->share_asset(d);\n\n   FC_ASSERT( share_asset_obj.can_create_new_supply(), \"Can not create new supply for the share asset\" );\n\n   if( _pool->balance_a == 0 ) // which implies that _pool->balance_b == 0\n   {\n      FC_ASSERT( share_asset_obj.issuer == op.account, \"The initial deposit can only be done by the pool owner\" );\n   }\n\n   _share_asset_dyn_data = &share_asset_obj.dynamic_data(d);\n\n   FC_ASSERT( (_pool->balance_a == 0) == (_share_asset_dyn_data->current_supply == 0), \"Internal error\" );\n\n   FC_ASSERT( _share_asset_dyn_data->current_supply < share_asset_obj.options.max_supply,\n              \"Can not create new supply for the share asset\" );\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, share_asset_obj ),\n              \"The account is unauthorized by the share asset\" );\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_a(d) ),\n              \"The account is unauthorized by asset A\" );\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_b(d) ),\n              \"The account is unauthorized by asset B\" );\n\n   if( _pool->balance_a == 0 )\n   {\n      share_type share_amount = std::max( op.amount_a.amount.value, op.amount_b.amount.value );\n      FC_ASSERT( share_amount <= share_asset_obj.options.max_supply,\n                 \"For initial deposit, each amount of the two assets in the pool should not be greater than \"\n                 \"the maximum supply of the share asset\" );\n      _pool_receives_a = op.amount_a;\n      _pool_receives_b = op.amount_b;\n      _account_receives = asset( share_amount, _pool->share_asset );\n   }\n   else\n   {\n      share_type max_new_supply = share_asset_obj.options.max_supply - _share_asset_dyn_data->current_supply;\n      fc::uint128_t max128( max_new_supply.value );\n      fc::uint128_t supply128( _share_asset_dyn_data->current_supply.value );\n      fc::uint128_t new_supply_if_a = supply128 * op.amount_a.amount.value / _pool->balance_a.value;\n      fc::uint128_t new_supply_if_b = supply128 * op.amount_b.amount.value / _pool->balance_b.value;\n      fc::uint128_t new_supply = std::min( { new_supply_if_a, new_supply_if_b, max128 } );\n\n      FC_ASSERT( new_supply > 0, \"Aborting due to zero outcome\" );\n\n      fc::uint128_t a128 = ( new_supply * _pool->balance_a.value + supply128 - 1 ) / supply128; // round up\n      FC_ASSERT( a128 <= fc::uint128_t( op.amount_a.amount.value ), \"Internal error\" );\n      _pool_receives_a = asset( static_cast<int64_t>( a128 ), _pool->asset_a );\n\n      fc::uint128_t b128 = ( new_supply * _pool->balance_b.value + supply128 - 1 ) / supply128; // round up\n      FC_ASSERT( b128 <= fc::uint128_t( op.amount_b.amount.value ), \"Internal error\" );\n      _pool_receives_b = asset( static_cast<int64_t>( b128 ), _pool->asset_b );\n\n      _account_receives = asset( static_cast<int64_t>( new_supply ), _pool->share_asset );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\ngeneric_exchange_operation_result liquidity_pool_deposit_evaluator::do_apply(\n      const liquidity_pool_deposit_operation& op)\n{ try {\n   database& d = db();\n   generic_exchange_operation_result result;\n\n   d.adjust_balance( op.account, -_pool_receives_a );\n   d.adjust_balance( op.account, -_pool_receives_b );\n   d.adjust_balance( op.account, _account_receives );\n\n   d.modify( *_pool, [this]( liquidity_pool_object& lpo ){\n      lpo.balance_a += _pool_receives_a.amount;\n      lpo.balance_b += _pool_receives_b.amount;\n      lpo.update_virtual_value();\n   });\n\n   d.modify( *_share_asset_dyn_data, [this]( asset_dynamic_data_object& data ){\n      data.current_supply += _account_receives.amount;\n   });\n\n   FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, \"Internal error\" );\n   FC_ASSERT( _share_asset_dyn_data->current_supply > 0, \"Internal error\" );\n\n   result.paid.emplace_back( _pool_receives_a );\n   result.paid.emplace_back( _pool_receives_b );\n   result.received.emplace_back( _account_receives );\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result liquidity_pool_withdraw_evaluator::do_evaluate(const liquidity_pool_withdraw_operation& op)\n{ try {\n   const database& d = db();\n\n   _pool = &op.pool(d);\n\n   FC_ASSERT( op.share_amount.asset_id == _pool->share_asset, \"Share asset type mismatch\" );\n\n   FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, \"The pool has not been initialized\" );\n\n   const asset_object& share_asset_obj = _pool->share_asset(d);\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, share_asset_obj ),\n              \"The account is unauthorized by the share asset\" );\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_a(d) ),\n              \"The account is unauthorized by asset A\" );\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _pool->asset_b(d) ),\n              \"The account is unauthorized by asset B\" );\n\n   _share_asset_dyn_data = &share_asset_obj.dynamic_data(d);\n\n   FC_ASSERT( _share_asset_dyn_data->current_supply >= op.share_amount.amount,\n              \"Can not withdraw an amount that is more than the current supply\" );\n\n   if( _share_asset_dyn_data->current_supply == op.share_amount.amount )\n   {\n      _pool_pays_a = asset( _pool->balance_a, _pool->asset_a );\n      _pool_pays_b = asset( _pool->balance_b, _pool->asset_b );\n      _fee_a = asset( 0, _pool->asset_a );\n      _fee_b = asset( 0, _pool->asset_b );\n   }\n   else\n   {\n      fc::uint128_t share128( op.share_amount.amount.value );\n      fc::uint128_t a128 = share128 * _pool->balance_a.value / _share_asset_dyn_data->current_supply.value;\n      FC_ASSERT( a128 < fc::uint128_t( _pool->balance_a.value ), \"Internal error\" );\n      fc::uint128_t fee_a = a128 * _pool->withdrawal_fee_percent / GRAPHENE_100_PERCENT;\n      FC_ASSERT( fee_a <= a128, \"Withdrawal fee percent of the pool is too high\" );\n      a128 -= fee_a;\n      fc::uint128_t b128 = share128 * _pool->balance_b.value / _share_asset_dyn_data->current_supply.value;\n      FC_ASSERT( b128 < fc::uint128_t( _pool->balance_b.value ), \"Internal error\" );\n      fc::uint128_t fee_b = b128 * _pool->withdrawal_fee_percent / GRAPHENE_100_PERCENT;\n      FC_ASSERT( fee_b <= b128, \"Withdrawal fee percent of the pool is too high\" );\n      b128 -= fee_b;\n      FC_ASSERT( a128 > 0 || b128 > 0, \"Aborting due to zero outcome\" );\n      _pool_pays_a = asset( static_cast<int64_t>( a128 ), _pool->asset_a );\n      _pool_pays_b = asset( static_cast<int64_t>( b128 ), _pool->asset_b );\n      _fee_a = asset( static_cast<int64_t>( fee_a ), _pool->asset_a );\n      _fee_b = asset( static_cast<int64_t>( fee_b ), _pool->asset_b );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\ngeneric_exchange_operation_result liquidity_pool_withdraw_evaluator::do_apply(\n      const liquidity_pool_withdraw_operation& op)\n{ try {\n   database& d = db();\n   generic_exchange_operation_result result;\n\n   d.adjust_balance( op.account, -op.share_amount );\n\n   if( _pool_pays_a.amount > 0 )\n      d.adjust_balance( op.account, _pool_pays_a );\n   if( _pool_pays_b.amount > 0 )\n      d.adjust_balance( op.account, _pool_pays_b );\n\n   d.modify( *_share_asset_dyn_data, [&op]( asset_dynamic_data_object& data ){\n      data.current_supply -= op.share_amount.amount;\n   });\n\n   d.modify( *_pool, [this]( liquidity_pool_object& lpo ){\n      lpo.balance_a -= _pool_pays_a.amount;\n      lpo.balance_b -= _pool_pays_b.amount;\n      lpo.update_virtual_value();\n   });\n\n   FC_ASSERT( (_pool->balance_a == 0) == (_pool->balance_b == 0), \"Internal error\" );\n   FC_ASSERT( (_pool->balance_a == 0) == (_share_asset_dyn_data->current_supply == 0), \"Internal error\" );\n\n   result.paid.emplace_back( op.share_amount );\n   result.received.emplace_back( _pool_pays_a );\n   result.received.emplace_back( _pool_pays_b );\n   result.fees.emplace_back( _fee_a );\n   result.fees.emplace_back( _fee_b );\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid_result liquidity_pool_exchange_evaluator::do_evaluate(const liquidity_pool_exchange_operation& op)\n{ try {\n   const database& d = db();\n\n   _pool = &op.pool(d);\n\n   FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, \"The pool has not been initialized\" );\n\n   FC_ASSERT(    ( op.amount_to_sell.asset_id == _pool->asset_a && op.min_to_receive.asset_id == _pool->asset_b )\n              || ( op.amount_to_sell.asset_id == _pool->asset_b && op.min_to_receive.asset_id == _pool->asset_a ),\n              \"Asset type mismatch\" );\n\n\n   const asset_object& asset_obj_a = _pool->asset_a(d);\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, asset_obj_a ),\n              \"The account is unauthorized by asset A\" );\n\n   const asset_object& asset_obj_b = _pool->asset_b(d);\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, asset_obj_b ),\n              \"The account is unauthorized by asset B\" );\n\n   if( HARDFORK_CORE_2350_PASSED( d.head_block_time() ) )\n   {\n      if( !asset_obj_a.options.whitelist_markets.empty() )\n      {\n         FC_ASSERT(    asset_obj_a.options.whitelist_markets.find(_pool->asset_b)\n                    != asset_obj_a.options.whitelist_markets.end(),\n                    \"The ${a}:${b} market has not been whitelisted by asset ${a}\",\n                    (\"a\", asset_obj_a.symbol) (\"b\", asset_obj_b.symbol) );\n      }\n      if( !asset_obj_a.options.blacklist_markets.empty() )\n      {\n         FC_ASSERT(    asset_obj_a.options.blacklist_markets.find(_pool->asset_b)\n                    == asset_obj_a.options.blacklist_markets.end(),\n                    \"The ${a}:${b} market has been blacklisted by asset ${a}\",\n                    (\"a\", asset_obj_a.symbol) (\"b\", asset_obj_b.symbol) );\n      }\n      if( !asset_obj_b.options.whitelist_markets.empty() )\n      {\n         FC_ASSERT(    asset_obj_b.options.whitelist_markets.find(_pool->asset_a)\n                    != asset_obj_b.options.whitelist_markets.end(),\n                    \"The ${a}:${b} market has not been whitelisted by asset ${b}\",\n                    (\"a\", asset_obj_a.symbol) (\"b\", asset_obj_b.symbol) );\n      }\n      if( !asset_obj_b.options.blacklist_markets.empty() )\n      {\n         FC_ASSERT(    asset_obj_b.options.blacklist_markets.find(_pool->asset_a)\n                    == asset_obj_b.options.blacklist_markets.end(),\n                    \"The ${a}:${b} market has been blacklisted by asset ${b}\",\n                    (\"a\", asset_obj_a.symbol) (\"b\", asset_obj_b.symbol) );\n      }\n   }\n\n   _pool_receives_asset = ( op.amount_to_sell.asset_id == _pool->asset_a ? &asset_obj_a : &asset_obj_b );\n\n   _maker_market_fee = d.calculate_market_fee( *_pool_receives_asset, op.amount_to_sell, true );\n   FC_ASSERT( _maker_market_fee < op.amount_to_sell,\n              \"Aborting since the maker market fee of the selling asset is too high\" );\n   _pool_receives = op.amount_to_sell - _maker_market_fee;\n\n   fc::uint128_t delta;\n   if( op.amount_to_sell.asset_id == _pool->asset_a )\n   {\n      share_type new_balance_a = _pool->balance_a + _pool_receives.amount;\n      // round up\n      fc::uint128_t new_balance_b = ( _pool->virtual_value + new_balance_a.value - 1 ) / new_balance_a.value;\n      FC_ASSERT( new_balance_b <= _pool->balance_b, \"Internal error\" );\n      delta = fc::uint128_t( _pool->balance_b.value ) - new_balance_b;\n      _pool_pays_asset = &asset_obj_b;\n   }\n   else\n   {\n      share_type new_balance_b = _pool->balance_b + _pool_receives.amount;\n      // round up\n      fc::uint128_t new_balance_a = ( _pool->virtual_value + new_balance_b.value - 1 ) / new_balance_b.value;\n      FC_ASSERT( new_balance_a <= _pool->balance_a, \"Internal error\" );\n      delta = fc::uint128_t( _pool->balance_a.value ) - new_balance_a;\n      _pool_pays_asset = &asset_obj_a;\n   }\n\n   fc::uint128_t pool_taker_fee = delta * _pool->taker_fee_percent / GRAPHENE_100_PERCENT;\n   FC_ASSERT( pool_taker_fee <= delta, \"Taker fee percent of the pool is too high\" );\n\n   _pool_pays = asset( static_cast<int64_t>( delta - pool_taker_fee ), op.min_to_receive.asset_id );\n\n   _taker_market_fee = d.calculate_market_fee( *_pool_pays_asset, _pool_pays, false );\n   FC_ASSERT( _taker_market_fee <= _pool_pays, \"Market fee should not be greater than the amount to receive\" );\n   _account_receives = _pool_pays - _taker_market_fee;\n\n   GRAPHENE_ASSERT( _account_receives.amount >= op.min_to_receive.amount,\n                    liquidity_pool_exchange_unfillable_price,\n                    \"Unable to exchange at expected price\" );\n\n   _pool_taker_fee = asset( static_cast<int64_t>( pool_taker_fee ), op.min_to_receive.asset_id );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\ngeneric_exchange_operation_result liquidity_pool_exchange_evaluator::do_apply(\n      const liquidity_pool_exchange_operation& op)\n{ try {\n   database& d = db();\n   generic_exchange_operation_result result;\n\n   d.adjust_balance( op.account, -op.amount_to_sell );\n   d.adjust_balance( op.account, _account_receives );\n\n   // For _pool_receives_asset, if market fee sharing is enabled,\n   // the share asset owner's registrar and referrer will get the shared maker market fee.\n   // For _pool_pays_asset, if market fee sharing is enabled,\n   // the trader's registrar and referrer will get the shared taker market fee.\n   d.pay_market_fees( &_pool->share_asset(d).issuer(d), *_pool_receives_asset, op.amount_to_sell, true,\n                      _maker_market_fee );\n   d.pay_market_fees( fee_paying_account, *_pool_pays_asset, _pool_pays, false, _taker_market_fee );\n\n   const auto old_virtual_value = _pool->virtual_value;\n   if( op.amount_to_sell.asset_id == _pool->asset_a )\n   {\n      d.modify( *_pool, [this]( liquidity_pool_object& lpo ){\n         lpo.balance_a += _pool_receives.amount;\n         lpo.balance_b -= _pool_pays.amount;\n         lpo.update_virtual_value();\n      });\n   }\n   else\n   {\n      d.modify( *_pool, [this]( liquidity_pool_object& lpo ){\n         lpo.balance_b += _pool_receives.amount;\n         lpo.balance_a -= _pool_pays.amount;\n         lpo.update_virtual_value();\n      });\n   }\n\n   FC_ASSERT( _pool->balance_a > 0 && _pool->balance_b > 0, \"Internal error\" );\n   FC_ASSERT( _pool->virtual_value >= old_virtual_value, \"Internal error\" );\n\n   result.paid.emplace_back( op.amount_to_sell );\n   result.received.emplace_back( _account_receives );\n   result.fees.emplace_back( _maker_market_fee );\n   result.fees.emplace_back( _taker_market_fee );\n   result.fees.emplace_back( _pool_taker_fee );\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/market_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include <graphene/chain/market_evaluator.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\nnamespace graphene { namespace chain {\nvoid_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op)\n{ try {\n   const database& d = db();\n\n   if( op.extensions.value.on_fill.valid() )\n   {\n      FC_ASSERT( HARDFORK_CORE_2535_PASSED( d.head_block_time() ) ,\n                 \"The on_fill extension is not allowed until the core-2535 hardfork\");\n      FC_ASSERT( 1 == op.extensions.value.on_fill->size(),\n                 \"The on_fill action list must contain only one action until expanded in a future hardfork\" );\n      const auto& take_profit_action = op.extensions.value.on_fill->front().get<create_take_profit_order_action>();\n      FC_ASSERT( d.find( take_profit_action.fee_asset_id ), \"Fee asset does not exist\" );\n   }\n\n   FC_ASSERT( op.expiration >= d.head_block_time() );\n\n   _seller        = this->fee_paying_account;\n   _sell_asset    = &op.amount_to_sell.asset_id(d);\n   _receive_asset = &op.min_to_receive.asset_id(d);\n\n   if( _sell_asset->options.whitelist_markets.size() )\n   {\n      GRAPHENE_ASSERT( _sell_asset->options.whitelist_markets.find(_receive_asset->get_id())\n                          != _sell_asset->options.whitelist_markets.end(),\n                       limit_order_create_market_not_whitelisted,\n                       \"This market has not been whitelisted by the selling asset\", );\n   }\n   if( _sell_asset->options.blacklist_markets.size() )\n   {\n      GRAPHENE_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->get_id())\n                          == _sell_asset->options.blacklist_markets.end(),\n                       limit_order_create_market_blacklisted,\n                       \"This market has been blacklisted by the selling asset\", );\n   }\n\n   GRAPHENE_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ),\n                    limit_order_create_selling_asset_unauthorized,\n                    \"The account is not allowed to transact the selling asset\", );\n\n   GRAPHENE_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ),\n                    limit_order_create_receiving_asset_unauthorized,\n                    \"The account is not allowed to transact the receiving asset\", );\n\n   GRAPHENE_ASSERT( d.get_balance( *_seller, *_sell_asset ) >= op.amount_to_sell,\n                    limit_order_create_insufficient_balance,\n                    \"insufficient balance\",\n                    (\"balance\",d.get_balance(*_seller,*_sell_asset))(\"amount_to_sell\",op.amount_to_sell) );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid limit_order_create_evaluator::convert_fee()\n{\n   if( db().head_block_time() <= HARDFORK_CORE_604_TIME )\n      generic_evaluator::convert_fee();\n   else if( fee_asset->get_id() != asset_id_type() )\n   {\n      db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) {\n         d.fee_pool -= core_fee_paid;\n      });\n   }\n}\n\nvoid limit_order_create_evaluator::pay_fee()\n{\n   if( db().head_block_time() <= HARDFORK_445_TIME )\n      generic_evaluator::pay_fee();\n   else\n   {\n      _deferred_fee = core_fee_paid;\n      if( db().head_block_time() > HARDFORK_CORE_604_TIME && fee_asset->get_id() != asset_id_type() )\n         _deferred_paid_fee = fee_from_account;\n   }\n}\n\nobject_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) const\n{ try {\n   if( op.amount_to_sell.asset_id == asset_id_type() )\n   {\n      db().modify( _seller->statistics(db()), [&op](account_statistics_object& bal) {\n         bal.total_core_in_orders += op.amount_to_sell.amount;\n      });\n   }\n\n   db().adjust_balance(op.seller, -op.amount_to_sell);\n\n   const auto& new_order_object = db().create<limit_order_object>([this,&op](limit_order_object& obj){\n       obj.seller   = _seller->id;\n       obj.for_sale = op.amount_to_sell.amount;\n       obj.sell_price = op.get_price();\n       obj.expiration = op.expiration;\n       obj.deferred_fee = _deferred_fee;\n       obj.deferred_paid_fee = _deferred_paid_fee;\n       if( op.extensions.value.on_fill.valid() )\n          obj.on_fill = *op.extensions.value.on_fill;\n   });\n   object_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it\n   bool filled;\n   if( db().get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_625_TIME )\n      filled = db().apply_order_before_hardfork_625( new_order_object );\n   else\n      filled = db().apply_order( new_order_object );\n\n   GRAPHENE_ASSERT( !op.fill_or_kill || filled,\n                    limit_order_create_kill_unfilled,\n                    \"Killing limit order ${op} due to unable to fill\",\n                    (\"op\",op) );\n\n   return order_id;\n} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE\n\nvoid limit_order_update_evaluator::convert_fee()\n{\n   if( fee_asset->get_id() != asset_id_type() )\n   {\n      db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& addo) {\n         addo.fee_pool -= core_fee_paid;\n      });\n   }\n}\n\nvoid limit_order_update_evaluator::pay_fee()\n{\n   _deferred_fee = core_fee_paid;\n   if( fee_asset->get_id() != asset_id_type() )\n      _deferred_paid_fee = fee_from_account;\n}\n\nvoid_result limit_order_update_evaluator::do_evaluate(const limit_order_update_operation& o)\n{ try {\n   const database& d = db();\n   FC_ASSERT( HARDFORK_CORE_1604_PASSED( d.head_block_time() ) , \"Operation has not activated yet\");\n\n   if( o.on_fill.valid() )\n   {\n      // Note: Assuming that HF core-1604 and HF core-2535 will take place at the same time,\n      //       no check for HF core-2535 here.\n      FC_ASSERT( o.on_fill->size() <= 1,\n                 \"The on_fill action list must contain zero or one action until expanded in a future hardfork\" );\n      if( !o.on_fill->empty() )\n      {\n         const auto& take_profit_action = o.on_fill->front().get<create_take_profit_order_action>();\n         FC_ASSERT( d.find( take_profit_action.fee_asset_id ), \"Fee asset does not exist\" );\n      }\n   }\n\n   _order = d.find( o.order );\n\n   GRAPHENE_ASSERT( _order != nullptr,\n                    limit_order_update_nonexist_order,\n                    \"Limit order ${oid} does not exist, cannot update\",\n                    (\"oid\", o.order) );\n\n   // Check this is my order\n   GRAPHENE_ASSERT( o.seller == _order->seller,\n                    limit_order_update_owner_mismatch,\n                    \"Limit order ${oid} is owned by someone else, cannot update\",\n                    (\"oid\", o.order) );\n\n   // Check new price is compatible and appropriate\n   if (o.new_price) {\n      auto base_id = o.new_price->base.asset_id;\n      auto quote_id = o.new_price->quote.asset_id;\n      FC_ASSERT(base_id == _order->sell_price.base.asset_id && quote_id == _order->sell_price.quote.asset_id,\n                \"Cannot update limit order with incompatible price\");\n\n      // Do not allow inappropriate price manipulation\n      auto max_amount_for_sale = std::max( _order->for_sale, _order->sell_price.base.amount );\n      if( o.delta_amount_to_sell )\n         max_amount_for_sale += o.delta_amount_to_sell->amount;\n      FC_ASSERT( o.new_price->base.amount <= max_amount_for_sale,\n                 \"The base amount in the new price cannot be greater than the estimated maximum amount for sale\" );\n\n   }\n\n   // Check delta asset is compatible\n   if (o.delta_amount_to_sell) {\n      const auto& delta = *o.delta_amount_to_sell;\n      FC_ASSERT(delta.asset_id == _order->sell_price.base.asset_id,\n                \"Cannot update limit order with incompatible asset\");\n      if (delta.amount < 0)\n          FC_ASSERT(_order->for_sale > -delta.amount,\n                    \"Cannot deduct all or more from order than order contains\");\n      // Note: if the delta amount is positive, account balance will be checked when calling adjust_balance()\n   }\n\n   // Check dust\n   if (o.new_price || (o.delta_amount_to_sell && o.delta_amount_to_sell->amount < 0)) {\n      auto new_price = o.new_price? *o.new_price : _order->sell_price;\n      auto new_amount = _order->amount_for_sale();\n      if (o.delta_amount_to_sell)\n          new_amount += *o.delta_amount_to_sell;\n      auto new_amount_to_receive = new_amount * new_price;\n\n      FC_ASSERT( new_amount_to_receive.amount > 0,\n                 \"Cannot update limit order: order becomes too small; cancel order instead\" );\n   }\n\n   // Check expiration is in the future\n   if (o.new_expiration)\n      FC_ASSERT( *o.new_expiration >= d.head_block_time(), \"Cannot update limit order to expire in the past\" );\n\n   // Check asset authorization\n   // TODO refactor to fix duplicate code (see limit_order_create)\n   const auto sell_asset_id = _order->sell_asset_id();\n   const auto receive_asset_id = _order->receive_asset_id();\n   const auto& sell_asset = sell_asset_id(d);\n   const auto& receive_asset = receive_asset_id(d);\n   const auto& seller = *this->fee_paying_account;\n\n   if( !sell_asset.options.whitelist_markets.empty() )\n   {\n      FC_ASSERT( sell_asset.options.whitelist_markets.find( receive_asset_id )\n                          != sell_asset.options.whitelist_markets.end(),\n                 \"This market has not been whitelisted by the selling asset\" );\n   }\n   if( !sell_asset.options.blacklist_markets.empty() )\n   {\n      FC_ASSERT( sell_asset.options.blacklist_markets.find( receive_asset_id )\n                          == sell_asset.options.blacklist_markets.end(),\n                 \"This market has been blacklisted by the selling asset\" );\n   }\n\n   FC_ASSERT( is_authorized_asset( d, seller, sell_asset ),\n              \"The account is not allowed to transact the selling asset\" );\n\n   FC_ASSERT( is_authorized_asset( d, seller, receive_asset ),\n              \"The account is not allowed to transact the receiving asset\" );\n\n   return {};\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid limit_order_update_evaluator::process_deferred_fee()\n{\n   // Attempt to deduct a possibly discounted order cancellation fee, and refund the remainder.\n   // Only deduct fee if there is any fee deferred.\n   // TODO fix duplicate code (see database::cancel_limit_order())\n   if( _order->deferred_fee <= 0 )\n      return;\n\n   database& d = db();\n\n   share_type deferred_fee = _order->deferred_fee;\n   asset deferred_paid_fee = _order->deferred_paid_fee;\n   const asset_dynamic_data_object* deferred_fee_asset_dyn_data = nullptr;\n   const auto& current_fees = d.current_fee_schedule();\n   asset core_cancel_fee = current_fees.calculate_fee( limit_order_cancel_operation() );\n   if( core_cancel_fee.amount > 0 )\n   {\n      // maybe-discounted cancel_fee calculation:\n      //   limit_order_cancel_fee * limit_order_update_fee / limit_order_create_fee\n      asset core_create_fee = current_fees.calculate_fee( limit_order_create_operation() );\n      fc::uint128_t fee128( core_cancel_fee.amount.value );\n      if( core_create_fee.amount > 0 )\n      {\n         asset core_update_fee = current_fees.calculate_fee( limit_order_update_operation() );\n         fee128 *= core_update_fee.amount.value;\n         fee128 /= core_create_fee.amount.value;\n      }\n      // cap the fee\n      if( fee128 > static_cast<uint64_t>( deferred_fee.value ) )\n         fee128 = deferred_fee.value;\n      core_cancel_fee.amount = static_cast<int64_t>( fee128 );\n   }\n\n   // if there is any CORE fee to deduct, redirect it to referral program\n   if( core_cancel_fee.amount > 0 )\n   {\n      if( !_seller_acc_stats )\n         _seller_acc_stats = &d.get_account_stats_by_owner( _order->seller );\n      d.modify( *_seller_acc_stats, [&core_cancel_fee, &d]( account_statistics_object& obj ) {\n         obj.pay_fee( core_cancel_fee.amount, d.get_global_properties().parameters.cashback_vesting_threshold );\n      } );\n      deferred_fee -= core_cancel_fee.amount;\n      // handle originally paid fee if any:\n      //    to_deduct = round_up( paid_fee * core_cancel_fee / deferred_core_fee_before_deduct )\n      if( deferred_paid_fee.amount > 0 )\n      {\n         fc::uint128_t fee128( deferred_paid_fee.amount.value );\n         fee128 *= core_cancel_fee.amount.value;\n         // to round up\n         fee128 += _order->deferred_fee.value;\n         fee128 -= 1;\n         fee128 /= _order->deferred_fee.value;\n         share_type cancel_fee_amount = static_cast<int64_t>(fee128);\n         // cancel_fee should be positive, pay it to asset's accumulated_fees\n         deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(d).dynamic_asset_data_id(d);\n         d.modify( *deferred_fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) {\n            addo.accumulated_fees += cancel_fee_amount;\n         });\n         // cancel_fee should be no more than deferred_paid_fee\n         deferred_paid_fee.amount -= cancel_fee_amount;\n      }\n   }\n\n   // refund fee\n   if( 0 == _order->deferred_paid_fee.amount )\n   {\n      // be here, order.create_time <= HARDFORK_CORE_604_TIME, or fee paid in CORE, or no fee to refund.\n      // if order was created before hard fork 604,\n      //    see it as fee paid in CORE, deferred_fee should be refunded to order owner but not fee pool\n      d.adjust_balance( _order->seller, deferred_fee );\n   }\n   else // need to refund fee in originally paid asset\n   {\n      d.adjust_balance( _order->seller, deferred_paid_fee );\n      // be here, must have: fee_asset != CORE\n      if( !deferred_fee_asset_dyn_data )\n         deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(d).dynamic_asset_data_id(d);\n      d.modify( *deferred_fee_asset_dyn_data, [&deferred_fee](asset_dynamic_data_object& addo) {\n         addo.fee_pool += deferred_fee;\n      });\n   }\n\n}\n\nbool limit_order_update_evaluator::is_linked_tp_order_compatible(const limit_order_update_operation& o) const\n{\n   if( !o.on_fill ) // there is no change to on_fill, so do nothing\n      return true;\n   bool is_compatible = true;\n   if( o.on_fill->empty() )\n   {\n      if( !_order->on_fill.empty() ) // This order's on_fill is being removed\n      {\n         // Two scenarios:\n         // 1. The linked order is generated by this order, now this order's on_fill is being removed, so unlink\n         // 2. This order is generated by the linked order, and \"repeat\" was true, now this order's on_fill is\n         //      being removed, so it becomes incompatible with the linked order, so unlink\n         is_compatible = false;\n      }\n      // else there is no change, nothing to do here\n   }\n   else // o.on_fill is not empty\n   {\n      if( _order->on_fill.empty() )\n      {\n         // It means this order was generated by the linked order, and the linked order's \"repeat\" is false.\n         // We are adding on_fill to this order, so it becomes incompatible with the linked order, so unlink.\n         is_compatible = false;\n      }\n      else // Not empty\n      {\n         // Two scenarios:\n         // 1. Both order's \"repeat\" are true\n         // 2. This order's \"repeat\" was false, and the linked order was generated by this order\n         //\n         // Either way, if \"spread_percent\" or \"repeat\" in on_fill changed, unlink the linked take profit order.\n         const auto& old_take_profit_action = _order->get_take_profit_action();\n         const auto& new_take_profit_action = o.on_fill->front().get<create_take_profit_order_action>();\n         if( old_take_profit_action.spread_percent != new_take_profit_action.spread_percent\n             || old_take_profit_action.repeat != new_take_profit_action.repeat )\n         {\n            is_compatible = false;\n         }\n      } // whether order's on_fill is empty (both handled)\n   } // whether o.on_fill is empty (both handled)\n   return is_compatible;\n};\n\nvoid_result limit_order_update_evaluator::do_apply(const limit_order_update_operation& o)\n{ try {\n   database& d = db();\n\n   // Adjust account balance\n   if( o.delta_amount_to_sell )\n   {\n      d.adjust_balance( o.seller, -*o.delta_amount_to_sell );\n      if( o.delta_amount_to_sell->asset_id == asset_id_type() )\n      {\n         _seller_acc_stats = &d.get_account_stats_by_owner( o.seller );\n         d.modify( *_seller_acc_stats, [&o]( account_statistics_object& bal ) {\n            bal.total_core_in_orders += o.delta_amount_to_sell->amount;\n         });\n      }\n   }\n\n   // Process deferred fee in the order.\n   process_deferred_fee();\n\n   // Process linked take profit order\n   bool unlink = false;\n   if( _order->take_profit_order_id.valid() )\n   {\n      // If price changed, unless it is triggered by on_fill of the linked take profit order,\n      //   unlink the linked take profit order.\n      if( !trx_state->skip_limit_order_price_check\n            && o.new_price.valid() && *o.new_price != _order->sell_price )\n      {\n         unlink = true;\n      }\n      // If on_fill changed and the order became incompatible with the linked order, unlink\n      else\n         unlink = !is_linked_tp_order_compatible( o );\n\n      // Now update database\n      if( unlink )\n      {\n         const auto& take_profit_order = (*_order->take_profit_order_id)(d);\n         d.modify( take_profit_order, []( limit_order_object& loo ) {\n            loo.take_profit_order_id.reset();\n         });\n      }\n   }\n\n   // Update order\n   d.modify(*_order, [&o,this,unlink](limit_order_object& loo) {\n      if (o.new_price)\n         loo.sell_price = *o.new_price;\n      if (o.delta_amount_to_sell)\n         loo.for_sale += o.delta_amount_to_sell->amount;\n      if (o.new_expiration)\n         loo.expiration = *o.new_expiration;\n      loo.deferred_fee = _deferred_fee;\n      loo.deferred_paid_fee = _deferred_paid_fee;\n      if( o.on_fill )\n         loo.on_fill= *o.on_fill;\n      if( unlink )\n         loo.take_profit_order_id.reset();\n   });\n\n   // Perform order matching if necessary\n   d.apply_order(*_order);\n\n   return {};\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o)\n{ try {\n   const database& d = db();\n\n   _order = d.find( o.order );\n\n   GRAPHENE_ASSERT( _order != nullptr,\n                    limit_order_cancel_nonexist_order,\n                    \"Limit order ${oid} does not exist\",\n                    (\"oid\", o.order) );\n\n   GRAPHENE_ASSERT( _order->seller == o.fee_paying_account,\n                    limit_order_cancel_owner_mismatch,\n                    \"Limit order ${oid} is owned by someone else\",\n                    (\"oid\", o.order) );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nasset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) const\n{ try {\n   database& d = db();\n\n   auto base_asset = _order->sell_price.base.asset_id;\n   auto quote_asset = _order->sell_price.quote.asset_id;\n   auto refunded = _order->amount_for_sale();\n\n   d.cancel_limit_order( *_order, false ); // don't create a virtual op\n\n   if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_606_TIME )\n   {\n      // Possible optimization:\n      // order can be called by canceling a limit order if the canceled order was at the top of the book.\n      // Do I need to check calls in both assets?\n      d.check_call_orders(base_asset(d));\n      d.check_call_orders(quote_asset(d));\n   }\n\n   return refunded;\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o)\n{ try {\n   const database& d = db();\n\n   auto next_maintenance_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   // Note: funding_account is the fee payer thus exists in the database\n   _debt_asset     = &o.delta_debt.asset_id(d);\n   FC_ASSERT( _debt_asset->is_market_issued(), \"Unable to cover ${sym} as it is not a collateralized asset.\",\n              (\"sym\", _debt_asset->symbol) );\n\n   FC_ASSERT( o.delta_debt.amount <= 0 || _debt_asset->can_create_new_supply(), \"Can not create new supply\" );\n\n   _dynamic_data_obj = &_debt_asset->dynamic_asset_data_id(d);\n\n   /***\n    * There are instances of assets exceeding max_supply before hf 1465, therefore this code must remain.\n    */\n   if (next_maintenance_time > HARDFORK_CORE_1465_TIME)\n   {\n      FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount <= _debt_asset->options.max_supply,\n            \"Borrowing this quantity would exceed MAX_SUPPLY\" );\n   }\n\n   FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount >= 0,\n         \"This transaction would bring current supply below zero.\");\n\n   _bitasset_data  = &_debt_asset->bitasset_data(d);\n\n   /// if there is a settlement for this asset, then no further margin positions may be taken and\n   /// all existing margin positions should have been closed va database::globally_settle_asset\n   FC_ASSERT( !_bitasset_data->is_globally_settled(),\n              \"Cannot update debt position when the asset has been globally settled\" );\n\n   FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset,\n              \"Collateral asset type should be same as backing asset of debt asset\" );\n\n   auto& call_idx = d.get_index_type<call_order_index>().indices().get<by_account>();\n   auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) );\n   if( itr != call_idx.end() ) // updating or closing debt position\n   {\n      call_ptr = &(*itr);\n      new_collateral = call_ptr->collateral + o.delta_collateral.amount;\n      new_debt = call_ptr->debt + o.delta_debt.amount;\n      if( new_debt == 0 )\n      {\n         FC_ASSERT( new_collateral == 0, \"Should claim all collateral when closing debt position\" );\n          _closing_order = true;\n      }\n      else\n      {\n         FC_ASSERT( new_collateral > 0 && new_debt > 0,\n                    \"Both collateral and debt should be positive after updated a debt position if not to close it\" );\n      }\n   }\n   else // creating new debt position\n   {\n      FC_ASSERT( o.delta_collateral.amount > 0, \"Delta collateral amount of new debt position should be positive\" );\n      FC_ASSERT( o.delta_debt.amount > 0, \"Delta debt amount of new debt position should be positive\" );\n   }\n\n   if( _bitasset_data->is_prediction_market )\n      FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount,\n                 \"Debt amount and collateral amount should be same when updating debt position in a prediction \"\n                 \"market\" );\n   else if( _bitasset_data->current_feed.settlement_price.is_null()\n            && !( HARDFORK_CORE_2467_PASSED( next_maintenance_time ) && _closing_order ) )\n      FC_THROW_EXCEPTION(insufficient_feeds, \"Cannot borrow asset with no price feed.\");\n\n   // Since hard fork core-973, check asset authorization limitations\n   if( HARDFORK_CORE_973_PASSED(d.head_block_time()) )\n   {\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_debt_asset ),\n                 \"The account is not allowed to transact the debt asset\" );\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _bitasset_data->options.short_backing_asset(d) ),\n                 \"The account is not allowed to transact the collateral asset\" );\n   }\n\n   // Note: there was code here checking whether the account has enough balance to increase delta collateral,\n   //       which is now removed since the check is implicitly done later by `adjust_balance()` in `do_apply()`.\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n\nobject_id_type call_order_update_evaluator::do_apply(const call_order_update_operation& o)\n{ try {\n   database& d = db();\n\n   if( o.delta_debt.amount != 0 )\n   {\n      d.adjust_balance( o.funding_account, o.delta_debt );\n\n      // Deduct the debt paid from the total supply of the debt asset.\n      d.modify(*_dynamic_data_obj, [&o](asset_dynamic_data_object& dynamic_asset) {\n         dynamic_asset.current_supply += o.delta_debt.amount;\n      });\n   }\n\n   if( o.delta_collateral.amount != 0 )\n   {\n      d.adjust_balance( o.funding_account, -o.delta_collateral  );\n\n      // Adjust the total core in orders accodingly\n      if( o.delta_collateral.asset_id == asset_id_type() )\n      {\n         d.modify( d.get_account_stats_by_owner( o.funding_account ), [&o](account_statistics_object& stats) {\n               stats.total_core_in_orders += o.delta_collateral.amount;\n         });\n      }\n   }\n\n   if( _closing_order ) // closing the debt position\n   {\n      auto call_order_id = call_ptr->id;\n\n      d.remove( *call_ptr );\n\n      // Update current_feed if needed\n      const auto bsrm = _bitasset_data->get_black_swan_response_method();\n      if( bitasset_options::black_swan_response_type::no_settlement == bsrm )\n      {\n         auto old_feed_price = _bitasset_data->current_feed.settlement_price;\n         d.update_bitasset_current_feed( *_bitasset_data, true );\n         if( !_bitasset_data->current_feed.settlement_price.is_null()\n               && _bitasset_data->current_feed.settlement_price != old_feed_price )\n         {\n            d.check_call_orders( *_debt_asset, true, false, _bitasset_data );\n         }\n      }\n\n      return call_order_id;\n   }\n\n   const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n   bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue\n\n   optional<price> old_collateralization;\n   optional<share_type> old_debt;\n\n   if( !call_ptr ) // creating new debt position\n   {\n      call_ptr = &d.create<call_order_object>( [&o,this,before_core_hardfork_1270]( call_order_object& call ){\n         call.borrower = o.funding_account;\n         call.collateral = o.delta_collateral.amount;\n         call.debt = o.delta_debt.amount;\n         if( before_core_hardfork_1270 ) // before core-1270 hard fork, calculate call_price here and cache it\n            call.call_price = price::call_price( o.delta_debt, o.delta_collateral,\n                                                 _bitasset_data->current_feed.maintenance_collateral_ratio );\n         else // after core-1270 hard fork, set call_price to 1\n            call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) );\n         call.target_collateral_ratio = o.extensions.value.target_collateral_ratio;\n      });\n   }\n   else // updating existing debt position\n   {\n      old_collateralization = call_ptr->collateralization();\n      old_debt = call_ptr->debt;\n\n      d.modify( *call_ptr, [&o,this,before_core_hardfork_1270]( call_order_object& call ){\n         call.collateral = new_collateral;\n         call.debt       = new_debt;\n         if( before_core_hardfork_1270 ) // don't update call_price after core-1270 hard fork\n         {\n            call.call_price  =  price::call_price( call.get_debt(), call.get_collateral(),\n                                                   _bitasset_data->current_feed.maintenance_collateral_ratio );\n         }\n         call.target_collateral_ratio = o.extensions.value.target_collateral_ratio;\n      });\n   }\n\n   object_id_type call_order_id = call_ptr->id;\n\n   if( _bitasset_data->is_prediction_market )\n      return call_order_id;\n\n   // then we must check for margin calls and other issues\n\n   // After hf core-2481, we do not allow new position's CR to be <= ~max_short_squeeze_price, because\n   // * if there is no force settlement order, it would trigger a blackswan event instantly,\n   // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the\n   //   force settlement order.\n   auto call_collateralization = call_ptr->collateralization();\n   bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt\n                                                        && call_collateralization > *old_collateralization );\n   if( HARDFORK_CORE_2481_PASSED( next_maint_time ) )\n   {\n      // Note: if it is to increase CR and is not increasing debt amount, it is allowed,\n      //       because it implies BSRM == no_settlement\n      FC_ASSERT( increasing_cr\n                 || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ),\n                 \"Could not create a debt position which would trigger a blackswan event instantly, \"\n                 \"unless it is to increase collateral ratio of an existing debt position and \"\n                 \"is not increasing its debt amount\" );\n   }\n   // Update current_feed if needed\n   const auto bsrm = _bitasset_data->get_black_swan_response_method();\n   if( bitasset_options::black_swan_response_type::no_settlement == bsrm )\n      d.update_bitasset_current_feed( *_bitasset_data, true );\n\n   // check to see if the order needs to be margin called now, but don't allow black swans and require there to be\n   // limit orders available that could be used to fill the order.\n   // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork,\n   //       the first call order may be unable to be updated if the second one is undercollateralized.\n   // Note: check call orders, don't allow black swan, not for new limit order\n   bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data );\n   call_ptr = d.find<call_order_object>(call_order_id);\n   if( called_some )\n   {\n      // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled.\n      // after hard fork core-583: we want to allow increasing collateral\n      //   Note: increasing collateral won't get the call order itself matched (instantly margin called)\n      //   if there is at least a call order get matched but didn't cause a black swan event,\n      //   current order must have got matched. in this case, it's OK if it's totally filled.\n      // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched\n      //   in check_call_orders, also possible that increasing CR will get the call order itself matched\n      if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf\n      {\n         GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call,\n                          \"Updating call order would trigger a margin call that cannot be fully filled\" );\n      }\n      // after core-2467 hf\n      else\n      {\n         // if the call order is totally filled, it is OK,\n         // if it is increasing CR, it is always ok, no matter if it or another another call order is called,\n         // otherwise, the remaining call order's CR need to be > ICR\n         // TODO: perhaps it makes sense to allow more cases, e.g.\n         //       - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR\n         //       - allow owners to sell collateral at price < MSSP (need to update code elsewhere)\n         FC_ASSERT( !call_ptr || increasing_cr\n                    || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization,\n                    \"Could not create a debt position which would trigger a margin call instantly, \"\n                    \"unless the debt position is fully filled, or it is to increase collateral ratio of \"\n                    \"an existing debt position and is not increasing its debt amount, \"\n                    \"or the remaining debt position's collateral ratio is above required \"\n                    \"initial collateral ratio (ICR)\" );\n      }\n   }\n   else\n   {\n      // we know no black swan event has occurred\n      FC_ASSERT( call_ptr, \"no margin call was executed and yet the call object was deleted\" );\n      // this HF must remain as-is, as the assert inside the \"if\" was triggered during push_proposal()\n      if( d.head_block_time() <= HARDFORK_CORE_583_TIME )\n      {\n         // We didn't fill any call orders.  This may be because we\n         // aren't in margin call territory, or it may be because there\n         // were no matching orders.  In the latter case, we throw.\n         GRAPHENE_ASSERT(\n            // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here\n            ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price,\n            call_order_update_unfilled_margin_call,\n            \"Updating call order would trigger a margin call that cannot be fully filled\",\n            // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here\n            (\"a\", ~call_ptr->call_price )(\"b\", _bitasset_data->current_feed.settlement_price)\n            );\n      }\n      else // after hard fork core-583, always allow call order to be updated if collateral ratio\n           // is increased and debt is not increased\n      {\n         // We didn't fill any call orders.  This may be because we\n         // aren't in margin call territory, or it may be because there\n         // were no matching orders. In the latter case,\n         // if collateral ratio is not increased or debt is increased, we throw.\n         // be here, we know no margin call was executed,\n         // so call_obj's collateral ratio should be set only by op\n         // ------\n         // Before BSIP77, CR of the new/updated position is required to be above MCR.\n         // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR).\n         // The `current_initial_collateralization` variable has been initialized according to the logic,\n         // so we directly use it here.\n         bool ok = increasing_cr;\n         if( !ok )\n            ok = before_core_hardfork_1270 ?\n                         ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price )\n                       : ( call_collateralization > _bitasset_data->current_initial_collateralization );\n         FC_ASSERT( ok,\n            \"Can only increase collateral ratio without increasing debt when the debt position's \"\n            \"collateral ratio is lower than or equal to required initial collateral ratio (ICR), \"\n            \"if not to trigger a margin call immediately\",\n            (\"old_debt\", old_debt)\n            (\"new_debt\", call_ptr->debt)\n            (\"old_collateralization\", old_collateralization)\n            (\"new_collateralization\", call_collateralization)\n            );\n      }\n   }\n\n   return call_order_id;\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation& o)\n{ try {\n   const database& d = db();\n\n   // TODO cleanup: remove the assertion and related test cases after hardfork\n   FC_ASSERT( d.head_block_time() > HARDFORK_CORE_216_TIME, \"Not yet!\" );\n\n   // Note: bidder is the fee payer thus exists in the database\n   _debt_asset     = &o.debt_covered.asset_id(d);\n   FC_ASSERT( _debt_asset->is_market_issued(), \"Unable to cover ${sym} as it is not a collateralized asset.\",\n              (\"sym\", _debt_asset->symbol) );\n\n   const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n   // Note: due to old bugs, an asset can have the flag set before the hardfork, so we need the hardfork check here\n   // TODO review after hardfork to see if we can remove the check\n   if( HARDFORK_CORE_2281_PASSED( next_maint_time ) )\n      FC_ASSERT( _debt_asset->can_bid_collateral(), \"Collateral bidding is disabled for this asset\" );\n\n   _bitasset_data  = &_debt_asset->bitasset_data(d);\n\n   FC_ASSERT( _bitasset_data->is_globally_settled(), \"Cannot bid since the asset is not globally settled\" );\n\n   FC_ASSERT( o.additional_collateral.asset_id == _bitasset_data->options.short_backing_asset );\n\n   FC_ASSERT( !_bitasset_data->is_prediction_market, \"Cannot bid on a prediction market!\" );\n\n   const collateral_bid_index& bids = d.get_index_type<collateral_bid_index>();\n   const auto& index = bids.indices().get<by_account>();\n   const auto& bid = index.find( boost::make_tuple( o.debt_covered.asset_id, o.bidder ) );\n   if( bid != index.end() )\n      _bid = &(*bid);\n   else\n       FC_ASSERT( o.debt_covered.amount > 0, \"Can't find bid to cancel?!\");\n\n   if( o.additional_collateral.amount > 0 )\n   {\n      auto collateral_balance = d.get_balance( o.bidder, _bitasset_data->options.short_backing_asset );\n      if( _bid && d.head_block_time() >= HARDFORK_CORE_1692_TIME ) // TODO: see if HF check can be removed after HF\n      {\n         asset delta = o.additional_collateral - _bid->get_additional_collateral();\n         FC_ASSERT( collateral_balance >= delta,\n                    \"Cannot increase bid from ${oc} to ${nc} collateral when payer only has ${b}\",\n                    (\"oc\", _bid->get_additional_collateral().amount)(\"nc\", o.additional_collateral.amount)\n                    (\"b\", collateral_balance.amount) );\n      } else\n         FC_ASSERT( collateral_balance >= o.additional_collateral,\n                    \"Cannot bid ${c} collateral when payer only has ${b}\", (\"c\", o.additional_collateral.amount)\n                    (\"b\", collateral_balance.amount) );\n   }\n\n   // Since hard fork core-973, check asset authorization limitations\n   if( HARDFORK_CORE_973_PASSED(d.head_block_time()) )\n   {\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_debt_asset ),\n                 \"The account is not allowed to transact the debt asset\" );\n      FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _bitasset_data->options.short_backing_asset(d) ),\n                 \"The account is not allowed to transact the collateral asset\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n\nvoid_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o) const\n{ try {\n   database& d = db();\n\n   if( _bid )\n      d.cancel_bid( *_bid, false );\n\n   if( o.debt_covered.amount == 0 ) return void_result();\n\n   d.adjust_balance( o.bidder, -o.additional_collateral  );\n\n   d.create<collateral_bid_object>([&o]( collateral_bid_object& bid ) {\n      bid.bidder = o.bidder;\n      bid.inv_swan_price = o.additional_collateral / o.debt_covered;\n   });\n\n   // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/market_object.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/market_object.hpp>\n\n#include <boost/multiprecision/cpp_int.hpp>\n\n#include <functional>\n\n#include <fc/io/raw.hpp>\n\nusing namespace graphene::chain;\n\n/*\ntarget_CR = max( target_CR, MCR )\n\ntarget_CR = new_collateral / ( new_debt / feed_price )\n          = ( collateral - max_amount_to_sell ) * feed_price\n            / ( debt - amount_to_get )\n          = ( collateral - max_amount_to_sell ) * feed_price\n            / ( debt - round_down(max_amount_to_sell * match_price ) )\n          = ( collateral - max_amount_to_sell ) * feed_price\n            / ( debt - (max_amount_to_sell * match_price - x) )\n\nNote: x is the fraction, 0 <= x < 1\n\n=>\n\nmax_amount_to_sell = ( (debt + x) * target_CR - collateral * feed_price )\n                     / (target_CR * match_price - feed_price)\n                   = ( (debt + x) * tCR / DENOM - collateral * fp_debt_amt / fp_coll_amt )\n                     / ( (tCR / DENOM) * (mp_debt_amt / mp_coll_amt) - fp_debt_amt / fp_coll_amt )\n                   = ( (debt + x) * tCR * fp_coll_amt * mp_coll_amt - collateral * fp_debt_amt * DENOM * mp_coll_amt)\n                     / ( tCR * mp_debt_amt * fp_coll_amt - fp_debt_amt * DENOM * mp_coll_amt )\n\nmax_debt_to_cover = max_amount_to_sell * match_price\n                  = max_amount_to_sell * mp_debt_amt / mp_coll_amt\n                  = ( (debt + x) * tCR * fp_coll_amt * mp_debt_amt - collateral * fp_debt_amt * DENOM * mp_debt_amt)\n                    / (tCR * mp_debt_amt * fp_coll_amt - fp_debt_amt * DENOM * mp_coll_amt)\n*/\nshare_type call_order_object::get_max_debt_to_cover( price match_price,\n                                                     price feed_price,\n                                                     const uint16_t maintenance_collateral_ratio,\n                                                     const optional<price>& maintenance_collateralization )const\n{ try {\n   // be defensive here, make sure feed_price is in collateral / debt format\n   if( feed_price.base.asset_id != call_price.base.asset_id )\n      feed_price = ~feed_price;\n\n   FC_ASSERT( feed_price.base.asset_id == call_price.base.asset_id\n              && feed_price.quote.asset_id == call_price.quote.asset_id );\n\n   bool after_core_hardfork_1270 = maintenance_collateralization.valid();\n\n   // be defensive here, make sure maintenance_collateralization is in collateral / debt format\n   if( after_core_hardfork_1270 )\n   {\n      FC_ASSERT( maintenance_collateralization->base.asset_id == call_price.base.asset_id\n                 && maintenance_collateralization->quote.asset_id == call_price.quote.asset_id );\n   }\n\n   // According to the feed protection rule (https://github.com/cryptonomex/graphene/issues/436),\n   // a call order should only be called when its collateral ratio is not higher than required\n   // maintenance collateral ratio.\n   // Although this should be guaranteed by the caller of this function, we still check here to be defensive.\n   // Theoretically this check can be skipped for better performance.\n   //\n   // Before core-1270 hard fork, we check with call_price; afterwards, we check with collateralization().\n   if( ( !after_core_hardfork_1270 && call_price > feed_price )\n       || ( after_core_hardfork_1270 && collateralization() > *maintenance_collateralization ) )\n      return 0;\n\n   if( !target_collateral_ratio.valid() ) // target cr is not set\n      return debt;\n\n   // use mcr if target cr is too small\n   uint16_t tcr = std::max( *target_collateral_ratio, maintenance_collateral_ratio );\n\n   price target_collateralization = ( after_core_hardfork_1270 ?\n                                      feed_price * ratio_type( tcr, GRAPHENE_COLLATERAL_RATIO_DENOM ) :\n                                      price() );\n\n   // be defensive here, make sure match_price is in collateral / debt format\n   if( match_price.base.asset_id != call_price.base.asset_id )\n      match_price = ~match_price;\n\n   FC_ASSERT( match_price.base.asset_id == call_price.base.asset_id\n              && match_price.quote.asset_id == call_price.quote.asset_id );\n\n   using i256 = boost::multiprecision::int256_t;\n   i256 mp_debt_amt = match_price.quote.amount.value;\n   i256 mp_coll_amt = match_price.base.amount.value;\n   i256 fp_debt_amt = feed_price.quote.amount.value;\n   i256 fp_coll_amt = feed_price.base.amount.value;\n\n   // firstly we calculate without the fraction (x), the result could be a bit too small\n   i256 numerator = fp_coll_amt * mp_debt_amt * debt.value * tcr\n                  - fp_debt_amt * mp_debt_amt * collateral.value * GRAPHENE_COLLATERAL_RATIO_DENOM;\n   if( numerator < 0 ) // feed protected, actually should not be true here, just check to be safe\n      return 0;\n\n   i256 denominator = fp_coll_amt * mp_debt_amt * tcr - fp_debt_amt * mp_coll_amt * GRAPHENE_COLLATERAL_RATIO_DENOM;\n   if( denominator <= 0 ) // black swan\n      return debt;\n\n   // note: if add 1 here, will result in 1.5x imperfection rate;\n   //       however, due to rounding, the result could still be a bit too big, thus imperfect.\n   i256 to_cover_i256 = ( numerator / denominator );\n   if( to_cover_i256 >= debt.value ) // avoid possible overflow\n      return debt;\n   share_type to_cover_amt = static_cast< int64_t >( to_cover_i256 );\n\n   // stabilize\n   // note: rounding up-down results in 3x imperfection rate in comparison to down-down-up\n   asset to_pay = asset( to_cover_amt, debt_type() ) * match_price;\n   asset to_cover = to_pay * match_price;\n   to_pay = to_cover.multiply_and_round_up( match_price );\n\n   if( to_cover.amount >= debt || to_pay.amount >= collateral ) // to be safe\n      return debt;\n   FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt );\n\n   // Check whether the collateral ratio after filled is high enough\n   // Before core-1270 hard fork, we check with call_price; afterwards, we check with collateralization().\n   std::function<bool()> result_is_good = after_core_hardfork_1270 ?\n      std::function<bool()>( [this,&to_cover,&to_pay,target_collateralization]() -> bool\n      {\n         price new_collateralization = ( get_collateral() - to_pay ) / ( get_debt() - to_cover );\n         return ( new_collateralization > target_collateralization );\n      }) :\n      std::function<bool()>( [this,&to_cover,&to_pay,tcr,feed_price]() -> bool\n      {\n         price new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr );\n         return ( new_call_price > feed_price );\n      });\n\n   // if the result is good, we return.\n   if( result_is_good() )\n      return to_cover.amount;\n\n   // be here, to_cover is too small due to rounding. deal with the fraction\n   numerator += fp_coll_amt * mp_debt_amt * tcr; // plus the fraction\n   to_cover_i256 = ( numerator / denominator ) + 1;\n   if( to_cover_i256 >= debt.value ) // avoid possible overflow\n      to_cover_i256 = debt.value;\n   to_cover_amt = static_cast< int64_t >( to_cover_i256 );\n\n   asset max_to_pay = ( ( to_cover_amt == debt.value ) ? get_collateral()\n                        : asset( to_cover_amt, debt_type() ).multiply_and_round_up( match_price ) );\n   if( max_to_pay.amount > collateral )\n      max_to_pay.amount = collateral;\n\n   asset max_to_cover = ( ( max_to_pay.amount == collateral ) ? get_debt() : ( max_to_pay * match_price ) );\n   if( max_to_cover.amount >= debt ) // to be safe\n   {\n      max_to_pay.amount = collateral;\n      max_to_cover.amount = debt;\n   }\n\n   if( max_to_pay <= to_pay || max_to_cover <= to_cover ) // strange data. should skip binary search and go on,\n                                                          // but doesn't help much\n      return debt;\n   FC_ASSERT( max_to_pay > to_pay && max_to_cover > to_cover );\n\n   asset min_to_pay = to_pay;\n   asset min_to_cover = to_cover;\n\n   // try with binary search to find a good value\n   // note: actually binary search can not always provide perfect result here,\n   //       due to rounding, collateral ratio is not always increasing while to_pay or to_cover is increasing\n   bool max_is_ok = false;\n   while( true )\n   {\n      // get the mean\n      if( match_price.base.amount < match_price.quote.amount ) // step of collateral is smaller\n      {\n         to_pay.amount = ( min_to_pay.amount + max_to_pay.amount + 1 ) / 2; // should not overflow. round up here\n         if( to_pay.amount == max_to_pay.amount )\n            to_cover.amount = max_to_cover.amount;\n         else\n         {\n            to_cover = to_pay * match_price;\n            if( to_cover.amount >= max_to_cover.amount ) // can be true when max_is_ok is false\n            {\n               to_pay.amount = max_to_pay.amount;\n               to_cover.amount = max_to_cover.amount;\n            }\n            else\n            {\n               to_pay = to_cover.multiply_and_round_up( match_price ); // stabilization, no change or become smaller\n               FC_ASSERT( to_pay.amount < max_to_pay.amount );\n            }\n         }\n      }\n      else // step of debt is smaller or equal\n      {\n         to_cover.amount = ( min_to_cover.amount + max_to_cover.amount ) / 2; // should not overflow. round down here\n         if( to_cover.amount == max_to_cover.amount )\n            to_pay.amount = max_to_pay.amount;\n         else\n         {\n            to_pay = to_cover.multiply_and_round_up( match_price );\n            if( to_pay.amount >= max_to_pay.amount ) // can be true when max_is_ok is false\n            {\n               to_pay.amount = max_to_pay.amount;\n               to_cover.amount = max_to_cover.amount;\n            }\n            else\n            {\n               to_cover = to_pay * match_price; // stabilization, to_cover should have increased\n               if( to_cover.amount >= max_to_cover.amount ) // to be safe\n               {\n                  to_pay.amount = max_to_pay.amount;\n                  to_cover.amount = max_to_cover.amount;\n               }\n            }\n         }\n      }\n\n      // check again to see if we've moved away from the minimums, if not, use the maximums directly\n      if( to_pay.amount <= min_to_pay.amount || to_cover.amount <= min_to_cover.amount\n            || to_pay.amount > max_to_pay.amount || to_cover.amount > max_to_cover.amount )\n      {\n         to_pay.amount = max_to_pay.amount;\n         to_cover.amount = max_to_cover.amount;\n      }\n\n      // check the mean\n      if( to_pay.amount == max_to_pay.amount && ( max_is_ok || to_pay.amount == collateral ) )\n         return to_cover.amount;\n      FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt );\n\n      // Check whether the result is good\n      if( result_is_good() ) // good\n      {\n         if( to_pay.amount == max_to_pay.amount )\n            return to_cover.amount;\n         max_to_pay.amount = to_pay.amount;\n         max_to_cover.amount = to_cover.amount;\n         max_is_ok = true;\n      }\n      else // not good\n      {\n         if( to_pay.amount == max_to_pay.amount )\n            break;\n         min_to_pay.amount = to_pay.amount;\n         min_to_cover.amount = to_cover.amount;\n      }\n   }\n\n   // be here, max_to_cover is too small due to rounding. search forward\n   for( uint64_t d1 = 0, d2 = 1, d3 = 1; ; d1 = d2, d2 = d3, d3 = d1 + d2 ) // 1,1,2,3,5,8,...\n   {\n      if( match_price.base.amount > match_price.quote.amount ) // step of debt is smaller\n      {\n         to_pay.amount += d2;\n         if( to_pay.amount >= collateral )\n            return debt;\n         to_cover = to_pay * match_price;\n         if( to_cover.amount >= debt )\n            return debt;\n         to_pay = to_cover.multiply_and_round_up( match_price ); // stabilization\n         if( to_pay.amount >= collateral )\n            return debt;\n      }\n      else // step of collateral is smaller or equal\n      {\n         to_cover.amount += d2;\n         if( to_cover.amount >= debt )\n            return debt;\n         to_pay = to_cover.multiply_and_round_up( match_price );\n         if( to_pay.amount >= collateral )\n            return debt;\n         to_cover = to_pay * match_price; // stabilization\n         if( to_cover.amount >= debt )\n            return debt;\n      }\n\n      // defensive check\n      FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt );\n\n      // Check whether the result is good\n      if( result_is_good() ) // good\n         return to_cover.amount;\n   }\n\n} FC_CAPTURE_AND_RETHROW( (*this)(feed_price)(match_price)(maintenance_collateral_ratio) ) }\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::limit_order_object,\n                    (graphene::db::object),\n                    (expiration)(seller)(for_sale)(sell_price)(filled_amount)(deferred_fee)(deferred_paid_fee)\n                    (is_settled_debt)(on_fill)(take_profit_order_id)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::call_order_object, (graphene::db::object),\n                    (borrower)(collateral)(debt)(call_price)(target_collateral_ratio) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::force_settlement_object,\n                    (graphene::db::object),\n                    (owner)(balance)(settlement_date)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::collateral_bid_object, (graphene::db::object),\n                    (bidder)(inv_swan_price) )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::limit_order_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::call_order_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::force_settlement_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::collateral_bid_object )\n"
  },
  {
    "path": "libraries/chain/proposal_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015-2018 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/proposal_evaluator.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n\nnamespace graphene { namespace chain {\n\nnamespace detail {\n   void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options);\n\n   void check_asset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const asset_options& options);\n   void check_bitasset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const bitasset_options& options);\n   void check_asset_update_extensions_hf_bsip_48_75( const fc::time_point_sec& block_time,\n                                                     const asset_update_operation::ext& extensions );\n\n   void check_asset_publish_feed_extensions_hf_bsip77( const fc::time_point_sec& block_time,\n                                                       const asset_publish_feed_operation::ext& extensions );\n   void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options);\n\n   void check_bitasset_options_hf_bsip74(const fc::time_point_sec& block_time,\n                                         const bitasset_options& options); // HF_REMOVABLE\n\n   void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options);\n\n   void check_bitasset_options_hf_bsip87(const fc::time_point_sec& block_time,\n                                         const bitasset_options& options); // HF_REMOVABLE\n\n   void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time,\n                                                        const asset_claim_fees_operation& op); // HF_REMOVABLE\n\n   void check_asset_options_hf_core2281(const fc::time_point_sec& next_maint_time, const asset_options& options);\n   void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options);\n   void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options);\n}\n\nstruct proposal_operation_hardfork_visitor\n{\n   typedef void result_type;\n   const database& db;\n   const fc::time_point_sec block_time;\n   const fc::time_point_sec next_maintenance_time;\n\n   proposal_operation_hardfork_visitor( const database& _db, const fc::time_point_sec bt )\n   : db( _db ), block_time(bt), next_maintenance_time( db.get_dynamic_global_properties().next_maintenance_time ) {}\n\n   template<typename T>\n   void operator()(const T &v) const {}\n\n   // TODO review and cleanup code below after hard fork\n   // hf_1604\n   void operator()(const graphene::chain::limit_order_update_operation &) const {\n      FC_ASSERT( HARDFORK_CORE_1604_PASSED(block_time), \"Operation is not enabled yet\" );\n   }\n\n   // hf_2535\n   void operator()(const graphene::chain::limit_order_create_operation& op) const {\n      if( !HARDFORK_CORE_2535_PASSED(block_time) ) {\n         FC_ASSERT( !op.extensions.value.on_fill.valid(),\n                    \"The on_fill extension is not allowed until the core-2535 hardfork\");\n      }\n   }\n\n   void operator()(const graphene::chain::asset_create_operation &v) const {\n      detail::check_asset_options_hf_1774(block_time, v.common_options);\n      detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options);\n      detail::check_asset_options_hf_bsip81(block_time, v.common_options);\n      detail::check_asset_options_hf_core2281( next_maintenance_time, v.common_options ); // HF_REMOVABLE\n      detail::check_asset_options_hf_core2467( next_maintenance_time, v.common_options ); // HF_REMOVABLE\n      if( v.bitasset_opts.valid() ) {\n         detail::check_bitasset_options_hf_bsip_48_75( block_time, *v.bitasset_opts );\n         detail::check_bitasset_options_hf_bsip74( block_time, *v.bitasset_opts ); // HF_REMOVABLE\n         detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts ); // HF_REMOVABLE\n         detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE\n         detail::check_bitasset_opts_hf_core2467( next_maintenance_time, *v.bitasset_opts ); // HF_REMOVABLE\n      }\n\n      // TODO move as many validations as possible to validate() if not triggered before hardfork\n      if( HARDFORK_CORE_2281_PASSED( next_maintenance_time ) )\n      {\n         v.common_options.validate_flags( v.bitasset_opts.valid() );\n      }\n      else if( HARDFORK_BSIP_48_75_PASSED( block_time ) )\n      {\n         // do not allow the 'disable_collateral_bidding' bit\n         v.common_options.validate_flags( v.bitasset_opts.valid(), false );\n      }\n   }\n\n   void operator()(const graphene::chain::asset_update_operation &v) const {\n      detail::check_asset_options_hf_1774(block_time, v.new_options);\n      detail::check_asset_options_hf_bsip_48_75(block_time, v.new_options);\n      detail::check_asset_options_hf_bsip81(block_time, v.new_options);\n      detail::check_asset_options_hf_core2281( next_maintenance_time, v.new_options ); // HF_REMOVABLE\n      detail::check_asset_options_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE\n\n      detail::check_asset_update_extensions_hf_bsip_48_75( block_time, v.extensions.value );\n\n      // TODO move as many validations as possible to validate() if not triggered before hardfork\n      if( HARDFORK_CORE_2281_PASSED( next_maintenance_time ) )\n      {\n         v.new_options.validate_flags( true );\n      }\n      else if( HARDFORK_BSIP_48_75_PASSED( block_time ) )\n      {\n         // do not allow the 'disable_collateral_bidding' bit\n         v.new_options.validate_flags( true, false );\n      }\n\n   }\n\n   void operator()(const graphene::chain::asset_update_bitasset_operation &v) const {\n      detail::check_bitasset_options_hf_bsip_48_75( block_time, v.new_options );\n      detail::check_bitasset_options_hf_bsip74( block_time, v.new_options ); // HF_REMOVABLE\n      detail::check_bitasset_options_hf_bsip77( block_time, v.new_options ); // HF_REMOVABLE\n      detail::check_bitasset_options_hf_bsip87( block_time, v.new_options ); // HF_REMOVABLE\n      detail::check_bitasset_opts_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE\n   }\n\n   void operator()(const graphene::chain::asset_claim_fees_operation &v) const {\n      detail::check_asset_claim_fees_hardfork_87_74_collatfee(block_time, v); // HF_REMOVABLE\n   }\n\n   void operator()(const graphene::chain::asset_publish_feed_operation &v) const {\n\n      detail::check_asset_publish_feed_extensions_hf_bsip77( block_time, v.extensions.value );\n\n   }\n\n   void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const {\n      if (block_time < HARDFORK_CORE_1468_TIME) {\n         FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), \n               \"Unable to set HTLC options before hardfork 1468\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<htlc_create_operation>());\n         FC_ASSERT(!op.new_parameters.current_fees->exists<htlc_redeem_operation>());\n         FC_ASSERT(!op.new_parameters.current_fees->exists<htlc_extend_operation>());\n      }\n      if (!HARDFORK_CORE_1604_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<limit_order_update_operation>(),\n                   \"Cannot set fees for limit_order_update_operation before its hardfork time\");\n      }\n      if (!HARDFORK_BSIP_40_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.extensions.value.custom_authority_options.valid(),\n                   \"Unable to set Custom Authority Options before hardfork BSIP 40\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<custom_authority_create_operation>(),\n                   \"Unable to define fees for custom authority operations prior to hardfork BSIP 40\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<custom_authority_update_operation>(),\n                   \"Unable to define fees for custom authority operations prior to hardfork BSIP 40\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<custom_authority_delete_operation>(),\n                   \"Unable to define fees for custom authority operations prior to hardfork BSIP 40\");\n      }\n      if (!HARDFORK_BSIP_85_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.extensions.value.maker_fee_discount_percent.valid(),\n                   \"Unable to set maker_fee_discount_percent before hardfork BSIP 85\");\n      }\n      if (!HARDFORK_BSIP_86_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.extensions.value.market_fee_network_percent.valid(),\n                   \"Unable to set market_fee_network_percent before hardfork BSIP 86\");\n      }\n      if (!HARDFORK_CORE_2103_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<ticket_create_operation>(),\n                   \"Unable to define fees for ticket operations prior to hardfork 2103\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<ticket_update_operation>(),\n                   \"Unable to define fees for ticket operations prior to hardfork 2103\");\n      }\n      if (!HARDFORK_LIQUIDITY_POOL_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<liquidity_pool_create_operation>(),\n                   \"Unable to define fees for liquidity pool operations prior to the LP hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<liquidity_pool_delete_operation>(),\n                   \"Unable to define fees for liquidity pool operations prior to the LP hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<liquidity_pool_deposit_operation>(),\n                   \"Unable to define fees for liquidity pool operations prior to the LP hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<liquidity_pool_withdraw_operation>(),\n                   \"Unable to define fees for liquidity pool operations prior to the LP hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<liquidity_pool_exchange_operation>(),\n                   \"Unable to define fees for liquidity pool operations prior to the LP hardfork\");\n      }\n      if (!HARDFORK_CORE_2351_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<samet_fund_create_operation>(),\n                   \"Unable to define fees for samet fund operations prior to the core-2351 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<samet_fund_delete_operation>(),\n                   \"Unable to define fees for samet fund operations prior to the core-2351 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<samet_fund_update_operation>(),\n                   \"Unable to define fees for samet fund operations prior to the core-2351 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<samet_fund_borrow_operation>(),\n                   \"Unable to define fees for samet fund operations prior to the core-2351 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<samet_fund_repay_operation>(),\n                   \"Unable to define fees for samet fund operations prior to the core-2351 hardfork\");\n      }\n      if (!HARDFORK_CORE_2362_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_offer_create_operation>(),\n                   \"Unable to define fees for credit offer operations prior to the core-2362 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_offer_delete_operation>(),\n                   \"Unable to define fees for credit offer operations prior to the core-2362 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_offer_update_operation>(),\n                   \"Unable to define fees for credit offer operations prior to the core-2362 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_offer_accept_operation>(),\n                   \"Unable to define fees for credit offer operations prior to the core-2362 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_deal_repay_operation>(),\n                   \"Unable to define fees for credit offer operations prior to the core-2362 hardfork\");\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_deal_expired_operation>(),\n                   \"Unable to define fees for credit offer operations prior to the core-2362 hardfork\");\n      }\n      if (!HARDFORK_CORE_2595_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<credit_deal_update_operation>(),\n                   \"Unable to define fees for credit deal update operation prior to the core-2595 hardfork\");\n      }\n      if (!HARDFORK_CORE_2604_PASSED(block_time)) {\n         FC_ASSERT(!op.new_parameters.current_fees->exists<liquidity_pool_update_operation>(),\n                   \"Unable to define fees for liquidity pool update operation prior to the core-2604 hardfork\");\n      }\n   }\n   void operator()(const graphene::chain::htlc_create_operation &op) const {\n      FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, \"Not allowed until hardfork 1468\" );\n      if (block_time < HARDFORK_CORE_BSIP64_TIME)\n      {\n         // memo field added at harfork BSIP64\n         // NOTE: both of these checks can be removed after hardfork time\n         FC_ASSERT( !op.extensions.value.memo.valid(), \n               \"Memo unavailable until after HARDFORK BSIP64\");\n         // HASH160 added at hardfork BSIP64\n         FC_ASSERT( !op.preimage_hash.is_type<fc::hash160>(),\n               \"HASH160 unavailable until after HARDFORK BSIP64\" );   \n      }\n   }\n   void operator()(const graphene::chain::htlc_redeem_operation&) const {\n      FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, \"Not allowed until hardfork 1468\" );\n   }\n   void operator()(const graphene::chain::htlc_extend_operation&) const {\n      FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, \"Not allowed until hardfork 1468\" );\n   }\n   void operator()(const graphene::chain::custom_authority_create_operation&) const {\n      FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), \"Not allowed until hardfork BSIP 40\" );\n   }\n   void operator()(const graphene::chain::custom_authority_update_operation&) const {\n      FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), \"Not allowed until hardfork BSIP 40\" );\n   }\n   void operator()(const graphene::chain::custom_authority_delete_operation&) const {\n      FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), \"Not allowed until hardfork BSIP 40\" );\n   }\n   void operator()(const graphene::chain::ticket_create_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), \"Not allowed until hardfork 2103\" );\n   }\n   void operator()(const graphene::chain::ticket_update_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), \"Not allowed until hardfork 2103\" );\n   }\n   void operator()(const graphene::chain::liquidity_pool_create_operation&) const {\n      FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), \"Not allowed until the LP hardfork\" );\n   }\n   void operator()(const graphene::chain::liquidity_pool_delete_operation&) const {\n      FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), \"Not allowed until the LP hardfork\" );\n   }\n   void operator()(const graphene::chain::liquidity_pool_update_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2604_PASSED(block_time), \"Not allowed until the core-2604 hardfork\" );\n   }\n   void operator()(const graphene::chain::liquidity_pool_deposit_operation&) const {\n      FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), \"Not allowed until the LP hardfork\" );\n   }\n   void operator()(const graphene::chain::liquidity_pool_withdraw_operation&) const {\n      FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), \"Not allowed until the LP hardfork\" );\n   }\n   void operator()(const graphene::chain::liquidity_pool_exchange_operation&) const {\n      FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), \"Not allowed until the LP hardfork\" );\n   }\n   void operator()(const graphene::chain::samet_fund_create_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), \"Not allowed until the core-2351 hardfork\" );\n   }\n   void operator()(const graphene::chain::samet_fund_delete_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), \"Not allowed until the core-2351 hardfork\" );\n   }\n   void operator()(const graphene::chain::samet_fund_update_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), \"Not allowed until the core-2351 hardfork\" );\n   }\n   void operator()(const graphene::chain::samet_fund_borrow_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), \"Not allowed until the core-2351 hardfork\" );\n   }\n   void operator()(const graphene::chain::samet_fund_repay_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), \"Not allowed until the core-2351 hardfork\" );\n   }\n   void operator()(const graphene::chain::credit_offer_create_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), \"Not allowed until the core-2362 hardfork\" );\n   }\n   void operator()(const graphene::chain::credit_offer_delete_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), \"Not allowed until the core-2362 hardfork\" );\n   }\n   void operator()(const graphene::chain::credit_offer_update_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), \"Not allowed until the core-2362 hardfork\" );\n   }\n   void operator()(const graphene::chain::credit_offer_accept_operation& op) const {\n      FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), \"Not allowed until the core-2362 hardfork\" );\n      if( !HARDFORK_CORE_2595_PASSED(block_time) ) {\n         FC_ASSERT( !op.extensions.value.auto_repay.valid(),\n                    \"auto_repay unavailable until the core-2595 hardfork\");\n      }\n   }\n   void operator()(const graphene::chain::credit_deal_repay_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), \"Not allowed until the core-2362 hardfork\" );\n   }\n   // Note: credit_deal_expired_operation is a virtual operation thus no need to add code here\n   void operator()(const graphene::chain::credit_deal_update_operation&) const {\n      FC_ASSERT( HARDFORK_CORE_2595_PASSED(block_time), \"Not allowed until the core-2595 hardfork\" );\n   }\n\n   // loop and self visit in proposals\n   void operator()(const graphene::chain::proposal_create_operation &v) const {\n      bool already_contains_proposal_update = false;\n\n      for (const op_wrapper &op : v.proposed_ops)\n      {\n         op.op.visit(*this);\n         // Do not allow more than 1 proposal_update in a proposal\n         if ( op.op.is_type<proposal_update_operation>() )\n         {\n            FC_ASSERT( !already_contains_proposal_update, \n                  \"At most one proposal update can be nested in a proposal!\" );\n            already_contains_proposal_update = true;\n         }\n      }\n   }\n};\n\nstruct hardfork_visitor_214 // non-recursive proposal visitor\n{\n   typedef void result_type;\n\n   template<typename T>\n   void operator()(const T &v) const {}\n\n   void operator()(const proposal_update_operation &v) const {\n      FC_ASSERT(false, \"Not allowed until hardfork 214\");\n   }\n};\n\nvoid hardfork_visitor_1479::operator()(const proposal_update_operation &v)\n{\n   if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance )\n      max_update_instance = v.proposal.instance.value;\n   nested_update_count++;\n}\n\nvoid hardfork_visitor_1479::operator()(const proposal_delete_operation &v)\n{\n   if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance )\n      max_update_instance = v.proposal.instance.value;\n   nested_update_count++;\n}\n\n// loop and self visit in proposals\nvoid hardfork_visitor_1479::operator()(const graphene::chain::proposal_create_operation &v)\n{\n   for (const op_wrapper &op : v.proposed_ops)\n      op.op.visit(*this);\n}\n\nvoid_result proposal_create_evaluator::do_evaluate( const proposal_create_operation& o )\n{ try {\n   const database& d = db();\n\n   // Calling the proposal hardfork visitor\n   const fc::time_point_sec block_time = d.head_block_time();\n   proposal_operation_hardfork_visitor vtor( d, block_time );\n   vtor( o );\n   if( block_time < HARDFORK_CORE_214_TIME )\n   {\n      // cannot be removed after hf, unfortunately\n      hardfork_visitor_214 hf214;\n      for( const op_wrapper &op : o.proposed_ops )\n         op.op.visit( hf214 );\n   }\n   vtor_1479( o );\n\n   const auto& global_parameters = d.get_global_properties().parameters;\n\n   FC_ASSERT( o.expiration_time > block_time, \"Proposal has already expired on creation.\" );\n   FC_ASSERT( o.expiration_time <= block_time + global_parameters.maximum_proposal_lifetime,\n              \"Proposal expiration time is too far in the future.\" );\n   FC_ASSERT( !o.review_period_seconds || \n         fc::seconds( *o.review_period_seconds ) < ( o.expiration_time - block_time ),\n         \"Proposal review period must be less than its overall lifetime.\" );\n\n   // Find all authorities required by the proposed operations\n   flat_set<account_id_type> tmp_required_active_auths;\n   vector<authority> other;\n   for( auto& op : o.proposed_ops )\n   {\n      operation_get_required_authorities( op.op, tmp_required_active_auths, _required_owner_auths, other,\n                                          MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( block_time ) );\n   }\n   // All accounts which must provide both owner and active authority should be omitted from the \n   // active authority set; owner authority approval implies active authority approval.\n   std::set_difference( tmp_required_active_auths.begin(), tmp_required_active_auths.end(),\n                        _required_owner_auths.begin(), _required_owner_auths.end(),\n                        std::inserter( _required_active_auths, _required_active_auths.begin() ) );\n\n   // TODO: what about other???\n   FC_ASSERT ( other.empty(),\n               \"Proposals containing operations requiring non-account authorities are not yet implemented.\" );\n\n   // If we're dealing with the committee authority, make sure this transaction has a sufficient review period.\n   if( _required_active_auths.count( GRAPHENE_COMMITTEE_ACCOUNT ) > 0 ||\n       _required_owner_auths.count( GRAPHENE_COMMITTEE_ACCOUNT ) > 0 )\n   {\n      GRAPHENE_ASSERT( o.review_period_seconds.valid(),\n                       proposal_create_review_period_required,\n                       \"Review period not given, but at least ${min} required\",\n                       (\"min\", global_parameters.committee_proposal_review_period) );\n      GRAPHENE_ASSERT( *o.review_period_seconds >= global_parameters.committee_proposal_review_period,\n                       proposal_create_review_period_insufficient,\n                       \"Review period of ${t} specified, but at least ${min} required\",\n                       (\"t\", *o.review_period_seconds)\n                       (\"min\", global_parameters.committee_proposal_review_period) );\n   }\n\n   for( const op_wrapper& op : o.proposed_ops )\n      _proposed_trx.operations.push_back( op.op );\n\n   _proposed_trx.validate();\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nobject_id_type proposal_create_evaluator::do_apply( const proposal_create_operation& o )\n{ try {\n   database& d = db();\n   auto chain_time = d.head_block_time();\n\n   const proposal_object& proposal = d.create<proposal_object>( [&o, this, chain_time](proposal_object& proposal) {\n      _proposed_trx.expiration = o.expiration_time;\n      proposal.proposed_transaction = _proposed_trx;\n      proposal.expiration_time = o.expiration_time;\n      proposal.proposer = o.fee_paying_account;\n      if( o.review_period_seconds )\n         proposal.review_period_time = o.expiration_time - *o.review_period_seconds;\n\n      //Populate the required approval sets\n      proposal.required_owner_approvals.insert( _required_owner_auths.begin(), _required_owner_auths.end() );\n      proposal.required_active_approvals.insert( _required_active_auths.begin(), _required_active_auths.end() );\n\n      if( chain_time > HARDFORK_CORE_1479_TIME )\n         FC_ASSERT( vtor_1479.nested_update_count == 0 || proposal.id.instance() > vtor_1479.max_update_instance,\n                    \"Cannot update/delete a proposal with a future id!\" );\n      else if( vtor_1479.nested_update_count > 0 && proposal.id.instance() <= vtor_1479.max_update_instance )\n      {\n         // Note: This happened on mainnet, proposal 1.10.17503\n         // prevent approval\n         transfer_operation top;\n         top.from = GRAPHENE_NULL_ACCOUNT;\n         top.to = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT;\n         top.amount = asset( GRAPHENE_MAX_SHARE_SUPPLY );\n         proposal.proposed_transaction.operations.emplace_back( top );\n      }\n   });\n\n   return proposal.id;\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result proposal_update_evaluator::do_evaluate( const proposal_update_operation& o )\n{ try {\n   database& d = db();\n\n   _proposal = &o.proposal(d);\n\n   if( _proposal->review_period_time && d.head_block_time() >= *_proposal->review_period_time )\n      FC_ASSERT( o.active_approvals_to_add.empty() && o.owner_approvals_to_add.empty(),\n                 \"This proposal is in its review period. No new approvals may be added.\" );\n\n   for( account_id_type id : o.active_approvals_to_remove )\n   {\n      FC_ASSERT( _proposal->available_active_approvals.find(id) != _proposal->available_active_approvals.end(),\n                 \"\", (\"id\", id)(\"available\", _proposal->available_active_approvals) );\n   }\n   for( account_id_type id : o.owner_approvals_to_remove )\n   {\n      FC_ASSERT( _proposal->available_owner_approvals.find(id) != _proposal->available_owner_approvals.end(),\n                 \"\", (\"id\", id)(\"available\", _proposal->available_owner_approvals) );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result proposal_update_evaluator::do_apply(const proposal_update_operation& o)\n{ try {\n   database& d = db();\n\n   // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal \n   // skip signature checks. This isn't done now because I just wrote all the proposals code, and I'm not yet \n   // 100% sure the required approvals are sufficient to authorize the transaction.\n   d.modify(*_proposal, [&o](proposal_object& p) {\n      p.available_active_approvals.insert(o.active_approvals_to_add.begin(), o.active_approvals_to_add.end());\n      p.available_owner_approvals.insert(o.owner_approvals_to_add.begin(), o.owner_approvals_to_add.end());\n      for( account_id_type id : o.active_approvals_to_remove )\n         p.available_active_approvals.erase(id);\n      for( account_id_type id : o.owner_approvals_to_remove )\n         p.available_owner_approvals.erase(id);\n      for( const auto& id : o.key_approvals_to_add )\n         p.available_key_approvals.insert(id);\n      for( const auto& id : o.key_approvals_to_remove )\n         p.available_key_approvals.erase(id);\n   });\n\n   // If the proposal has a review period, don't bother attempting to authorize/execute it.\n   // Proposals with a review period may never be executed except at their expiration.\n   if( _proposal->review_period_time )\n      return void_result();\n\n   if( _proposal->is_authorized_to_execute(d) )\n   {\n      // All required approvals are satisfied. Execute!\n      _executed_proposal = true;\n      try {\n         _processed_transaction = d.push_proposal(*_proposal);\n      } catch(fc::exception& e) {\n         d.modify(*_proposal, [&e](proposal_object& p) {\n            p.fail_reason = e.to_string(fc::log_level(fc::log_level::all));\n         });\n         wlog(\"Proposed transaction ${id} failed to apply once approved with exception:\\n\"\n              \"----\\n${reason}\\n----\\nWill try again when it expires.\",\n              (\"id\", o.proposal)(\"reason\", e.to_detail_string()));\n         _proposal_failed = true;\n      }\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result proposal_delete_evaluator::do_evaluate(const proposal_delete_operation& o)\n{ try {\n   database& d = db();\n\n   _proposal = &o.proposal(d);\n\n   auto required_approvals = o.using_owner_authority? &_proposal->required_owner_approvals\n                                                    : &_proposal->required_active_approvals;\n   FC_ASSERT( required_approvals->find(o.fee_paying_account) != required_approvals->end(),\n              \"Provided authority is not authoritative for this proposal.\",\n              (\"provided\", o.fee_paying_account)(\"required\", *required_approvals));\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\nvoid_result proposal_delete_evaluator::do_apply(const proposal_delete_operation& o)\n{ try {\n   db().remove(*_proposal);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE\n\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/proposal_object.cpp",
    "content": "/*\n * Copyright (c) 2015-2018 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/transaction_evaluation_state.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/restriction_predicate.hpp>\n\nnamespace graphene { namespace chain {\n\nbool proposal_object::is_authorized_to_execute( database& db ) const\n{\n   transaction_evaluation_state dry_run_eval( &db );\n\n   try {\n      bool allow_non_immediate_owner = ( db.head_block_time() >= HARDFORK_CORE_584_TIME );\n      verify_authority( proposed_transaction.operations,\n                        available_key_approvals,\n                        [&db]( account_id_type id ){ return &id( db ).active; },\n                        [&db]( account_id_type id ){ return &id( db ).owner;  },\n                        [&db]( account_id_type id, const operation& op, rejected_predicate_map* rejects ){\n                           return db.get_viable_custom_authorities(id, op, rejects); },\n                        allow_non_immediate_owner,\n                        MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ),\n                        db.get_global_properties().parameters.max_authority_depth,\n                        true, /* allow committee */\n                        available_active_approvals,\n                        available_owner_approvals );\n   } \n   catch ( const fc::exception& e )\n   {\n      return false;\n   }\n   return true;\n}\n\nvoid required_approval_index::object_inserted( const object& obj )\n{\n    assert( dynamic_cast<const proposal_object*>(&obj) );\n    const proposal_object& p = static_cast<const proposal_object&>(obj);\n    const proposal_id_type proposal_id = p.get_id();\n\n    for( const auto& a : p.required_active_approvals )\n       _account_to_proposals[a].insert( proposal_id );\n    for( const auto& a : p.required_owner_approvals )\n       _account_to_proposals[a].insert( proposal_id );\n    for( const auto& a : p.available_active_approvals )\n       _account_to_proposals[a].insert( proposal_id );\n    for( const auto& a : p.available_owner_approvals )\n       _account_to_proposals[a].insert( proposal_id );\n}\n\nvoid required_approval_index::remove( account_id_type a, proposal_id_type p )\n{\n    auto itr = _account_to_proposals.find(a);\n    if( itr != _account_to_proposals.end() )\n    {\n        itr->second.erase( p );\n        if( itr->second.empty() )\n            _account_to_proposals.erase( itr->first );\n    }\n}\n\nvoid required_approval_index::object_removed( const object& obj )\n{\n    assert( dynamic_cast<const proposal_object*>(&obj) );\n    const proposal_object& p = static_cast<const proposal_object&>(obj);\n    const proposal_id_type proposal_id = p.get_id();\n\n    for( const auto& a : p.required_active_approvals )\n       remove( a, proposal_id );\n    for( const auto& a : p.required_owner_approvals )\n       remove( a, proposal_id );\n    for( const auto& a : p.available_active_approvals )\n       remove( a, proposal_id );\n    for( const auto& a : p.available_owner_approvals )\n       remove( a, proposal_id );\n}\n\nvoid required_approval_index::insert_or_remove_delta( proposal_id_type p,\n                                                      const flat_set<account_id_type>& before,\n                                                      const flat_set<account_id_type>& after )\n{\n    auto b = before.begin();\n    auto a = after.begin();\n    while( b != before.end() || a != after.end() )\n    {\n       if( a == after.end() || (b != before.end() && *b < *a) )\n       {\n           remove( *b, p );\n           ++b;\n       }\n       else if( b == before.end() || (a != after.end() && *a < *b) )\n       {\n           _account_to_proposals[*a].insert( p );\n           ++a;\n       }\n       else // *a == *b\n       {\n           ++a;\n           ++b;\n       }\n    }\n}\n\nvoid required_approval_index::about_to_modify( const object& before )\n{\n    const proposal_object& p = static_cast<const proposal_object&>(before);\n    available_active_before_modify = p.available_active_approvals;\n    available_owner_before_modify  = p.available_owner_approvals;\n}\n\nvoid required_approval_index::object_modified( const object& after )\n{\n    const proposal_object& p = static_cast<const proposal_object&>(after);\n    const proposal_id_type proposal_id = p.get_id();\n    insert_or_remove_delta( proposal_id, available_active_before_modify, p.available_active_approvals );\n    insert_or_remove_delta( proposal_id, available_owner_before_modify,  p.available_owner_approvals );\n}\n\n} } // graphene::chain\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::proposal_object, (graphene::chain::object),\n                    (expiration_time)(review_period_time)(proposed_transaction)(required_active_approvals)\n                    (available_active_approvals)(required_owner_approvals)(available_owner_approvals)\n                    (available_key_approvals)(proposer)(fail_reason) )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::proposal_object )\n"
  },
  {
    "path": "libraries/chain/samet_fund_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n\n#include <graphene/chain/samet_fund_evaluator.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <graphene/protocol/samet_fund.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result samet_fund_create_evaluator::do_evaluate(const samet_fund_create_operation& op) const\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), \"Not allowed until the core-2351 hardfork\" );\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, op.asset_type(d) ),\n              \"The account is unauthorized by the asset\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nobject_id_type samet_fund_create_evaluator::do_apply(const samet_fund_create_operation& op) const\n{ try {\n   database& d = db();\n\n   d.adjust_balance( op.owner_account, -asset( op.balance, op.asset_type ) );\n\n   const auto& new_samet_fund_object = d.create<samet_fund_object>([&op](samet_fund_object& obj){\n      obj.owner_account = op.owner_account;\n      obj.asset_type = op.asset_type;\n      obj.balance = op.balance;\n      obj.fee_rate = op.fee_rate;\n      // unpaid amount is 0 by default\n   });\n   return new_samet_fund_object.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result samet_fund_delete_evaluator::do_evaluate(const samet_fund_delete_operation& op)\n{ try {\n   const database& d = db();\n\n   _fund = &op.fund_id(d);\n\n   FC_ASSERT( _fund->owner_account == op.owner_account, \"The account is not the owner of the SameT Fund\" );\n\n   FC_ASSERT( _fund->unpaid_amount == 0, \"Can only delete a SameT Fund when the unpaid amount is zero\" );\n\n   // Note: no asset authorization check here, allow funds to be moved to account balance\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nasset samet_fund_delete_evaluator::do_apply(const samet_fund_delete_operation& op) const\n{ try {\n   database& d = db();\n\n   asset released( _fund->balance, _fund->asset_type );\n\n   d.adjust_balance( op.owner_account, released );\n\n   d.remove( *_fund );\n\n   return released;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result samet_fund_update_evaluator::do_evaluate(const samet_fund_update_operation& op)\n{ try {\n   const database& d = db();\n\n   _fund = &op.fund_id(d);\n\n   FC_ASSERT( _fund->owner_account == op.owner_account, \"The account is not the owner of the SameT Fund\" );\n\n   if( op.delta_amount.valid() )\n   {\n      FC_ASSERT( _fund->asset_type == op.delta_amount->asset_id, \"Asset type mismatch\" );\n      FC_ASSERT( _fund->unpaid_amount == 0,\n                 \"Can only update the balance of a SameT Fund when the unpaid amount is zero\" );\n\n      if( op.delta_amount->amount > 0 )\n      {\n         // Check asset authorization only when moving funds from account balance to somewhere else\n         FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ),\n                    \"The account is unauthorized by the asset\" );\n      }\n      else\n      {\n         FC_ASSERT( _fund->balance > -op.delta_amount->amount, \"Insufficient balance in the SameT Fund\" );\n      }\n   }\n\n   if( op.new_fee_rate.valid() )\n   {\n      FC_ASSERT( _fund->fee_rate != *op.new_fee_rate,\n                 \"New fee rate should not be the same as the original fee rate\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result samet_fund_update_evaluator::do_apply( const samet_fund_update_operation& op) const\n{ try {\n   database& d = db();\n\n   if( op.delta_amount.valid() )\n      d.adjust_balance( op.owner_account, -(*op.delta_amount) );\n\n   d.modify( *_fund, [&op]( samet_fund_object& sfo ){\n      if( op.delta_amount.valid() )\n         sfo.balance += op.delta_amount->amount;\n      if( op.new_fee_rate.valid() )\n         sfo.fee_rate = *op.new_fee_rate;\n   });\n\n   // Defensive check\n   FC_ASSERT( _fund->balance > 0, \"Balance in the SameT Fund should be positive\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result samet_fund_borrow_evaluator::do_evaluate(const samet_fund_borrow_operation& op)\n{ try {\n   const database& d = db();\n\n   _fund = &op.fund_id(d);\n\n   FC_ASSERT( _fund->asset_type == op.borrow_amount.asset_id, \"Asset type mismatch\" );\n\n   FC_ASSERT( _fund->balance >= _fund->unpaid_amount + op.borrow_amount.amount,\n              \"Insufficient balance in the SameT Fund thus unable to borrow\" );\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ),\n              \"The account is unauthorized by the asset\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nextendable_operation_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) const\n{ try {\n   database& d = db();\n\n   d.modify( *_fund, [&op]( samet_fund_object& sfo ){\n      sfo.unpaid_amount += op.borrow_amount.amount;\n   });\n\n   d.adjust_balance( op.borrower, op.borrow_amount );\n\n   // Defensive check\n   FC_ASSERT( _fund->balance >= _fund->unpaid_amount, \"Should not borrow more than available\" );\n\n   extendable_operation_result result;\n   result.value.impacted_accounts = flat_set<account_id_type>({ _fund->owner_account });\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_operation& op)\n{ try {\n   const database& d = db();\n\n   _fund = &op.fund_id(d);\n\n   FC_ASSERT( _fund->asset_type == op.repay_amount.asset_id, \"Asset type mismatch\" );\n\n   FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ),\n              \"The account is unauthorized by the asset\" );\n\n   FC_ASSERT( op.repay_amount.amount <= _fund->unpaid_amount,\n              \"Repay amount should not be greater than unpaid amount\" );\n\n   // Note: the result can be larger than 64 bit, but since we don't store it, it is allowed\n   auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _fund->fee_rate )\n                         + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up\n\n   FC_ASSERT( fc::uint128_t(op.fund_fee.amount.value) >= required_fee,\n              \"Insuffient fund fee, requires ${r}, offered ${p}\",\n              (\"r\", required_fee) (\"p\", op.fund_fee.amount) );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nextendable_operation_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) const\n{ try {\n   database& d = db();\n\n   d.adjust_balance( op.account, -( op.repay_amount + op.fund_fee ) );\n\n   d.modify( *_fund, [op]( samet_fund_object& sfo ){\n      sfo.balance += op.fund_fee.amount;\n      sfo.unpaid_amount -= op.repay_amount.amount;\n   });\n\n   extendable_operation_result result;\n   result.value.impacted_accounts = flat_set<account_id_type>({ _fund->owner_account });\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/small_objects.cpp",
    "content": "/*\n * Copyright (c) 2019 BitShares Blockchain Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/block_summary_object.hpp>\n#include <graphene/chain/budget_record_object.hpp>\n#include <graphene/chain/buyback_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/confidential_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/global_property_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/special_authority_object.hpp>\n#include <graphene/chain/transaction_history_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n\n#include <fc/io/raw.hpp>\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::balance_object, (graphene::db::object),\n                    (owner)(balance)(vesting_policy)(last_claim_date) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::block_summary_object, (graphene::db::object), (block_id) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME(\n   graphene::chain::budget_record, BOOST_PP_SEQ_NIL,\n   (time_since_last_budget)\n   (from_initial_reserve)\n   (from_accumulated_fees)\n   (from_unused_witness_budget)\n   (requested_witness_budget)\n   (total_budget)\n   (witness_budget)\n   (worker_budget)\n   (leftover_worker_funds)\n   (supply_delta)\n   (max_supply)\n   (current_supply)\n)\n\nFC_REFLECT_DERIVED_NO_TYPENAME(\n   graphene::chain::budget_record_object,\n   (graphene::db::object),\n   (time)\n   (record)\n)\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::buyback_object, (graphene::db::object), (asset_to_buy) )\n\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::immutable_chain_parameters, BOOST_PP_SEQ_NIL,\n   (min_committee_member_count)\n   (min_witness_count)\n   (num_special_accounts)\n   (num_special_assets)\n)\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::chain_property_object, (graphene::db::object),\n                    (chain_id)\n                    (immutable_parameters)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::committee_member_object, (graphene::db::object),\n                    (committee_member_account)(vote_id)(total_votes)(url) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::blinded_balance_object, (graphene::db::object),\n                                (commitment)(asset_id)(owner) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::fba_accumulator_object, (graphene::db::object),\n                                (accumulated_fba_fees)(designated_asset) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::dynamic_global_property_object, (graphene::db::object),\n                    (head_block_number)\n                    (head_block_id)\n                    (time)\n                    (current_witness)\n                    (next_maintenance_time)\n                    (last_vote_tally_time)\n                    (last_budget_time)\n                    (witness_budget)\n                    (total_pob)\n                    (total_inactive)\n                    (accounts_registered_this_interval)\n                    (recently_missed_count)\n                    (current_aslot)\n                    (recent_slots_filled)\n                    (dynamic_flags)\n                    (last_irreversible_block_num)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::global_property_object, (graphene::db::object),\n                    (parameters)\n                    (pending_parameters)\n                    (next_available_vote_id)\n                    (active_committee_members)\n                    (active_witnesses)\n                  )\n\nFC_REFLECT( graphene::chain::htlc_object::transfer_info,\n   (from) (to) (amount) (asset_id) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info::hash_lock_info, BOOST_PP_SEQ_NIL,\n   (preimage_hash) (preimage_size) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info::time_lock_info, BOOST_PP_SEQ_NIL,\n   (expiration) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info, BOOST_PP_SEQ_NIL,\n   (hash_lock)(time_lock) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object, (graphene::db::object),\n               (transfer) (conditions) (memo) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::operation_history_object, (graphene::chain::object),\n                    (op)(result)(block_num)(trx_in_block)(op_in_trx)(virtual_op)(is_virtual)(block_time) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::account_history_object, (graphene::chain::object),\n                    (account)(operation_id)(sequence)(next) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME(\n   graphene::chain::special_authority_object,\n   (graphene::db::object),\n   (account)\n)\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::transaction_history_object, (graphene::db::object), (trx)(trx_id) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::withdraw_permission_object, (graphene::db::object),\n                    (withdraw_from_account)\n                    (authorized_account)\n                    (withdrawal_limit)\n                    (withdrawal_period_sec)\n                    (period_start_time)\n                    (expiration)\n                    (claimed_this_period)\n                 )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::witness_object, (graphene::db::object),\n                    (witness_account)\n                    (last_aslot)\n                    (signing_key)\n                    (pay_vb)\n                    (vote_id)\n                    (total_votes)\n                    (url)\n                    (total_missed)\n                    (last_confirmed_block_num)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME(\n   graphene::chain::witness_schedule_object,\n   (graphene::db::object),\n   (current_shuffled_witnesses)\n)\n\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::refund_worker_type, BOOST_PP_SEQ_NIL, (total_burned) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::vesting_balance_worker_type, BOOST_PP_SEQ_NIL, (balance) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::burn_worker_type, BOOST_PP_SEQ_NIL, (total_burned) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::worker_object, (graphene::db::object),\n                    (worker_account)\n                    (work_begin_date)\n                    (work_end_date)\n                    (daily_pay)\n                    (worker)\n                    (vote_for)\n                    (vote_against)\n                    (total_votes_for)\n                    (total_votes_against)\n                    (name)\n                    (url)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::custom_authority_object, (graphene::db::object),\n                               (account)(enabled)(valid_from)(valid_to)(operation_type)\n                               (auth)(restrictions)(restriction_counter) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::samet_fund_object, (graphene::db::object),\n                    (owner_account)\n                    (asset_type)\n                    (balance)\n                    (fee_rate)\n                    (unpaid_amount)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_offer_object, (graphene::db::object),\n                    (owner_account)\n                    (asset_type)\n                    (total_balance)\n                    (current_balance)\n                    (fee_rate)\n                    (max_duration_seconds)\n                    (min_deal_amount)\n                    (enabled)\n                    (auto_disable_time)\n                    (acceptable_collateral)\n                    (acceptable_borrowers)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_deal_object, (graphene::db::object),\n                    (borrower)\n                    (offer_id)\n                    (offer_owner)\n                    (debt_asset)\n                    (debt_amount)\n                    (collateral_asset)\n                    (collateral_amount)\n                    (fee_rate)\n                    (latest_repay_time)\n                    (auto_repay)\n                  )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_deal_summary_object, (graphene::db::object),\n                    (borrower)\n                    (offer_id)\n                    (offer_owner)\n                    (debt_asset)\n                    (total_debt_amount)\n                  )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::balance_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::block_summary_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::budget_record )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::budget_record_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::buyback_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::immutable_chain_parameters )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::chain_property_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::committee_member_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::blinded_balance_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::fba_accumulator_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::dynamic_global_property_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::global_property_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::htlc_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::operation_history_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::account_history_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::special_authority_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::transaction_history_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::withdraw_permission_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::witness_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::witness_schedule_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::worker_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::custom_authority_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::liquidity_pool_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::samet_fund_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::credit_offer_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_object )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_summary_object )\n"
  },
  {
    "path": "libraries/chain/special_authority_evaluation.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/special_authority_evaluation.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace chain {\n\nstruct special_authority_evaluate_visitor\n{\n   typedef void result_type;\n\n   special_authority_evaluate_visitor( const database& d ) : db(d) {}\n\n   void operator()( const no_special_authority& a ) {}\n\n   void operator()( const top_holders_special_authority& a )\n   {\n      db.get(a.asset);     // require asset to exist\n   }\n\n   const database& db;\n};\n\nvoid evaluate_special_authority( const database& db, const special_authority& a )\n{\n   special_authority_evaluate_visitor vtor( db );\n   a.visit( vtor );\n}\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/ticket_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/ticket_object.hpp>\n\n#include <graphene/chain/ticket_evaluator.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/ticket.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result ticket_create_evaluator::do_evaluate(const ticket_create_operation& op)\n{ try {\n   const database& d = db();\n   const auto block_time = d.head_block_time();\n\n   FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), \"Not allowed until hardfork 2103\" );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nobject_id_type ticket_create_evaluator::do_apply(const ticket_create_operation& op)\n{ try {\n   database& d = db();\n   const auto block_time = d.head_block_time();\n   const auto maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   ticket_version version = ( HARDFORK_CORE_2262_PASSED(maint_time) ? ticket_v2 : ticket_v1 );\n\n   d.adjust_balance( op.account, -op.amount );\n\n   const auto& new_ticket_object = d.create<ticket_object>([&op,block_time,version](ticket_object& obj){\n      obj.init_new( block_time, op.account, op.target_type, op.amount, version );\n   });\n\n   // Note: amount.asset_id is checked in validate(), so no check here\n   d.modify( d.get_account_stats_by_owner( op.account ), [&op,&new_ticket_object](account_statistics_object& aso) {\n      aso.total_core_pol += op.amount.amount;\n      aso.total_pol_value += new_ticket_object.value;\n   });\n\n   return new_ticket_object.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result ticket_update_evaluator::do_evaluate(const ticket_update_operation& op)\n{ try {\n   database& d = db();\n\n   _ticket = &op.ticket(d);\n\n   FC_ASSERT( _ticket->account == op.account, \"Ticket is not owned by the account\" );\n\n   FC_ASSERT( _ticket->current_type != lock_forever, \"Can not to update a ticket that is locked forever\" );\n\n   FC_ASSERT( static_cast<uint64_t>(_ticket->target_type) != op.target_type, \"Target type does not change\" );\n\n   if( op.amount_for_new_target.valid() )\n   {\n      // Note: amount.asset_id is checked in validate(), so no check here\n      FC_ASSERT( *op.amount_for_new_target <= _ticket->amount, \"Insufficient amount in ticket to be updated\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\ngeneric_operation_result ticket_update_evaluator::do_apply(const ticket_update_operation& op)\n{ try {\n   database& d = db();\n   const auto block_time = d.head_block_time();\n   const auto maint_time = d.get_dynamic_global_properties().next_maintenance_time;\n\n   ticket_version version = ( HARDFORK_CORE_2262_PASSED(maint_time) ? ticket_v2 : ticket_v1 );\n\n   generic_operation_result result;\n\n   share_type old_value = _ticket->value;\n   share_type delta_value;\n\n   // To partially update the ticket, aka splitting\n   if ( op.amount_for_new_target.valid() && *op.amount_for_new_target < _ticket->amount )\n   {\n      const auto& new_ticket_object = d.create<ticket_object>([&op,this,block_time,version](ticket_object& obj){\n         obj.init_split( block_time, *_ticket, op.target_type, *op.amount_for_new_target, version );\n      });\n\n      result.new_objects.insert( new_ticket_object.id );\n\n      d.modify( *_ticket, [&op,version](ticket_object& obj){\n         obj.adjust_amount( -(*op.amount_for_new_target), version );\n      });\n      delta_value = new_ticket_object.value + _ticket->value - old_value;\n   }\n   else // To update the whole ticket\n   {\n      d.modify( *_ticket, [&op,block_time,version](ticket_object& obj){\n         obj.update_target_type( block_time, op.target_type, version );\n      });\n      delta_value = _ticket->value - old_value;\n   }\n   result.updated_objects.insert( _ticket->id );\n\n   if( delta_value != 0 )\n   {\n      const auto& stat = d.get_account_stats_by_owner( op.account );\n      d.modify( stat, [delta_value](account_statistics_object& aso) {\n         aso.total_pol_value += delta_value;\n      });\n   }\n\n   // Do auto-update now.\n   // Note: calling process_tickets() here won't affect other tickets,\n   //       since head_block_time is not updated after last call,\n   //       even when called via a proposal this time or last time\n   generic_operation_result process_result = d.process_tickets();\n   result.removed_objects.insert( process_result.removed_objects.begin(), process_result.removed_objects.end() );\n   result.updated_objects.insert( process_result.updated_objects.begin(), process_result.updated_objects.end() );\n   for( const auto& id : result.new_objects )\n      result.updated_objects.erase( id );\n   for( const auto& id : result.removed_objects )\n      result.updated_objects.erase( id );\n\n   return result;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/ticket_object.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/ticket_object.hpp>\n\n#include <fc/io/raw.hpp>\n\nusing namespace graphene::chain;\n\nvoid ticket_object::init_new( time_point_sec now, account_id_type new_account,\n                              ticket_type new_target_type, const asset& new_amount, ticket_version version )\n{\n   account = new_account;\n   target_type = new_target_type;\n   amount = new_amount;\n\n   current_type = liquid;\n   status = charging;\n   next_auto_update_time = now + seconds_per_charging_step;\n   next_type_downgrade_time = time_point_sec::maximum();\n\n   update_value( version );\n}\n\nvoid ticket_object::init_split( time_point_sec now, const ticket_object& old_ticket,\n                                ticket_type new_target_type, const asset& new_amount, ticket_version version )\n{\n   account = old_ticket.account;\n   target_type = old_ticket.target_type;\n   amount = new_amount;\n\n   current_type = old_ticket.current_type;\n   status = old_ticket.status;\n   next_auto_update_time = old_ticket.next_auto_update_time;\n   next_type_downgrade_time = old_ticket.next_type_downgrade_time;\n\n   update_target_type( now, new_target_type, version );\n}\n\nvoid ticket_object::update_target_type( time_point_sec now, ticket_type new_target_type, ticket_version version )\n{\n   if( current_type < new_target_type )\n   {\n      if( status != charging )\n      {\n         status = charging;\n         next_auto_update_time = now + seconds_per_charging_step;\n      }\n      // else do nothing here\n   }\n   else // current_type >= new_target_type\n   {\n      status = withdrawing;\n      if( next_type_downgrade_time == time_point_sec::maximum() ) // no downgrade was started ago\n      {\n         if( current_type == new_target_type ) // was charging, to cancel\n            next_type_downgrade_time = now + seconds_to_cancel_charging;\n         else // was stable or charging, to downgrade\n         {\n            current_type = static_cast<ticket_type>( static_cast<uint8_t>(current_type) - 1 );\n            next_type_downgrade_time = now + seconds_to_downgrade(current_type);\n         }\n      }\n      // else a downgrade was started ago, keep the old value of `next_type_downgrade_time`\n      // Note: it's possible that `next_type_downgrade_time` is in the past,\n      //       in this case, it will be processed in database::process_tickets().\n      next_auto_update_time = next_type_downgrade_time;\n   }\n   target_type = new_target_type;\n\n   update_value( version );\n}\n\nvoid ticket_object::adjust_amount( const asset& delta_amount, ticket_version version )\n{\n   amount += delta_amount;\n   update_value( version );\n}\n\nvoid ticket_object::auto_update( ticket_version version )\n{\n   if( status == charging )\n   {\n      current_type = static_cast<ticket_type>( static_cast<uint8_t>(current_type) + 1 );\n      next_type_downgrade_time = time_point_sec::maximum();\n      if( current_type < target_type )\n      {\n         next_auto_update_time += seconds_per_charging_step;\n      }\n      else // reached target, stop\n      {\n         if( current_type != lock_forever )\n         {\n            status = stable;\n            next_auto_update_time = time_point_sec::maximum();\n         }\n         else // lock forever\n         {\n            status = withdrawing;\n            next_auto_update_time += seconds_per_lock_forever_update_step;\n            value = amount.amount * value_multiplier( current_type, version );\n         }\n      }\n   }\n   else // status == withdrawing\n   {\n      // Note: current_type != liquid, guaranteed by the caller\n      if( current_type == lock_forever )\n      {\n         share_type delta_value = amount.amount * value_multiplier( current_type, version )\n                                                / lock_forever_update_steps;\n         if( delta_value <= 0 )\n            delta_value = 1;\n         if( value <= delta_value )\n         {\n            value = 0;\n            status = stable;\n            next_auto_update_time = time_point_sec::maximum();\n         }\n         else\n         {\n            value -= delta_value;\n            next_auto_update_time += seconds_per_lock_forever_update_step;\n         }\n      }\n      else if( current_type == target_type ) // finished downgrading\n      {\n         status = stable;\n         next_type_downgrade_time = time_point_sec::maximum();\n         next_auto_update_time = time_point_sec::maximum();\n      }\n      else // to downgrade\n      {\n         current_type = static_cast<ticket_type>( static_cast<uint8_t>(current_type) - 1 );\n         next_type_downgrade_time += seconds_to_downgrade(current_type);\n         next_auto_update_time = next_type_downgrade_time;\n      }\n   }\n\n   update_value( version );\n}\n\nvoid ticket_object::update_value( ticket_version version )\n{\n   if( current_type != lock_forever )\n   {\n      value = amount.amount * value_multiplier( current_type, version );\n   }\n   // else lock forever and to be updated, do nothing here\n}\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::ticket_object, (graphene::db::object),\n                    (account)\n                    (target_type)\n                    (amount)\n                    (current_type)\n                    (status)\n                    (value)\n                    (next_auto_update_time)\n                    (next_type_downgrade_time)\n                  )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::ticket_object )\n"
  },
  {
    "path": "libraries/chain/transfer_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/transfer_evaluator.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\nnamespace graphene { namespace chain {\nvoid_result transfer_evaluator::do_evaluate( const transfer_operation& op )\n{ try {\n   \n   const database& d = db();\n\n   const account_object& from_account    = op.from(d);\n   const account_object& to_account      = op.to(d);\n   const asset_object&   asset_type      = op.amount.asset_id(d);\n\n   try {\n\n      GRAPHENE_ASSERT(\n         is_authorized_asset( d, from_account, asset_type ),\n         transfer_from_account_not_whitelisted,\n         \"'from' account ${from} is not whitelisted for asset ${asset}\",\n         (\"from\",op.from)\n         (\"asset\",op.amount.asset_id)\n         );\n      GRAPHENE_ASSERT(\n         is_authorized_asset( d, to_account, asset_type ),\n         transfer_to_account_not_whitelisted,\n         \"'to' account ${to} is not whitelisted for asset ${asset}\",\n         (\"to\",op.to)\n         (\"asset\",op.amount.asset_id)\n         );\n\n      if( asset_type.is_transfer_restricted() )\n      {\n         GRAPHENE_ASSERT(\n            from_account.id == asset_type.issuer || to_account.id == asset_type.issuer,\n            transfer_restricted_transfer_asset,\n            \"Asset ${asset} has transfer_restricted flag enabled\",\n            (\"asset\", op.amount.asset_id)\n          );\n      }\n\n      bool insufficient_balance = d.get_balance( from_account, asset_type ).amount >= op.amount.amount;\n      FC_ASSERT( insufficient_balance,\n                 \"Insufficient Balance: ${balance}, unable to transfer '${total_transfer}' from account '${a}' to '${t}'\", \n                 (\"a\",from_account.name)(\"t\",to_account.name)(\"total_transfer\",d.to_pretty_string(op.amount))(\"balance\",d.to_pretty_string(d.get_balance(from_account, asset_type))) );\n\n      return void_result();\n   } FC_RETHROW_EXCEPTIONS( error, \"Unable to transfer ${a} from ${f} to ${t}\", (\"a\",d.to_pretty_string(op.amount))(\"f\",op.from(d).name)(\"t\",op.to(d).name) );\n\n}  FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result transfer_evaluator::do_apply( const transfer_operation& o )\n{ try {\n   db().adjust_balance( o.from, -o.amount );\n   db().adjust_balance( o.to, o.amount );\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\n\n\nvoid_result override_transfer_evaluator::do_evaluate( const override_transfer_operation& op )\n{ try {\n   const database& d = db();\n\n   const asset_object&   asset_type      = op.amount.asset_id(d);\n   GRAPHENE_ASSERT(\n      asset_type.can_override(),\n      override_transfer_not_permitted,\n      \"override_transfer not permitted for asset ${asset}\",\n      (\"asset\", op.amount.asset_id)\n      );\n   FC_ASSERT( asset_type.issuer == op.issuer );\n\n   const account_object& from_account    = op.from(d);\n   const account_object& to_account      = op.to(d);\n\n   FC_ASSERT( is_authorized_asset( d, to_account, asset_type ),\n              \"The to_account is not allowed to transact the asset\" );\n   // Since hard fork core-2295, do not check asset authorization limitations on from_account for override_transfer\n   // TODO code cleanup: if applicable (could be false due to proposals),\n   //      remove the check and the assertion below after the hard fork time, keep the comment about reasoning above\n   if( !HARDFORK_CORE_2295_PASSED(d.head_block_time()) )\n   {\n      FC_ASSERT( is_authorized_asset( d, from_account, asset_type ),\n                 \"The from_account is not allowed to transact the asset\" );\n   }\n\n   FC_ASSERT( d.get_balance( from_account, asset_type ).amount >= op.amount.amount,\n              \"\", (\"total_transfer\",op.amount)(\"balance\",d.get_balance(from_account, asset_type).amount) );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result override_transfer_evaluator::do_apply( const override_transfer_operation& o )\n{ try {\n   db().adjust_balance( o.from, -o.amount );\n   db().adjust_balance( o.to, o.amount );\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/vesting_balance_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/vesting_balance_evaluator.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance_create_operation& op )\n{ try {\n   const database& d = db();\n\n   const account_object& creator_account = *fee_paying_account;\n   const account_object& owner_account = op.owner( d );\n\n   const asset_object& asset_obj = op.amount.asset_id( d );\n\n   FC_ASSERT( !asset_obj.is_transfer_restricted(), \"Asset has transfer_restricted flag enabled\" );\n\n   // Since hard fork core-973, check asset authorization limitations\n   if( HARDFORK_CORE_973_PASSED(d.head_block_time()) )\n   {\n      FC_ASSERT( is_authorized_asset( d, creator_account, asset_obj ),\n                 \"The creator account is not allowed to transact the asset\" );\n      FC_ASSERT( is_authorized_asset( d, owner_account, asset_obj ),\n                 \"The owner account is not allowed to transact the asset\" );\n   }\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nstruct init_policy_visitor\n{\n   typedef void result_type;\n\n   init_policy_visitor( vesting_policy& po,\n                        const share_type& begin_balance,\n                        const fc::time_point_sec& n ):p(po),init_balance(begin_balance),now(n){}\n\n   vesting_policy&    p;\n   share_type         init_balance;\n   fc::time_point_sec now;\n\n   void operator()( const linear_vesting_policy_initializer& i )const\n   {\n      linear_vesting_policy policy;\n      policy.begin_timestamp = i.begin_timestamp;\n      policy.vesting_cliff_seconds = i.vesting_cliff_seconds;\n      policy.vesting_duration_seconds = i.vesting_duration_seconds;\n      policy.begin_balance = init_balance;\n      p = policy;\n   }\n\n   void operator()( const cdd_vesting_policy_initializer& i )const\n   {\n      cdd_vesting_policy policy;\n      policy.vesting_seconds = i.vesting_seconds;\n      policy.start_claim = i.start_claim;\n      policy.coin_seconds_earned = 0;\n      policy.coin_seconds_earned_last_update = now;\n      p = policy;\n   }\n\n   void operator()( const instant_vesting_policy_initializer& i )const\n   {\n      p = instant_vesting_policy{};\n   }\n\n};\n\nobject_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op )\n{ try {\n   database& d = db();\n   const time_point_sec now = d.head_block_time();\n\n   d.adjust_balance( op.creator, -op.amount );\n\n   const vesting_balance_object& vbo = d.create< vesting_balance_object >( [&]( vesting_balance_object& obj )\n   {\n      //WARNING: The logic to create a vesting balance object is replicated in vesting_balance_worker_type::initializer::init.\n      // If making changes to this logic, check if those changes should also be made there as well.\n      obj.owner = op.owner;\n      obj.balance = op.amount;\n      op.policy.visit( init_policy_visitor( obj.policy, op.amount.amount, now ) );\n   } );\n\n\n   return vbo.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result vesting_balance_withdraw_evaluator::do_evaluate( const vesting_balance_withdraw_operation& op )\n{ try {\n   const database& d = db();\n   const time_point_sec now = d.head_block_time();\n\n   const vesting_balance_object& vbo = op.vesting_balance( d );\n   FC_ASSERT( op.owner == vbo.owner, \"\", (\"op.owner\", op.owner)(\"vbo.owner\", vbo.owner) );\n   FC_ASSERT( vbo.is_withdraw_allowed( now, op.amount ), \"\", (\"now\", now)(\"op\", op)(\"vbo\", vbo) );\n   assert( op.amount <= vbo.balance );      // is_withdraw_allowed should fail before this check is reached\n\n   // Note: asset authorization check is skipped here,\n   //       because it is fine to allow the funds to be moved to account balance\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result vesting_balance_withdraw_evaluator::do_apply( const vesting_balance_withdraw_operation& op )\n{ try {\n   database& d = db();\n   const time_point_sec now = d.head_block_time();\n\n   const vesting_balance_object& vbo = op.vesting_balance( d );\n\n   // Allow zero balance objects to stick around, (1) to comply\n   // with the chain's \"objects live forever\" design principle, (2)\n   // if it's cashback or worker, it'll be filled up again.\n\n   d.modify( vbo, [&]( vesting_balance_object& vbo )\n   {\n      vbo.withdraw( now, op.amount );\n   } );\n\n   d.adjust_balance( op.owner, op.amount );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/vesting_balance_object.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/vesting_balance_object.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace chain {\n\ninline bool sum_below_max_shares(const asset& a, const asset& b)\n{\n   assert(GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY);\n   return (a.amount              <= GRAPHENE_MAX_SHARE_SUPPLY)\n       && (            b.amount  <= GRAPHENE_MAX_SHARE_SUPPLY)\n       && ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY);\n}\n\nasset linear_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const\n{\n    share_type allowed_withdraw = 0;\n\n    if( ctx.now > begin_timestamp )\n    {\n        const auto elapsed_seconds = (ctx.now - begin_timestamp).to_seconds();\n        assert( elapsed_seconds > 0 );\n\n        if( elapsed_seconds >= vesting_cliff_seconds )\n        {\n            share_type total_vested = 0;\n            if( elapsed_seconds < vesting_duration_seconds )\n            {\n                total_vested = static_cast<uint64_t>(fc::uint128_t( begin_balance.value ) * elapsed_seconds\n                                                     / vesting_duration_seconds);\n            }\n            else\n            {\n                total_vested = begin_balance;\n            }\n            assert( total_vested >= 0 );\n\n            const share_type withdrawn_already = begin_balance - ctx.balance.amount;\n            assert( withdrawn_already >= 0 );\n\n            allowed_withdraw = total_vested - withdrawn_already;\n            assert( allowed_withdraw >= 0 );\n        }\n    }\n\n    return asset( allowed_withdraw, ctx.balance.asset_id );\n}\n\nvoid linear_vesting_policy::on_deposit(const vesting_policy_context& ctx)\n{\n}\n\nbool linear_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const\n{\n   return (ctx.amount.asset_id == ctx.balance.asset_id)\n      && sum_below_max_shares(ctx.amount, ctx.balance);\n}\n\nvoid linear_vesting_policy::on_withdraw(const vesting_policy_context& ctx)\n{\n}\n\nbool linear_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const\n{\n   return (ctx.amount.asset_id == ctx.balance.asset_id)\n          && (ctx.amount <= get_allowed_withdraw(ctx));\n}\n\nfc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned(const vesting_policy_context& ctx)const\n{\n   assert(ctx.now >= coin_seconds_earned_last_update);\n   int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds();\n   assert(delta_seconds >= 0);\n\n   fc::uint128_t delta_coin_seconds = ctx.balance.amount.value;\n   delta_coin_seconds *= delta_seconds;\n\n   fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value;\n   coin_seconds_earned_cap *= std::max(vesting_seconds, 1u);\n\n   return std::min(coin_seconds_earned + delta_coin_seconds, coin_seconds_earned_cap);\n}\n\nvoid cdd_vesting_policy::update_coin_seconds_earned(const vesting_policy_context& ctx)\n{\n   coin_seconds_earned = compute_coin_seconds_earned(ctx);\n   coin_seconds_earned_last_update = ctx.now;\n}\n\nasset cdd_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx)const\n{\n   if(ctx.now <= start_claim)\n      return asset(0, ctx.balance.asset_id);\n   fc::uint128_t cs_earned = compute_coin_seconds_earned(ctx);\n   fc::uint128_t withdraw_available = cs_earned / std::max(vesting_seconds, 1u);\n   assert(withdraw_available <= static_cast<fc::uint128_t>(ctx.balance.amount.value));\n   return asset(static_cast<uint64_t>(withdraw_available), ctx.balance.asset_id);\n}\n\nvoid cdd_vesting_policy::on_deposit(const vesting_policy_context& ctx)\n{\n   update_coin_seconds_earned(ctx);\n}\n\nvoid cdd_vesting_policy::on_deposit_vested(const vesting_policy_context& ctx)\n{\n   on_deposit(ctx);\n   coin_seconds_earned += ctx.amount.amount.value * std::max(vesting_seconds, 1u);\n}\n\nvoid cdd_vesting_policy::on_withdraw(const vesting_policy_context& ctx)\n{\n   update_coin_seconds_earned(ctx);\n   fc::uint128_t coin_seconds_needed = ctx.amount.amount.value;\n   coin_seconds_needed *= std::max(vesting_seconds, 1u);\n   // is_withdraw_allowed should forbid any withdrawal that\n   // would trigger this assert\n   assert(coin_seconds_needed <= coin_seconds_earned);\n\n   coin_seconds_earned -= coin_seconds_needed;\n}\n\nbool cdd_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const\n{\n   return (ctx.amount.asset_id == ctx.balance.asset_id)\n         && sum_below_max_shares(ctx.amount, ctx.balance);\n}\n\nbool cdd_vesting_policy::is_deposit_vested_allowed(const vesting_policy_context& ctx) const\n{\n   return is_deposit_allowed(ctx);\n}\n\nbool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const\n{\n   return (ctx.amount <= get_allowed_withdraw(ctx));\n}\n\nasset instant_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const\n{\n   return ctx.balance;\n}\n\nvoid instant_vesting_policy::on_deposit(const vesting_policy_context& ctx)\n{\n}\n\nvoid instant_vesting_policy::on_deposit_vested(const vesting_policy_context&)\n{\n\n}\n\nbool instant_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const\n{\n   return (ctx.amount.asset_id == ctx.balance.asset_id)\n      && sum_below_max_shares(ctx.amount, ctx.balance);\n}\n\nvoid instant_vesting_policy::on_withdraw(const vesting_policy_context& ctx)\n{\n}\n\nbool instant_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const\n{\n   return (ctx.amount.asset_id == ctx.balance.asset_id)\n          && (ctx.amount <= get_allowed_withdraw(ctx));\n}\n\n#define VESTING_VISITOR(NAME, MAYBE_CONST)                    \\\nstruct NAME ## _visitor                                       \\\n{                                                             \\\n   typedef decltype(                                          \\\n      std::declval<linear_vesting_policy>().NAME(             \\\n         std::declval<vesting_policy_context>())              \\\n     ) result_type;                                           \\\n                                                              \\\n   NAME ## _visitor(                                          \\\n      const asset& balance,                                   \\\n      const time_point_sec& now,                              \\\n      const asset& amount                                     \\\n     )                                                        \\\n   : ctx(balance, now, amount) {}                             \\\n                                                              \\\n   template< typename Policy >                                \\\n   result_type                                                \\\n   operator()(MAYBE_CONST Policy& policy) MAYBE_CONST         \\\n   {                                                          \\\n      return policy.NAME(ctx);                                \\\n   }                                                          \\\n                                                              \\\n   vesting_policy_context ctx;                                \\\n}\n\nVESTING_VISITOR(on_deposit,);\nVESTING_VISITOR(on_deposit_vested,);\nVESTING_VISITOR(on_withdraw,);\nVESTING_VISITOR(is_deposit_allowed, const);\nVESTING_VISITOR(is_deposit_vested_allowed, const);\nVESTING_VISITOR(is_withdraw_allowed, const);\nVESTING_VISITOR(get_allowed_withdraw, const);\n\nbool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const\n{\n   return policy.visit(is_deposit_allowed_visitor(balance, now, amount));\n}\n\nbool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const\n{\n   bool result = policy.visit(is_withdraw_allowed_visitor(balance, now, amount));\n   // if some policy allows you to withdraw more than your balance,\n   //    there's a programming bug in the policy algorithm\n   assert((amount <= balance) || (!result));\n   return result;\n}\n\nvoid vesting_balance_object::deposit(const time_point_sec& now, const asset& amount)\n{\n   on_deposit_visitor vtor(balance, now, amount);\n   policy.visit(vtor);\n   balance += amount;\n}\n\nvoid vesting_balance_object::deposit_vested(const time_point_sec& now, const asset& amount)\n{\n   on_deposit_vested_visitor vtor(balance, now, amount);\n   policy.visit(vtor);\n   balance += amount;\n}\n\nbool vesting_balance_object::is_deposit_vested_allowed(const time_point_sec& now, const asset& amount) const\n{\n   return policy.visit(is_deposit_vested_allowed_visitor(balance, now, amount));\n}\n\nvoid vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount)\n{\n   assert(amount <= balance);\n   on_withdraw_visitor vtor(balance, now, amount);\n   policy.visit(vtor);\n   balance -= amount;\n}\n\nasset vesting_balance_object::get_allowed_withdraw(const time_point_sec& now)const\n{\n   asset amount = asset();\n   return policy.visit(get_allowed_withdraw_visitor(balance, now, amount));\n}\n\n} } // graphene::chain\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::linear_vesting_policy )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::cdd_vesting_policy )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::vesting_balance_object )\n"
  },
  {
    "path": "libraries/chain/withdraw_permission_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/withdraw_permission_evaluator.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result withdraw_permission_create_evaluator::do_evaluate(const operation_type& op)const\n{ try {\n   database& d = db();\n   FC_ASSERT(d.find(op.withdraw_from_account));\n   FC_ASSERT(d.find(op.authorized_account));\n   FC_ASSERT(d.find(op.withdrawal_limit.asset_id));\n   FC_ASSERT(op.period_start_time > d.head_block_time());\n   FC_ASSERT(op.period_start_time + op.periods_until_expiration * op.withdrawal_period_sec > d.head_block_time());\n   FC_ASSERT(op.withdrawal_period_sec >= d.get_global_properties().parameters.block_interval);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nobject_id_type withdraw_permission_create_evaluator::do_apply(const operation_type& op)const\n{ try {\n   return db().create<withdraw_permission_object>([&op](withdraw_permission_object& p) {\n      p.withdraw_from_account = op.withdraw_from_account;\n      p.authorized_account = op.authorized_account;\n      p.withdrawal_limit = op.withdrawal_limit;\n      p.withdrawal_period_sec = op.withdrawal_period_sec;\n      p.expiration = op.period_start_time + op.periods_until_expiration * op.withdrawal_period_sec;\n      p.period_start_time = op.period_start_time;\n   }).id;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result withdraw_permission_claim_evaluator::do_evaluate(\n      const withdraw_permission_claim_evaluator::operation_type& op)const\n{ try {\n   const database& d = db();\n   time_point_sec head_block_time = d.head_block_time();\n\n   const withdraw_permission_object& permit = op.withdraw_permission(d);\n   FC_ASSERT(permit.expiration > head_block_time);\n   FC_ASSERT(permit.authorized_account == op.withdraw_to_account);\n   FC_ASSERT(permit.withdraw_from_account == op.withdraw_from_account);\n   FC_ASSERT(permit.period_start_time <= head_block_time);\n   FC_ASSERT(op.amount_to_withdraw <= permit.available_this_period( head_block_time ) );\n   FC_ASSERT(d.get_balance(op.withdraw_from_account, op.amount_to_withdraw.asset_id) >= op.amount_to_withdraw);\n\n   const asset_object& _asset = op.amount_to_withdraw.asset_id(d);\n   if( _asset.is_transfer_restricted() )\n   {\n      FC_ASSERT( _asset.issuer == permit.authorized_account || _asset.issuer == permit.withdraw_from_account,\n                 \"Asset ${a} '${sym}' has transfer_restricted flag enabled\",\n                 (\"a\", _asset.id)(\"sym\", _asset.symbol) );\n   }\n\n   const account_object& to    = permit.authorized_account(d);\n   FC_ASSERT( is_authorized_asset( d, to, _asset ),\n         \"Account ${acct} '${name}' is unauthorized to transact asset ${a} '${sym}' due to whitelist / blacklist\",\n         (\"acct\", to.id)(\"name\", to.name)(\"a\", _asset.id)(\"sym\", _asset.symbol) );\n\n   const account_object& from  = op.withdraw_from_account(d);\n   bool from_is_authorized = ( is_authorized_asset( d, from, _asset ) );\n   FC_ASSERT( from_is_authorized,\n         \"Account ${acct} '${name}' is unauthorized to withdraw asset ${a} '${sym}' due to whitelist / blacklist\",\n         (\"acct\", from.id)(\"name\", from.name)(\"a\", _asset.id)(\"sym\", _asset.symbol) );\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result withdraw_permission_claim_evaluator::do_apply(\n      const withdraw_permission_claim_evaluator::operation_type& op)const\n{ try {\n   database& d = db();\n\n   const withdraw_permission_object& permit = d.get(op.withdraw_permission);\n   d.modify(permit, [&](withdraw_permission_object& p) {\n      auto periods = (d.head_block_time() - p.period_start_time).to_seconds() / p.withdrawal_period_sec;\n      p.period_start_time += periods * p.withdrawal_period_sec;\n      if( periods == 0 )\n         p.claimed_this_period += op.amount_to_withdraw.amount;\n      else\n         p.claimed_this_period = op.amount_to_withdraw.amount;\n   });\n\n   d.adjust_balance(op.withdraw_from_account, -op.amount_to_withdraw);\n   d.adjust_balance(op.withdraw_to_account, op.amount_to_withdraw);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result withdraw_permission_update_evaluator::do_evaluate(\n      const withdraw_permission_update_evaluator::operation_type& op)const\n{ try {\n   database& d = db();\n\n   const withdraw_permission_object& permit = op.permission_to_update(d);\n   FC_ASSERT(permit.authorized_account == op.authorized_account);\n   FC_ASSERT(permit.withdraw_from_account == op.withdraw_from_account);\n   FC_ASSERT(d.find(op.withdrawal_limit.asset_id));\n   FC_ASSERT(op.period_start_time >= d.head_block_time());\n   FC_ASSERT(op.period_start_time + op.periods_until_expiration * op.withdrawal_period_sec > d.head_block_time());\n   FC_ASSERT(op.withdrawal_period_sec >= d.get_global_properties().parameters.block_interval);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result withdraw_permission_update_evaluator::do_apply(\n      const withdraw_permission_update_evaluator::operation_type& op)const\n{ try {\n   database& d = db();\n\n   d.modify(op.permission_to_update(d), [&op](withdraw_permission_object& p) {\n      p.period_start_time = op.period_start_time;\n      p.expiration = op.period_start_time + op.periods_until_expiration * op.withdrawal_period_sec;\n      p.withdrawal_limit = op.withdrawal_limit;\n      p.withdrawal_period_sec = op.withdrawal_period_sec;\n   });\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result withdraw_permission_delete_evaluator::do_evaluate(\n      const withdraw_permission_delete_evaluator::operation_type& op)const\n{ try {\n   database& d = db();\n\n   const withdraw_permission_object& permit = op.withdrawal_permission(d);\n   FC_ASSERT(permit.authorized_account == op.authorized_account);\n   FC_ASSERT(permit.withdraw_from_account == op.withdraw_from_account);\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result withdraw_permission_delete_evaluator::do_apply(\n      const withdraw_permission_delete_evaluator::operation_type& op)const\n{ try {\n   db().remove(db().get(op.withdrawal_permission));\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/witness_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/witness_evaluator.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/protocol/vote.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result witness_create_evaluator::do_evaluate( const witness_create_operation& op )\n{ try {\n   FC_ASSERT(db().get(op.witness_account).is_lifetime_member());\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nobject_id_type witness_create_evaluator::do_apply( const witness_create_operation& op )\n{ try {\n   database& _db = db();\n   vote_id_type vote_id;\n   _db.modify( _db.get_global_properties(), [&vote_id](global_property_object& p) {\n      vote_id = vote_id_type(vote_id_type::witness, p.next_available_vote_id++);\n   });\n\n   const auto& new_witness_object = _db.create<witness_object>( [&op,&vote_id]( witness_object& obj ){\n         obj.witness_account  = op.witness_account;\n         obj.signing_key      = op.block_signing_key;\n         obj.vote_id          = vote_id;\n         obj.url              = op.url;\n   });\n   return new_witness_object.id;\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result witness_update_evaluator::do_evaluate( const witness_update_operation& op )\n{ try {\n   FC_ASSERT(db().get(op.witness).witness_account == op.witness_account);\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\nvoid_result witness_update_evaluator::do_apply( const witness_update_operation& op )\n{ try {\n   database& _db = db();\n   _db.modify(\n      _db.get(op.witness),\n      [&op]( witness_object& wit )\n      {\n         if( op.new_url.valid() )\n            wit.url = *op.new_url;\n         if( op.new_signing_key.valid() )\n            wit.signing_key = *op.new_signing_key;\n      });\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (op) ) }\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/chain/worker_evaluator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/worker_evaluator.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n\n#include <graphene/protocol/vote.hpp>\n\nnamespace graphene { namespace chain {\n\nvoid_result worker_create_evaluator::do_evaluate(const worker_create_evaluator::operation_type& o)\n{ try {\n   database& d = db();\n\n   FC_ASSERT(d.get(o.owner).is_lifetime_member());\n   FC_ASSERT(o.work_begin_date >= d.head_block_time());\n\n   return void_result();\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\n\nstruct worker_init_visitor\n{\n   typedef void result_type;\n\n   worker_object& worker;\n   database&      db;\n\n   worker_init_visitor( worker_object& w, database& d ):worker(w),db(d){}\n\n   result_type operator()( const vesting_balance_worker_initializer& i )const\n   {\n      vesting_balance_worker_type w;\n       w.balance = db.create<vesting_balance_object>([&](vesting_balance_object& b) {\n         b.owner = worker.worker_account;\n         b.balance = asset(0);\n         b.balance_type = vesting_balance_type::worker;\n\n         cdd_vesting_policy policy;\n         policy.vesting_seconds = fc::days(i.pay_vesting_period_days).to_seconds();\n         policy.coin_seconds_earned = 0;\n         policy.coin_seconds_earned_last_update = db.head_block_time();\n         b.policy = policy;\n      }).id;\n      worker.worker = w;\n   }\n\n   template<typename T>\n   result_type operator()( const T& )const\n   {\n      // DO NOTHING FOR OTHER WORKERS\n   }\n};\n\n\n\n\n\nobject_id_type worker_create_evaluator::do_apply(const worker_create_evaluator::operation_type& o)\n{ try {\n   database& d = db();\n   vote_id_type for_id, against_id;\n   d.modify(d.get_global_properties(), [&for_id, &against_id](global_property_object& p) {\n      for_id = vote_id_type(vote_id_type::worker, p.next_available_vote_id++);\n      against_id = vote_id_type(vote_id_type::worker, p.next_available_vote_id++);\n   });\n\n   return d.create<worker_object>([&](worker_object& w) {\n      w.worker_account = o.owner;\n      w.daily_pay = o.daily_pay;\n      w.work_begin_date = o.work_begin_date;\n      w.work_end_date = o.work_end_date;\n      w.name = o.name;\n      w.url = o.url;\n      w.vote_for = for_id;\n      w.vote_against = against_id;\n\n      w.worker.set_which(o.initializer.which());\n      o.initializer.visit( worker_init_visitor( w, d ) );\n   }).id;\n} FC_CAPTURE_AND_RETHROW( (o) ) }\n\nvoid refund_worker_type::pay_worker(share_type pay, database& db)\n{\n   total_burned += pay;\n   db.modify( db.get_core_dynamic_data(), [pay](asset_dynamic_data_object& d) {\n      d.current_supply -= pay;\n   });\n}\n\nvoid vesting_balance_worker_type::pay_worker(share_type pay, database& db)\n{\n   db.modify(balance(db), [&](vesting_balance_object& b) {\n      b.deposit(db.head_block_time(), asset(pay));\n   });\n}\n\n\nvoid burn_worker_type::pay_worker(share_type pay, database& db)\n{\n   total_burned += pay;\n   db.adjust_balance( GRAPHENE_NULL_ACCOUNT, pay );\n}\n\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/db/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/db/*.hpp\")\nadd_library( graphene_db undo_database.cpp index.cpp object_database.cpp ${HEADERS} )\ntarget_link_libraries( graphene_db graphene_protocol fc )\ntarget_include_directories( graphene_db PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\ninstall( TARGETS\n   graphene_db\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\ninstall( FILES ${HEADERS} DESTINATION \"include/graphene/db\" )\n"
  },
  {
    "path": "libraries/db/include/graphene/db/generic_index.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/db/index.hpp>\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n\nnamespace graphene { namespace db {\n\n   using boost::multi_index_container;\n   using namespace boost::multi_index;\n\n   struct by_id;\n   /**\n    *  Almost all objects can be tracked and managed via a boost::multi_index container that uses\n    *  an unordered_unique key on the object ID.  This template class adapts the generic index interface\n    *  to work with arbitrary boost multi_index containers on the same type.\n    */\n   template<typename ObjectType, typename MultiIndexType>\n   class generic_index : public index\n   {\n      public:\n         using index_type = MultiIndexType;\n         using object_type = ObjectType;\n\n         const object& insert( object&& obj )override\n         {\n            assert( nullptr != dynamic_cast<ObjectType*>(&obj) );\n            auto insert_result = _indices.insert( std::move( static_cast<ObjectType&>(obj) ) );\n            FC_ASSERT( insert_result.second,\n                       \"Could not insert object, most likely a uniqueness constraint was violated\" );\n            return *insert_result.first;\n         }\n\n         const object&  create(const std::function<void(object&)>& constructor )override\n         {\n            ObjectType item;\n            item.id = get_next_id();\n            constructor( item );\n            auto insert_result = _indices.insert( std::move(item) );\n            FC_ASSERT( insert_result.second,\n                       \"Could not create object! Most likely a uniqueness constraint is violated.\");\n            use_next_id();\n            return *insert_result.first;\n         }\n\n         void modify( const object& obj, const std::function<void(object&)>& m )override\n         {\n            assert(nullptr != dynamic_cast<const ObjectType*>(&obj));\n            std::exception_ptr exc;\n            auto ok = _indices.modify(_indices.iterator_to(static_cast<const ObjectType&>(obj)),\n                                       [&m, &exc](ObjectType& o) mutable {\n                                          try {\n                                             m(o);\n                                          } catch (fc::exception& e) {\n                                             exc = std::current_exception();\n                                             elog(\"Exception while modifying object: ${e} -- object may be corrupted\",\n                                                  (\"e\", e));\n                                          } catch (...) {\n                                             exc = std::current_exception();\n                                             elog(\"Unknown exception while modifying object\");\n                                          }\n                                       }\n                      );\n            if (exc)\n                std::rethrow_exception(exc);\n            FC_ASSERT(ok, \"Could not modify object, most likely an index constraint was violated\");\n         }\n\n         void remove( const object& obj )override\n         {\n            _indices.erase( _indices.iterator_to( static_cast<const ObjectType&>(obj) ) );\n         }\n\n         const object* find( object_id_type id )const override\n         {\n            static_assert(std::is_same<typename MultiIndexType::key_type, object_id_type>::value,\n                          \"First index of MultiIndexType MUST be object_id_type!\");\n            auto itr = _indices.find( id );\n            if( itr == _indices.end() ) return nullptr;\n            return &*itr;\n         }\n\n         void inspect_all_objects(std::function<void (const object&)> inspector)const override\n         {\n            try {\n               for( const auto& ptr : _indices )\n                  inspector(ptr);\n            } FC_CAPTURE_AND_RETHROW()\n         }\n\n         const index_type& indices()const { return _indices; }\n\n      private:\n         index_type  _indices;\n   };\n\n   /**\n    * @brief An index type for objects which may be deleted\n    *\n    * This is the preferred index type for objects which need only be referenced by ID, but may be deleted.\n    */\n   template< class T >\n   struct sparse_index : public generic_index<T, boost::multi_index_container<\n      T,\n      indexed_by<\n         ordered_unique<\n            tag<by_id>,\n            member<object, object_id_type, &object::id>\n         >\n      >\n   >>{};\n\n} }\n"
  },
  {
    "path": "libraries/db/include/graphene/db/index.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/db/object.hpp>\n\n#include <fc/interprocess/file_mapping.hpp>\n#include <fc/io/raw.hpp>\n#include <fc/io/json.hpp>\n#include <fc/crypto/sha256.hpp>\n\n#include <fstream>\n#include <stack>\n\nnamespace graphene { namespace db {\n   class object_database;\n\n   /**\n    * @class index_observer\n    * @brief used to get callbacks when objects change\n    */\n   class index_observer\n   {\n      public:\n         virtual ~index_observer() = default;\n         /** called just after the object is added */\n         virtual void on_add( const object& obj ){}\n         /** called just before obj is removed */\n         virtual void on_remove( const object& obj ){}\n         /** called just after obj is modified with new value*/\n         virtual void on_modify( const object& obj ){}\n   };\n\n   /**\n    *  @class index\n    *  @brief abstract base class for accessing objects indexed in various ways.\n    *\n    *  All indexes assume that there exists an object ID space that will grow\n    *  forever in a seqential manner.  These IDs are used to identify the\n    *  index, type, and instance of the object.\n    *\n    *  Items in an index can only be modified via a call to modify and\n    *  all references to objects outside of that callback are const references.\n    *\n    *  Most implementations will probably be some form of boost::multi_index_container\n    *  which means that they can covnert a reference to an object to an iterator.  When\n    *  at all possible save a pointer/reference to your objects rather than constantly\n    *  looking them up by ID.\n    */\n   class index\n   {\n      public:\n         virtual ~index() = default;\n\n         virtual uint8_t object_space_id()const = 0;\n         virtual uint8_t object_type_id()const = 0;\n\n         virtual object_id_type get_next_id()const = 0;\n         virtual void           use_next_id() = 0;\n         virtual void           set_next_id( object_id_type id ) = 0;\n\n         virtual const object&  load( const std::vector<char>& data ) = 0;\n         /**\n          *  Polymorphically insert by moving an object into the index.\n          *  this should throw if the object is already in the database.\n          */\n         virtual const object& insert( object&& obj ) = 0;\n\n         /**\n          * Builds a new object and assigns it the next available ID and then\n          * initializes it with constructor and lastly inserts it into the index.\n          */\n         virtual const object&  create( const std::function<void(object&)>& constructor ) = 0;\n\n         /**\n          *  Opens the index loading objects from a file\n          */\n         virtual void open( const fc::path& db ) = 0;\n         virtual void save( const fc::path& db ) = 0;\n\n\n\n         /** @return the object with id or nullptr if not found */\n         virtual const object*      find( object_id_type id )const = 0;\n\n         /**\n          * This version will automatically check for nullptr and throw an exception if the\n          * object ID could not be found.\n          */\n         const object&              get( object_id_type id )const\n         {\n            auto maybe_found = find( id );\n            FC_ASSERT( maybe_found != nullptr, \"Unable to find Object ${id}\", (\"id\",id) );\n            return *maybe_found;\n         }\n\n         virtual void               modify( const object& obj, const std::function<void(object&)>& ) = 0;\n         virtual void               remove( const object& obj ) = 0;\n\n         /**\n          *   When forming your lambda to modify obj, it is natural to have Object& be the signature, but\n          *   that is not compatible with the type erasue required by the virtual method.  This method\n          *   provides a helper to wrap the lambda in a form compatible with the virtual modify call.\n          *   @note Lambda should have the signature:  void(Object&)\n          */\n         template<typename Object, typename Lambda>\n         void modify( const Object& obj, const Lambda& l ) {\n            modify( static_cast<const object&>(obj),\n                    std::function<void(object&)>( [&l]( object& o ){ l( static_cast<Object&>(o) ); } ) );\n         }\n\n         virtual void inspect_all_objects(std::function<void(const object&)> inspector)const = 0;\n         virtual void add_observer( const std::shared_ptr<index_observer>& ) = 0;\n\n         virtual void object_from_variant( const fc::variant& var, object& obj, uint32_t max_depth )const = 0;\n         virtual void object_default( object& obj )const = 0;\n   };\n\n   class secondary_index\n   {\n      public:\n         virtual ~secondary_index() = default;\n         virtual void object_inserted( const object& obj ){};\n         virtual void object_removed( const object& obj ){};\n         virtual void about_to_modify( const object& before ){};\n         virtual void object_modified( const object& after  ){};\n   };\n\n   /**\n    *   Defines the common implementation\n    */\n   class base_primary_index\n   {\n      public:\n         explicit base_primary_index( object_database& db ):_db(db){}\n\n         virtual ~base_primary_index() = default;\n\n         /** called just before obj is modified */\n         void save_undo( const object& obj );\n\n         /** called just after the object is added */\n         void on_add( const object& obj );\n\n         /** called just before obj is removed */\n         void on_remove( const object& obj );\n\n         /** called just after obj is modified */\n         void on_modify( const object& obj );\n\n         template<typename T, typename... Args>\n         T* add_secondary_index(Args... args)\n         {\n            _sindex.emplace_back( std::make_unique<T>(args...) );\n            return static_cast<T*>(_sindex.back().get());\n         }\n\n         template<typename T>\n         const T& get_secondary_index()const\n         {\n            for( const auto& item : _sindex )\n            {\n               const T* result = dynamic_cast<const T*>(item.get());\n               if( result != nullptr ) return *result;\n            }\n            FC_THROW_EXCEPTION( fc::assert_exception, \"invalid index type\" );\n         }\n\n      protected:\n         std::vector< std::shared_ptr<index_observer> >   _observers;\n         std::vector< std::unique_ptr<secondary_index> >  _sindex;\n\n      private:\n         object_database& _db;\n   };\n\n   /** @class direct_index\n    *  @brief A secondary index that tracks objects in vectors indexed by object\n    *  id. It is meant for fully (or almost fully) populated indexes only (will\n    *  fail when loading an object_database with large gaps).\n    *\n    *  WARNING! If any of the methods called on insertion, removal or\n    *  modification throws, subsequent behaviour is undefined! Such exceptions\n    *  indicate that this index type is not appropriate for the use-case.\n    */\n   template<typename Object, uint8_t chunkbits>\n   class direct_index : public secondary_index\n   {\n      static_assert( chunkbits < 64, \"Do you really want arrays with more than 2^63 elements???\" );\n\n      // private\n         static const size_t MAX_HOLE = 100;\n         static const size_t _mask = ((1ULL << chunkbits) - 1);\n         uint64_t next = 0;\n         std::vector< std::vector< const Object* > > content;\n         std::stack< object_id_type > ids_being_modified;\n\n      public:\n         direct_index() {\n            FC_ASSERT( (1ULL << chunkbits) > MAX_HOLE, \"Small chunkbits is inefficient.\" );\n         }\n\n         void object_inserted( const object& obj ) override\n         {\n            uint64_t instance = obj.id.instance();\n            if( instance == next )\n            {\n               if( 0 == (next & _mask) )\n               {\n                  content.resize((next >> chunkbits) + 1);\n                  content[next >> chunkbits].resize( 1ULL << chunkbits, nullptr );\n               }\n               next++;\n            }\n            else if( instance < next )\n               FC_ASSERT( !content[instance >> chunkbits][instance & _mask],\n                          \"Overwriting insert at {id}!\", (\"id\",obj.id) );\n            else // instance > next, allow small \"holes\"\n            {\n               FC_ASSERT( instance <= next + MAX_HOLE,\n                          \"Out-of-order insert: {id} > {next}!\", (\"id\",obj.id)(\"next\",next) );\n               if( 0 == (next & _mask) || (next & (~_mask)) != (instance & (~_mask)) )\n               {\n                  content.resize((instance >> chunkbits) + 1);\n                  content[instance >> chunkbits].resize( 1ULL << chunkbits, nullptr );\n               }\n               while( next <= instance )\n               {\n                  content[next >> chunkbits][next & _mask] = nullptr;\n                  next++;\n               }\n            }\n            FC_ASSERT( nullptr != dynamic_cast<const Object*>(&obj), \"Wrong object type!\" );\n            content[instance >> chunkbits][instance & _mask] = static_cast<const Object*>( &obj );\n         }\n\n         void object_removed( const object& obj ) override\n         {\n            FC_ASSERT( nullptr != dynamic_cast<const Object*>(&obj), \"Wrong object type!\" );\n            uint64_t instance = obj.id.instance();\n            FC_ASSERT( instance < next, \"Removing out-of-range object: {id} > {next}!\", (\"id\",obj.id)(\"next\",next) );\n            FC_ASSERT( content[instance >> chunkbits][instance & _mask],\n                       \"Removing non-existent object {id}!\", (\"id\",obj.id) );\n            content[instance >> chunkbits][instance & _mask] = nullptr;\n         }\n\n         void about_to_modify( const object& before ) override\n         {\n            ids_being_modified.emplace( before.id );\n         }\n\n         void object_modified( const object& after  ) override\n         {\n            FC_ASSERT( ids_being_modified.top() == after.id, \"Modification of ID is not supported!\");\n            ids_being_modified.pop();\n         }\n\n         template< typename object_id >\n         const Object* find( const object_id& id )const\n         {\n            static_assert( object_id::space_id == Object::space_id, \"Space ID mismatch!\" );\n            static_assert( object_id::type_id == Object::type_id, \"Type_ID mismatch!\" );\n            if( id.instance >= next ) return nullptr;\n            return content[id.instance.value >> chunkbits][id.instance.value & _mask];\n         };\n\n         template< typename object_id >\n         const Object& get( const object_id& id )const\n         {\n            const Object* ptr = find( id );\n            FC_ASSERT( ptr != nullptr, \"Object not found!\" );\n            return *ptr;\n         };\n\n         const Object* find( const object_id_type& id )const\n         {\n            FC_ASSERT( id.space() == Object::space_id, \"Space ID mismatch!\" );\n            FC_ASSERT( id.type() == Object::type_id, \"Type_ID mismatch!\" );\n            if( id.instance() >= next ) return nullptr;\n            return content[id.instance() >> chunkbits][id.instance() & ((1ULL << chunkbits) - 1)];\n         };\n   };\n\n   /**\n    * @class primary_index\n    * @brief  Wraps a derived index to intercept calls to create, modify, and remove so that\n    *  callbacks may be fired and undo state saved.\n    *\n    *  @see http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern\n    */\n   template<typename DerivedIndex, uint8_t DirectBits = 0>\n   class primary_index  : public DerivedIndex, public base_primary_index\n   {\n      public:\n         using object_type = typename DerivedIndex::object_type;\n\n         explicit primary_index( object_database& db )\n         :base_primary_index(db),_next_id(object_type::space_id,object_type::type_id,0)\n         {\n            if( DirectBits > 0 )\n               _direct_by_id = add_secondary_index< direct_index< object_type, DirectBits > >();\n         }\n\n         uint8_t object_space_id()const override\n         { return object_type::space_id; }\n\n         uint8_t object_type_id()const override\n         { return object_type::type_id; }\n\n         object_id_type get_next_id()const override              { return _next_id;    }\n         void           use_next_id()override                    { ++_next_id.number;  }\n         void           set_next_id( object_id_type id )override { _next_id = id;      }\n\n         /** @return the object with id or nullptr if not found */\n         const object*  find( object_id_type id )const override\n         {\n            if( DirectBits > 0 )\n               return _direct_by_id->find( id );\n            return DerivedIndex::find( id );\n         }\n\n         fc::sha256 get_object_version()const\n         {\n            std::string desc = \"1.0\";\n            return fc::sha256::hash(desc);\n         }\n\n         void open( const fc::path& db )override\n         {\n            if( !fc::exists( db ) ) return;\n            fc::file_mapping fm( db.generic_string().c_str(), fc::read_only );\n            fc::mapped_region mr( fm, fc::read_only, 0, fc::file_size(db) );\n            fc::datastream<const char*> ds( (const char*)mr.get_address(), mr.get_size() );\n            fc::sha256 open_ver;\n\n            fc::raw::unpack(ds, _next_id);\n            fc::raw::unpack(ds, open_ver);\n            FC_ASSERT( open_ver == get_object_version(),\n                       \"Incompatible Version, the serialization of objects in this index has changed\" );\n            std::vector<char> tmp;\n            while( ds.remaining() > 0 )\n            {\n               fc::raw::unpack( ds, tmp );\n               load( tmp );\n            }\n         }\n\n         void save( const fc::path& db ) override\n         {\n            std::ofstream out( db.generic_string(),\n                               std::ofstream::binary | std::ofstream::out | std::ofstream::trunc );\n            FC_ASSERT( out );\n            auto ver  = get_object_version();\n            fc::raw::pack( out, _next_id );\n            fc::raw::pack( out, ver );\n            this->inspect_all_objects( [&out]( const object& o ) {\n                auto vec = fc::raw::pack( static_cast<const object_type&>(o) );\n                auto packed_vec = fc::raw::pack( vec );\n                out.write( packed_vec.data(), packed_vec.size() );\n            });\n         }\n\n         const object&  load( const std::vector<char>& data )override\n         {\n            const auto& result = DerivedIndex::insert( fc::raw::unpack<object_type>( data ) );\n            for( const auto& item : _sindex )\n               item->object_inserted( result );\n            return result;\n         }\n\n\n         const object&  create(const std::function<void(object&)>& constructor )override\n         {\n            const auto& result = DerivedIndex::create( constructor );\n            for( const auto& item : _sindex )\n               item->object_inserted( result );\n            on_add( result );\n            return result;\n         }\n\n         const object& insert( object&& obj ) override\n         {\n            const auto& result = DerivedIndex::insert( std::move( obj ) );\n            for( const auto& item : _sindex )\n               item->object_inserted( result );\n            on_add( result );\n            return result;\n         }\n\n         void  remove( const object& obj ) override\n         {\n            for( const auto& item : _sindex )\n               item->object_removed( obj );\n            on_remove(obj);\n            DerivedIndex::remove(obj);\n         }\n\n         void modify( const object& obj, const std::function<void(object&)>& m )override\n         {\n            save_undo( obj );\n            for( const auto& item : _sindex )\n               item->about_to_modify( obj );\n            DerivedIndex::modify( obj, m );\n            for( const auto& item : _sindex )\n               item->object_modified( obj );\n            on_modify( obj );\n         }\n\n         void add_observer( const std::shared_ptr<index_observer>& o ) override\n         {\n            _observers.emplace_back( o );\n         }\n\n         void object_from_variant( const fc::variant& var, object& obj, uint32_t max_depth )const override\n         {\n            object_id_type id = obj.id;\n            object_type* result = dynamic_cast<object_type*>( &obj );\n            FC_ASSERT( result != nullptr );\n            fc::from_variant( var, *result, max_depth );\n            obj.id = id;\n         }\n\n         void object_default( object& obj )const override\n         {\n            object_id_type id = obj.id;\n            object_type* result = dynamic_cast<object_type*>( &obj );\n            FC_ASSERT( result != nullptr );\n            (*result) = object_type();\n            obj.id = id;\n         }\n\n      private:\n         object_id_type                                 _next_id;\n         const direct_index< object_type, DirectBits >* _direct_by_id = nullptr;\n   };\n\n} } // graphene::db\n"
  },
  {
    "path": "libraries/db/include/graphene/db/object.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <boost/multiprecision/integer.hpp>\n#include <graphene/protocol/object_id.hpp>\n#include <fc/io/raw.hpp>\n#include <fc/crypto/city.hpp>\n\n#define MAX_NESTING (200)\n\nnamespace graphene { namespace db {\n   /**\n    *  @brief base for all database objects\n    *\n    *  The object is the fundamental building block of the database and\n    *  is the level upon which undo/redo operations are performed.  Objects\n    *  are used to track data and their relationships and provide an effecient\n    *  means to find and update information.\n    *\n    *  Objects are assigned a unique and sequential object ID by the database within\n    *  the id_space defined in the object.\n    *\n    *  All objects must be serializable via FC_REFLECT() and their content must be\n    *  faithfully restored.   Additionally all objects must be copy-constructable and\n    *  assignable in a relatively efficient manner.  In general this means that objects\n    *  should only refer to other objects by ID and avoid expensive operations when\n    *  they are copied, especially if they are modified frequently.\n    *\n    *  Additionally all objects may be annotated by plugins which wish to maintain\n    *  additional information to an object.  There can be at most one annotation\n    *  per id_space for each object.   An example of an annotation would be tracking\n    *  extra data not required by validation such as the name and description of\n    *  a user asset.  By carefully organizing how information is organized and\n    *  tracked systems can minimize the workload to only that which is necessary\n    *  to perform their function.\n    *\n    *  @note Do not use multiple inheritance with object because the code assumes\n    *  a static_cast will work between object and derived types.\n    */\n   class object\n   {\n      public:\n         object() = default;\n         object( uint8_t space_id, uint8_t type_id ) : id( space_id, type_id, 0 ) {}\n         virtual ~object() = default;\n\n         // serialized\n         object_id_type          id;\n\n         /// these methods are implemented for derived classes by inheriting base_abstract_object<DerivedClass>\n         /// @{\n         virtual std::unique_ptr<object> clone()const = 0;\n         virtual void                    move_from( object& obj ) = 0;\n         virtual fc::variant             to_variant()const  = 0;\n         virtual std::vector<char>       pack()const = 0;\n         /// @}\n   };\n\n   /**\n    * @class base_abstract_object\n    * @brief   Use the Curiously Recurring Template Pattern to automatically add the ability to\n    *  clone, serialize, and move objects polymorphically.\n    *\n    *  http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern\n    */\n   template<typename DerivedClass>\n   class base_abstract_object : public object\n   {\n      public:\n         using object::object; // constructors\n         std::unique_ptr<object> clone()const override\n         {\n            return std::make_unique<DerivedClass>( *static_cast<const DerivedClass*>(this) );\n         }\n\n         void    move_from( object& obj ) override\n         {\n            static_cast<DerivedClass&>(*this) = std::move( static_cast<DerivedClass&>(obj) );\n         }\n         fc::variant to_variant()const override\n         { return fc::variant( static_cast<const DerivedClass&>(*this), MAX_NESTING ); }\n         std::vector<char> pack()const override { return fc::raw::pack( static_cast<const DerivedClass&>(*this) ); }\n   };\n\n   template<typename DerivedClass, uint8_t SpaceID, uint8_t TypeID>\n   class abstract_object : public base_abstract_object<DerivedClass>\n   {\n   public:\n      static constexpr uint8_t space_id = SpaceID;\n      static constexpr uint8_t type_id = TypeID;\n      abstract_object() : base_abstract_object<DerivedClass>( space_id, type_id ) {}\n      object_id<SpaceID,TypeID> get_id() const { return object_id<SpaceID,TypeID>( this->id ); }\n   };\n\n   using annotation_map = fc::flat_map<uint8_t, object_id_type>;\n\n   /**\n    *  @class annotated_object\n    *  @brief An object that is easily extended by providing pointers to other objects, one for each space.\n    */\n   template<typename DerivedClass>\n   class annotated_object : public base_abstract_object<DerivedClass>\n   {\n      public:\n         /** return object_id_type() if no anotation is found for id_space */\n         object_id_type          get_annotation( uint8_t annotation_id_space )const\n         {\n            auto itr = annotations.find(annotation_id_space);\n            if( itr != annotations.end() ) return itr->second;\n            return object_id_type();\n         }\n         void                    set_annotation( object_id_type id )\n         {\n            annotations[id.space()] = id;\n         }\n\n         /**\n          *  Annotations should be accessed via get_annotation and set_annotation so\n          *  that they can be maintained in sorted order.\n          */\n         annotation_map annotations;\n   };\n\n} } // graphene::db\n\n// Without this, pack(object_id) tries to match the template for\n// pack(boost::multiprecision::uint128_t). No idea why. :-(\nnamespace boost { namespace multiprecision { namespace detail {\ntemplate<typename To>\nstruct is_restricted_conversion<graphene::db::object,To> : public mpl::true_ {};\n}}}\n\nFC_REFLECT_TYPENAME( graphene::db::annotation_map )\nFC_REFLECT( graphene::db::object, (id) )\nFC_REFLECT_DERIVED_TEMPLATE( (typename Derived), graphene::db::annotated_object<Derived>, (graphene::db::object),\n                             (annotations) )\n"
  },
  {
    "path": "libraries/db/include/graphene/db/object_database.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/db/object.hpp>\n#include <graphene/db/index.hpp>\n#include <graphene/db/undo_database.hpp>\n\n#include <fc/log/logger.hpp>\n\n#include <map>\n\nnamespace graphene { namespace db {\n\n   /**\n    *   @class object_database\n    *   @brief maintains a set of indexed objects that can be modified with multi-level rollback support\n    */\n   class object_database\n   {\n      public:\n         object_database();\n         virtual ~object_database() = default;\n\n         static constexpr uint8_t _index_size = 255;\n\n         void reset_indexes()\n         {\n            _index.clear();\n            _index.resize(_index_size);\n         }\n\n         void open(const fc::path& data_dir );\n\n         /**\n          * Saves the complete state of the object_database to disk, this could take a while\n          */\n         void flush();\n         void wipe(const fc::path& data_dir); // remove from disk\n         void close();\n\n         template<typename T, typename F>\n         const T& create( F&& constructor )\n         {\n            auto& idx = get_mutable_index<T>();\n            return static_cast<const T&>( idx.create( [&](object& o)\n            {\n               assert( dynamic_cast<T*>(&o) );\n               constructor( static_cast<T&>(o) );\n            } ));\n         }\n\n         /// These methods are used to retrieve indexes on the object_database. All public index accessors are\n         /// const-access only.\n         /// @{\n         template<typename IndexType>\n         const IndexType& get_index_type()const {\n            static_assert( std::is_base_of<index,IndexType>::value, \"Type must be an index type\" );\n            return static_cast<const IndexType&>( get_index( IndexType::object_type::space_id,\n                                                             IndexType::object_type::type_id ) );\n         }\n         template<typename T>\n         const index&  get_index()const { return get_index(T::space_id,T::type_id); }\n         const index&  get_index(uint8_t space_id, uint8_t type_id)const;\n         const index&  get_index(const object_id_type& id)const { return get_index(id.space(),id.type()); }\n         /// @}\n\n         const object& get_object( const object_id_type& id )const;\n         const object* find_object( const object_id_type& id )const;\n\n         /// These methods are mutators of the object_database.\n         /// You must use these methods to make changes to the object_database,\n         /// in order to maintain proper undo history.\n         ///@{\n\n         const object& insert( object&& obj ) { return get_mutable_index(obj.id).insert( std::move(obj) ); }\n         void          remove( const object& obj ) { get_mutable_index(obj.id).remove( obj ); }\n         template<typename T, typename Lambda>\n         void modify( const T& obj, const Lambda& m ) {\n            get_mutable_index(obj.id).modify(obj,m);\n         }\n\n         ///@}\n\n         template<typename T>\n         static const T& cast( const object& obj )\n         {\n            assert( nullptr != dynamic_cast<const T*>(&obj) );\n            return static_cast<const T&>(obj);\n         }\n         template<typename T>\n         static T& cast( object& obj )\n         {\n            assert( nullptr != dynamic_cast<T*>(&obj) );\n            return static_cast<T&>(obj);\n         }\n\n         template<typename T>\n         const T& get( const object_id_type& id )const\n         {\n            const object& obj = get_object( id );\n            assert( nullptr != dynamic_cast<const T*>(&obj) );\n            return static_cast<const T&>(obj);\n         }\n         template<typename T>\n         const T* find( const object_id_type& id )const\n         {\n            const object* obj = find_object( id );\n            assert(  !obj || nullptr != dynamic_cast<const T*>(obj) );\n            return static_cast<const T*>(obj);\n         }\n\n         template<uint8_t SpaceID, uint8_t TypeID>\n         auto find( const object_id<SpaceID,TypeID>& id )const -> const object_downcast_t<decltype(id)>* {\n             return find<object_downcast_t<decltype(id)>>(object_id_type(id));\n         }\n\n         template<uint8_t SpaceID, uint8_t TypeID>\n         auto get( const object_id<SpaceID,TypeID>& id )const -> const object_downcast_t<decltype(id)>& {\n             return get<object_downcast_t<decltype(id)>>(object_id_type(id));\n         }\n\n         template<typename IndexType>\n         IndexType* add_index()\n         {\n            using ObjectType = typename IndexType::object_type;\n            const auto space_id = ObjectType::space_id;\n            const auto type_id = ObjectType::type_id;\n            FC_ASSERT( space_id < _index.size(), \"Space ID ${s} overflow\", (\"s\",space_id) );\n            if( _index[space_id].size() <= type_id )\n                _index[space_id].resize( _index_size );\n            FC_ASSERT( type_id < _index[space_id].size(), \"Type ID ${t} overflow\", (\"t\",type_id) );\n            FC_ASSERT( !_index[space_id][type_id], \"Index ${s}.${t} already exists\", (\"s\",space_id)(\"t\",type_id) );\n            _index[space_id][type_id] = std::make_unique<IndexType>(*this);\n            return static_cast<IndexType*>(_index[space_id][type_id].get());\n         }\n\n         template<typename IndexType, typename SecondaryIndexType, typename... Args>\n         SecondaryIndexType* add_secondary_index( Args... args )\n         {\n            return get_mutable_index_type<IndexType>().template\n                      add_secondary_index<SecondaryIndexType, Args...>(args...);\n         }\n\n         void pop_undo();\n\n         fc::path get_data_dir()const { return _data_dir; }\n\n         /** public for testing purposes only... should be private in practice. */\n         undo_database                          _undo_db;\n     protected:\n         template<typename IndexType>\n         IndexType&    get_mutable_index_type() {\n            static_assert( std::is_base_of<index,IndexType>::value, \"Type must be an index type\" );\n            return static_cast<IndexType&>( get_mutable_index( IndexType::object_type::space_id,\n                                                               IndexType::object_type::type_id ) );\n         }\n         template<typename T>\n         index& get_mutable_index()                          { return get_mutable_index(T::space_id,T::type_id); }\n         index& get_mutable_index(const object_id_type& id)  { return get_mutable_index(id.space(),id.type());   }\n         index& get_mutable_index(uint8_t space_id, uint8_t type_id);\n\n     private:\n\n         friend class base_primary_index;\n         friend class undo_database;\n         void save_undo( const object& obj );\n         void save_undo_add( const object& obj );\n         void save_undo_remove( const object& obj );\n\n         fc::path                                                  _data_dir;\n         std::vector< std::vector< std::unique_ptr<index> > >      _index;\n   };\n\n} } // graphene::db\n\n\n"
  },
  {
    "path": "libraries/db/include/graphene/db/simple_index.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/db/index.hpp>\n\nnamespace graphene { namespace db {\n\n   /**\n    *  @class simple_index\n    *  @brief A simple index uses a vector<unique_ptr<T>> to store data\n    *\n    *  This index is preferred in situations where the data will never be\n    *  removed from main memory and when access by ID is the only kind\n    *  of access that is necessary.\n    */\n   template<typename T>\n   class simple_index : public index\n   {\n      public:\n         using object_type = T;\n\n         virtual const object&  create( const std::function<void(object&)>& constructor ) override\n         {\n             auto id = get_next_id();\n             auto instance = id.instance();\n             if( instance >= _objects.size() ) _objects.resize( instance + 1 );\n             _objects[instance] = std::make_unique<T>();\n             _objects[instance]->id = id;\n             constructor( *_objects[instance] );\n             _objects[instance]->id = id; // just in case it changed\n             use_next_id();\n             return *_objects[instance];\n         }\n\n         virtual void modify( const object& obj, const std::function<void(object&)>& modify_callback ) override\n         {\n            assert( obj.id.instance() < _objects.size() );\n            modify_callback( *_objects[obj.id.instance()] );\n         }\n\n         virtual const object& insert( object&& obj )override\n         {\n            auto instance = obj.id.instance();\n            assert( nullptr != dynamic_cast<T*>(&obj) );\n            if( _objects.size() <= instance ) _objects.resize( instance+1 );\n            assert( !_objects[instance] );\n            _objects[instance] = std::make_unique<T>( std::move( static_cast<T&>(obj) ) );\n            return *_objects[instance];\n         }\n\n         virtual void remove( const object& obj ) override\n         {\n            assert( nullptr != dynamic_cast<const T*>(&obj) );\n            const auto instance = obj.id.instance();\n            _objects[instance].reset();\n            while( (_objects.size() > 0) && (_objects.back() == nullptr) )\n               _objects.pop_back();\n         }\n\n         virtual const object* find( object_id_type id )const override\n         {\n            assert( id.space() == T::space_id );\n            assert( id.type() == T::type_id );\n\n            const auto instance = id.instance();\n            if( instance >= _objects.size() ) return nullptr;\n            return _objects[instance].get();\n         }\n\n         virtual void inspect_all_objects(std::function<void (const object&)> inspector)const override\n         {\n            try {\n               for( const auto& ptr : _objects )\n               {\n                  if( ptr.get() )\n                     inspector(*ptr);\n               }\n            } FC_CAPTURE_AND_RETHROW()\n         }\n\n         class const_iterator\n         {\n            public:\n               explicit const_iterator( const std::vector<std::unique_ptr<object>>& objects ):_objects(objects) {}\n               const_iterator(\n                  const std::vector<std::unique_ptr<object>>& objects,\n                  const std::vector<std::unique_ptr<object>>::const_iterator& a ):_itr(a),_objects(objects){}\n               friend bool operator==( const const_iterator& a, const const_iterator& b ) { return a._itr == b._itr; }\n               friend bool operator!=( const const_iterator& a, const const_iterator& b ) { return a._itr != b._itr; }\n               const T& operator*()const { return static_cast<const T&>(*_itr->get()); }\n               const_iterator operator++(int)     // postfix\n               {\n                  const_iterator result( *this );\n                  ++(*this);\n                  return result;\n               }\n               const_iterator& operator++()       // prefix\n               {\n                  ++_itr;\n                  while( (_itr != _objects.end()) && ( (*_itr) == nullptr ) )\n                     ++_itr;\n                  return *this;\n               }\n               using iterator_category = std::forward_iterator_tag;\n               using value_type        = std::vector<std::unique_ptr<object> >::value_type;\n               using difference_type   = std::vector<std::unique_ptr<object> >::difference_type;\n               using pointer           = std::vector<std::unique_ptr<object> >::pointer;\n               using reference         = std::vector<std::unique_ptr<object> >::reference;\n            private:\n               std::vector<std::unique_ptr<object>>::const_iterator _itr;\n               const std::vector<std::unique_ptr<object>>& _objects;\n         };\n         const_iterator begin()const { return const_iterator(_objects, _objects.begin()); }\n         const_iterator end()const   { return const_iterator(_objects, _objects.end());   }\n\n         size_t size()const { return _objects.size(); }\n      private:\n         std::vector< std::unique_ptr<object> > _objects;\n   };\n\n} } // graphene::db\n"
  },
  {
    "path": "libraries/db/include/graphene/db/undo_database.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/db/object.hpp>\n#include <deque>\n#include <fc/exception/exception.hpp>\n\nnamespace graphene { namespace db {\n\n   class object_database;\n\n   struct undo_state\n   {\n      std::unordered_map<object_id_type, std::unique_ptr<object> > old_values;\n      std::unordered_map<object_id_type, object_id_type>           old_index_next_ids;\n      std::unordered_set<object_id_type>                           new_ids;\n      std::unordered_map<object_id_type, std::unique_ptr<object> > removed;\n   };\n\n\n   /**\n    * @class undo_database\n    * @brief tracks changes to the state and allows changes to be undone\n    *\n    */\n   class undo_database\n   {\n      public:\n         explicit undo_database( object_database& db ):_db(db){}\n\n         class session\n         {\n            public:\n               session( session&& mv )\n               :_db(mv._db),_apply_undo(mv._apply_undo)\n               {\n                  mv._apply_undo = false;\n               }\n               ~session();\n               void commit() { _apply_undo = false; _db.commit();  }\n               void undo()   { if( _apply_undo ) _db.undo(); _apply_undo = false; }\n               void merge()  { if( _apply_undo ) _db.merge(); _apply_undo = false; }\n\n               session& operator = ( session&& mv )\n               { try {\n                  if( this == &mv ) return *this;\n                  if( _apply_undo ) _db.undo();\n                  _apply_undo = mv._apply_undo;\n                  mv._apply_undo = false;\n                  return *this;\n               } FC_CAPTURE_AND_RETHROW() }\n\n            private:\n               friend class undo_database;\n\n               explicit session(undo_database& db, bool disable_on_exit = false)\n               : _db(db),_disable_on_exit(disable_on_exit) {}\n\n               undo_database& _db;\n               bool _apply_undo = true;\n               bool _disable_on_exit = false;\n         };\n\n         void    disable();\n         void    enable();\n         bool    enabled()const { return !_disabled; }\n\n         session start_undo_session( bool force_enable = false );\n         /**\n          * This should be called just after an object is created\n          */\n         void on_create( const object& obj );\n         /**\n          * This should be called just before an object is modified\n          *\n          * If it's a new object as of this undo state, its pre-modification value is not stored, because prior to this\n          * undo state, it did not exist. Any modifications in this undo state are irrelevant, as the object will simply\n          * be removed if we undo.\n          */\n         void on_modify( const object& obj );\n         /**\n          * This should be called just before an object is removed.\n          *\n          * If it's a new object as of this undo state, its pre-removal value is not stored, because prior to this undo\n          * state, it did not exist. Now that it's been removed, it doesn't exist again, so nothing has happened.\n          * Instead, remove it from the list of newly created objects (which must be deleted if we undo), as we don't\n          * want to re-delete it if this state is undone.\n          */\n         void on_remove( const object& obj );\n\n         /**\n          *  Removes the last committed session,\n          *  note... this is dangerous if there are\n          *  active sessions... thus active sessions should\n          *  track\n          */\n         void pop_commit();\n\n         std::size_t size()const { return _stack.size(); }\n         void set_max_size(size_t new_max_size) { _max_size = new_max_size; }\n         size_t max_size()const { return _max_size; }\n         uint32_t active_sessions()const { return _active_sessions; }\n\n         const undo_state& head()const;\n\n      private:\n         void undo();\n         void merge();\n         void commit();\n\n         uint32_t                _active_sessions = 0;\n         bool                    _disabled = true;\n         std::deque<undo_state>  _stack;\n         object_database&        _db;\n         size_t                  _max_size = 256;\n   };\n\n} } // graphene::db\n"
  },
  {
    "path": "libraries/db/index.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <fc/io/raw.hpp>\n#include <graphene/db/index.hpp>\n#include <graphene/db/object_database.hpp>\n\nnamespace graphene { namespace db {\n   void base_primary_index::save_undo( const object& obj )\n   { _db.save_undo( obj ); }\n\n   void base_primary_index::on_add( const object& obj )\n   {\n      _db.save_undo_add( obj );\n      for( auto ob : _observers ) ob->on_add( obj );\n   }\n\n   void base_primary_index::on_remove( const object& obj )\n   { _db.save_undo_remove( obj ); for( auto ob : _observers ) ob->on_remove( obj ); }\n\n   void base_primary_index::on_modify( const object& obj )\n   {for( auto ob : _observers ) ob->on_modify(  obj ); }\n} } // graphene::chain\n"
  },
  {
    "path": "libraries/db/object_database.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/db/object_database.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/container/flat.hpp>\n#include <fc/thread/parallel.hpp>\n\nnamespace graphene { namespace db {\n\nobject_database::object_database()\n:_undo_db(*this)\n{\n   _index.resize(_index_size);\n   _undo_db.enable();\n}\n\nvoid object_database::close()\n{\n}\n\nconst object* object_database::find_object( const object_id_type& id )const\n{\n   return get_index(id.space(),id.type()).find( id );\n}\nconst object& object_database::get_object( const object_id_type& id )const\n{\n   return get_index(id.space(),id.type()).get( id );\n}\n\nconst index& object_database::get_index(uint8_t space_id, uint8_t type_id)const\n{\n   FC_ASSERT( _index.size() > space_id,\n              \"Database index ${space_id}.${type_id} does not exist, index size is ${index.size}\",\n              (\"space_id\",space_id)(\"type_id\",type_id)(\"index.size\",_index.size()) );\n   FC_ASSERT( _index[space_id].size() > type_id,\n              \"Database index ${space_id}.${type_id} does not exist, space size is ${index[space_id].size}\",\n              (\"space_id\",space_id)(\"type_id\",type_id)(\"index[space_id].size\",_index[space_id].size()) );\n   const auto& tmp = _index[space_id][type_id]; // it is a unique_ptr\n   FC_ASSERT( tmp != nullptr,\n              \"Database index ${space_id}.${type_id} has not been initialized\",\n              (\"space_id\",space_id)(\"type_id\",type_id) );\n   return *tmp;\n}\nindex& object_database::get_mutable_index(uint8_t space_id, uint8_t type_id)\n{\n   FC_ASSERT( _index.size() > space_id,\n              \"Database index ${space_id}.${type_id} does not exist, index size is ${index.size}\",\n              (\"space_id\",space_id)(\"type_id\",type_id)(\"index.size\",_index.size()) );\n   FC_ASSERT( _index[space_id].size() > type_id ,\n              \"Database index ${space_id}.${type_id} does not exist, space size is ${index[space_id].size}\",\n              (\"space_id\",space_id)(\"type_id\",type_id)(\"index[space_id].size\",_index[space_id].size()) );\n   const auto& idx = _index[space_id][type_id]; // it is a unique_ptr\n   FC_ASSERT( idx != nullptr,\n              \"Database index ${space_id}.${type_id} has not been initialized\",\n              (\"space_id\",space_id)(\"type_id\",type_id) );\n   return *idx;\n}\n\nvoid object_database::flush()\n{\n   const auto tmp_dir = _data_dir / \"object_database.tmp\";\n   const auto old_dir = _data_dir / \"object_database.old\";\n   const auto target_dir = _data_dir / \"object_database\";\n\n   if( fc::exists( tmp_dir ) )\n      fc::remove_all( tmp_dir );\n   fc::create_directories( tmp_dir / \"lock\" );\n   std::vector<fc::future<void>> tasks;\n   constexpr size_t max_tasks = 200;\n   tasks.reserve(max_tasks);\n\n   auto push_task = [this,&tasks,&tmp_dir]( size_t space, size_t type ) {\n      if( _index[space][type] )\n         tasks.push_back( fc::do_parallel( [this,space,type,&tmp_dir] () {\n            _index[space][type]->save( tmp_dir / fc::to_string(space) / fc::to_string(type) );\n         } ) );\n   };\n\n   const auto spaces = _index.size();\n   for( size_t space = 0; space < spaces; ++space )\n   {\n      fc::create_directories( tmp_dir / fc::to_string(space) );\n      const auto types = _index[space].size();\n      for( size_t type = 0; type  <  types; ++type )\n         push_task( space, type );\n   }\n   for( auto& task : tasks )\n      task.wait();\n   fc::remove_all( tmp_dir / \"lock\" );\n   if( fc::exists( target_dir ) )\n   {\n      if( fc::exists( old_dir ) )\n         fc::remove_all( old_dir );\n      fc::rename( target_dir, old_dir );\n   }\n   fc::rename( tmp_dir, target_dir );\n   fc::remove_all( old_dir );\n}\n\nvoid object_database::wipe(const fc::path& data_dir)\n{\n   close();\n   ilog(\"Wiping object database...\");\n   fc::remove_all(data_dir / \"object_database\");\n   ilog(\"Done wiping object database.\");\n}\n\nvoid object_database::open(const fc::path& data_dir)\n{ try {\n   _data_dir = data_dir;\n   if( fc::exists( _data_dir / \"object_database\" / \"lock\" ) )\n   {\n       wlog(\"Ignoring locked object_database\");\n       return;\n   }\n   std::vector<fc::future<void>> tasks;\n   tasks.reserve(200);\n\n   auto push_task = [this,&tasks]( size_t space, size_t type ) {\n      if( _index[space][type] )\n         tasks.push_back( fc::do_parallel( [this,space,type] () {\n            _index[space][type]->open( _data_dir / \"object_database\" / fc::to_string(space) / fc::to_string(type) );\n         } ) );\n   };\n\n   ilog(\"Opening object database from ${d} ...\", (\"d\", data_dir));\n   const auto spaces = _index.size();\n   for( size_t space = 0; space < spaces; ++space )\n   {\n      const auto types = _index[space].size();\n      for( size_t type = 0; type  < types; ++type )\n         push_task( space, type );\n   }\n   for( auto& task : tasks )\n      task.wait();\n   ilog( \"Done opening object database.\" );\n\n} FC_CAPTURE_AND_RETHROW( (data_dir) ) }\n\n\nvoid object_database::pop_undo()\n{ try {\n   _undo_db.pop_commit();\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid object_database::save_undo( const object& obj )\n{\n   _undo_db.on_modify( obj );\n}\n\nvoid object_database::save_undo_add( const object& obj )\n{\n   _undo_db.on_create( obj );\n}\n\nvoid object_database::save_undo_remove(const object& obj)\n{\n   _undo_db.on_remove( obj );\n}\n\n} } // namespace graphene::db\n"
  },
  {
    "path": "libraries/db/undo_database.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/db/object_database.hpp>\n#include <graphene/db/undo_database.hpp>\n#include <fc/reflect/variant.hpp>\n\nnamespace graphene { namespace db {\n\nvoid undo_database::enable()  { _disabled = false; }\nvoid undo_database::disable() { _disabled = true; }\n\nundo_database::session::~session()\n{\n   try {\n      if( _apply_undo ) _db.undo();\n   }\n   catch ( const fc::exception& e )\n   {\n      elog( \"${e}\", (\"e\",e.to_detail_string() ) );\n      std::terminate();\n   }\n   if( _disable_on_exit ) _db.disable();\n}\n\nundo_database::session undo_database::start_undo_session( bool force_enable )\n{\n   if( _disabled && !force_enable ) return session(*this);\n   bool disable_on_exit = _disabled  && force_enable;\n   if( force_enable ) \n      _disabled = false;\n\n   while( size() > max_size() )\n      _stack.pop_front();\n\n   _stack.emplace_back();\n   ++_active_sessions;\n   return session(*this, disable_on_exit );\n}\nvoid undo_database::on_create( const object& obj )\n{\n   if( _disabled ) return;\n\n   if( _stack.empty() )\n      _stack.emplace_back();\n   auto& state = _stack.back();\n   auto index_id = object_id_type( obj.id.space(), obj.id.type(), 0 );\n   auto itr = state.old_index_next_ids.find( index_id );\n   if( itr == state.old_index_next_ids.end() )\n      state.old_index_next_ids[index_id] = obj.id;\n   state.new_ids.insert(obj.id);\n}\nvoid undo_database::on_modify( const object& obj )\n{\n   if( _disabled ) return;\n\n   if( _stack.empty() )\n      _stack.emplace_back();\n   auto& state = _stack.back();\n   if( state.new_ids.find(obj.id) != state.new_ids.end() )\n      return;\n   auto itr =  state.old_values.find(obj.id);\n   if( itr != state.old_values.end() ) return;\n   state.old_values[obj.id] = obj.clone();\n}\nvoid undo_database::on_remove( const object& obj )\n{\n   if( _disabled ) return;\n\n   if( _stack.empty() )\n      _stack.emplace_back();\n   undo_state& state = _stack.back();\n   if( state.new_ids.count(obj.id) > 0 )\n   {\n      state.new_ids.erase(obj.id);\n      return;\n   }\n   if( state.old_values.count(obj.id) > 0 )\n   {\n      state.removed[obj.id] = std::move(state.old_values[obj.id]);\n      state.old_values.erase(obj.id);\n      return;\n   }\n   if( state.removed.count(obj.id) > 0 ) return;\n   state.removed[obj.id] = obj.clone();\n}\n\nvoid undo_database::undo()\n{ try {\n   FC_ASSERT( !_disabled );\n   FC_ASSERT( _active_sessions > 0 );\n   disable();\n\n   auto& state = _stack.back();\n   for( auto& item : state.old_values )\n   {\n      _db.modify( _db.get_object( item.second->id ), [&]( object& obj ){ obj.move_from( *item.second ); } );\n   }\n\n   for( auto ritr = state.new_ids.begin(); ritr != state.new_ids.end(); ++ritr  )\n   {\n      _db.remove( _db.get_object(*ritr) );\n   }\n\n   for( auto& item : state.old_index_next_ids )\n   {\n      _db.get_mutable_index( item.first.space(), item.first.type() ).set_next_id( item.second );\n   }\n\n   for( auto& item : state.removed )\n      _db.insert( std::move(*item.second) );\n\n   _stack.pop_back();\n   enable();\n   --_active_sessions;\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid undo_database::merge()\n{\n   FC_ASSERT( _active_sessions > 0 );\n   if( _active_sessions == 1 && _stack.size() == 1 )\n   {\n      _stack.pop_back();\n      --_active_sessions;\n      return;\n   }\n   FC_ASSERT( _stack.size() >=2 );\n   auto& state = _stack.back();\n   auto& prev_state = _stack[_stack.size()-2];\n\n   // An object's relationship to a state can be:\n   // in new_ids            : new\n   // in old_values (was=X) : upd(was=X)\n   // in removed (was=X)    : del(was=X)\n   // not in any of above   : nop\n   //\n   // When merging A=prev_state and B=state we have a 4x4 matrix of all possibilities:\n   //\n   //                   |--------------------- B ----------------------|\n   //\n   //                +------------+------------+------------+------------+\n   //                | new        | upd(was=Y) | del(was=Y) | nop        |\n   //   +------------+------------+------------+------------+------------+\n   // / | new        | N/A        | new       A| nop       C| new       A|\n   // | +------------+------------+------------+------------+------------+\n   // | | upd(was=X) | N/A        | upd(was=X)A| del(was=X)C| upd(was=X)A|\n   // A +------------+------------+------------+------------+------------+\n   // | | del(was=X) | N/A        | N/A        | N/A        | del(was=X)A|\n   // | +------------+------------+------------+------------+------------+\n   // \\ | nop        | new       B| upd(was=Y)B| del(was=Y)B| nop      AB|\n   //   +------------+------------+------------+------------+------------+\n   //\n   // Each entry was composed by labelling what should occur in the given case.\n   //\n   // Type A means the composition of states contains the same entry as the first of the two merged states for that object.\n   // Type B means the composition of states contains the same entry as the second of the two merged states for that object.\n   // Type C means the composition of states contains an entry different from either of the merged states for that object.\n   // Type N/A means the composition of states violates causal timing.\n   // Type AB means both type A and type B simultaneously.\n   //\n   // The merge() operation is defined as modifying prev_state in-place to be the state object which represents the composition of\n   // state A and B.\n   //\n   // Type A (and AB) can be implemented as a no-op; prev_state already contains the correct value for the merged state.\n   // Type B (and AB) can be implemented by copying from state to prev_state.\n   // Type C needs special case-by-case logic.\n   // Type N/A can be ignored or assert(false) as it can only occur if prev_state and state have illegal values\n   // (a serious logic error which should never happen).\n   //\n\n   // We can only be outside type A/AB (the nop path) if B is not nop, so it suffices to iterate through B's three containers.\n\n   // *+upd\n   for( auto& obj : state.old_values )\n   {\n      if( prev_state.new_ids.find(obj.second->id) != prev_state.new_ids.end() )\n      {\n         // new+upd -> new, type A\n         continue;\n      }\n      if( prev_state.old_values.find(obj.second->id) != prev_state.old_values.end() )\n      {\n         // upd(was=X) + upd(was=Y) -> upd(was=X), type A\n         continue;\n      }\n      // del+upd -> N/A\n      assert( prev_state.removed.find(obj.second->id) == prev_state.removed.end() );\n      // nop+upd(was=Y) -> upd(was=Y), type B\n      prev_state.old_values[obj.second->id] = std::move(obj.second);\n   }\n\n   // *+new, but we assume the N/A cases don't happen, leaving type B nop+new -> new\n   for( auto id : state.new_ids )\n      prev_state.new_ids.insert(id);\n\n   // old_index_next_ids can only be updated, iterate over *+upd cases\n   for( auto& item : state.old_index_next_ids )\n   {\n      if( prev_state.old_index_next_ids.find( item.first ) == prev_state.old_index_next_ids.end() )\n      {\n         // nop+upd(was=Y) -> upd(was=Y), type B\n         prev_state.old_index_next_ids[item.first] = item.second;\n         continue;\n      }\n      else\n      {\n         // upd(was=X)+upd(was=Y) -> upd(was=X), type A\n         // type A implementation is a no-op, as discussed above, so there is no code here\n         continue;\n      }\n   }\n\n   // *+del\n   for( auto& obj : state.removed )\n   {\n      if( prev_state.new_ids.find(obj.second->id) != prev_state.new_ids.end() )\n      {\n         // new + del -> nop (type C)\n         prev_state.new_ids.erase(obj.second->id);\n         continue;\n      }\n      auto it = prev_state.old_values.find(obj.second->id);\n      if( it != prev_state.old_values.end() )\n      {\n         // upd(was=X) + del(was=Y) -> del(was=X)\n         prev_state.removed[obj.second->id] = std::move(it->second);\n         prev_state.old_values.erase(obj.second->id);\n         continue;\n      }\n      // del + del -> N/A\n      assert( prev_state.removed.find( obj.second->id ) == prev_state.removed.end() );\n      // nop + del(was=Y) -> del(was=Y)\n      prev_state.removed[obj.second->id] = std::move(obj.second);\n   }\n   _stack.pop_back();\n   --_active_sessions;\n}\nvoid undo_database::commit()\n{\n   FC_ASSERT( _active_sessions > 0 );\n   --_active_sessions;\n}\n\nvoid undo_database::pop_commit()\n{\n   FC_ASSERT( _active_sessions == 0 );\n   FC_ASSERT( !_stack.empty() );\n\n   disable();\n   try {\n      auto& state = _stack.back();\n\n      for( auto& item : state.old_values )\n      {\n         _db.modify( _db.get_object( item.second->id ), [&]( object& obj ){ obj.move_from( *item.second ); } );\n      }\n\n      for( auto ritr = state.new_ids.begin(); ritr != state.new_ids.end(); ++ritr  )\n      {\n         _db.remove( _db.get_object(*ritr) );\n      }\n\n      for( auto& item : state.old_index_next_ids )\n      {\n         _db.get_mutable_index( item.first.space(), item.first.type() ).set_next_id( item.second );\n      }\n\n      for( auto& item : state.removed )\n         _db.insert( std::move(*item.second) );\n\n      _stack.pop_back();\n   }\n   catch ( const fc::exception& e )\n   {\n      elog( \"error popping commit ${e}\", (\"e\", e.to_detail_string() )  );\n      enable();\n      throw;\n   }\n   enable();\n}\nconst undo_state& undo_database::head()const\n{\n   FC_ASSERT( !_stack.empty() );\n   return _stack.back();\n}\n\n} } // graphene::db\n"
  },
  {
    "path": "libraries/egenesis/CMakeLists.txt",
    "content": "if( GRAPHENE_EGENESIS_JSON )\n  set( embed_genesis_args \"${GRAPHENE_EGENESIS_JSON}\" )\nelse( GRAPHENE_EGENESIS_JSON )\n  set( embed_genesis_args \"genesis.json\" )\nendif( GRAPHENE_EGENESIS_JSON )\n\nadd_custom_target( build_egenesis_cpp\n   BYPRODUCTS\n      \"${CMAKE_CURRENT_BINARY_DIR}/egenesis_brief.cpp\"\n      \"${CMAKE_CURRENT_BINARY_DIR}/egenesis_full.cpp\"\n   WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\n   COMMAND ${CMAKE_COMMAND}\n        -DINIT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}\n        -DINIT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}\n        -Dembed_genesis_args=${embed_genesis_args}\n        -P ${CMAKE_CURRENT_SOURCE_DIR}/embed_genesis.cmake\n   COMMENT \"Generating egenesis\"\n   DEPENDS\n      \"${GRAPHENE_EGENESIS_JSON}\"\n      \"${CMAKE_CURRENT_SOURCE_DIR}/egenesis_brief.cpp.tmpl\"\n      \"${CMAKE_CURRENT_SOURCE_DIR}/egenesis_full.cpp.tmpl\"\n)\n\nadd_library( graphene_egenesis_none egenesis_none.cpp\n             include/graphene/egenesis/egenesis.hpp )\nadd_library( graphene_egenesis_brief \"${CMAKE_CURRENT_BINARY_DIR}/egenesis_brief.cpp\"\n             include/graphene/egenesis/egenesis.hpp )\nadd_dependencies( graphene_egenesis_brief build_egenesis_cpp )\nadd_library( graphene_egenesis_full  \"${CMAKE_CURRENT_BINARY_DIR}/egenesis_full.cpp\"\n             include/graphene/egenesis/egenesis.hpp )\nadd_dependencies( graphene_egenesis_full build_egenesis_cpp )\n\ntarget_link_libraries( graphene_egenesis_none graphene_chain fc )\ntarget_link_libraries( graphene_egenesis_brief graphene_chain fc )\ntarget_link_libraries( graphene_egenesis_full graphene_chain fc )\n\ntarget_include_directories( graphene_egenesis_none\n   PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\ntarget_include_directories( graphene_egenesis_brief\n   PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\ntarget_include_directories( graphene_egenesis_full\n   PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nINSTALL( TARGETS\n   graphene_egenesis_none graphene_egenesis_brief graphene_egenesis_full\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "libraries/egenesis/README-dev.md",
    "content": "Use this key to produce blocks with the `genesis-dev.json` Genesis.\nThe following line may be added directly to `config.ini`:\n\n```\nprivate-key = [\"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"]\n```\n"
  },
  {
    "path": "libraries/egenesis/egenesis_brief.cpp.tmpl",
    "content": "${generated_file_banner}\n/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/types.hpp>\n#include <graphene/egenesis/egenesis.hpp>\n\nnamespace graphene { namespace egenesis {\n\nusing namespace graphene::chain;\n\nchain_id_type get_egenesis_chain_id()\n{\n   return chain_id_type( \"${chain_id}\" );\n}\n\nvoid compute_egenesis_json( std::string& result )\n{\n   result = \"\";\n}\n\nfc::sha256 get_egenesis_json_hash()\n{\n   return fc::sha256( \"${genesis_json_hash}\" );\n}\n\n} }\n"
  },
  {
    "path": "libraries/egenesis/egenesis_full.cpp.tmpl",
    "content": "${generated_file_banner}\n/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/types.hpp>\n#include <graphene/egenesis/egenesis.hpp>\n\nnamespace graphene { namespace egenesis {\n\nusing namespace graphene::chain;\n\nstatic const char* genesis_json_array[${genesis_json_array_height}] =\n{\n${genesis_json_array}\n};\n\nchain_id_type get_egenesis_chain_id()\n{\n   return chain_id_type( \"${chain_id}\" );\n}\n\nvoid compute_egenesis_json( std::string& result )\n{\n   result.reserve( ${genesis_json_length} );\n   result.resize(0);\n   for( size_t i=0; i<${genesis_json_array_height}; i++ )\n   {\n      result.append( genesis_json_array[i] );\n   }\n}\n\nfc::sha256 get_egenesis_json_hash()\n{\n   return fc::sha256( \"${genesis_json_hash}\" );\n}\n\n} }\n"
  },
  {
    "path": "libraries/egenesis/egenesis_none.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/egenesis/egenesis.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace egenesis {\n\nusing namespace graphene::chain;\n\nchain_id_type get_egenesis_chain_id()\n{\n   return chain_id_type();\n}\n\nvoid compute_egenesis_json( std::string& result )\n{\n   result = \"\";\n}\n\nfc::sha256 get_egenesis_json_hash()\n{\n   return fc::sha256::hash( \"\" );\n}\n\n} }\n"
  },
  {
    "path": "libraries/egenesis/genesis-dev.json",
    "content": "{\n  \"initial_timestamp\": \"2019-02-14T20:32:55\",\n  \"max_core_supply\": \"1000000000000000\",\n  \"initial_parameters\": {\n    \"current_fees\": {\n      \"parameters\": [[\n          0,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 1000000\n          }\n        ],[\n          1,{\n            \"fee\": 500000\n          }\n        ],[\n          2,{\n            \"fee\": 0\n          }\n        ],[\n          3,{\n            \"fee\": 2000000\n          }\n        ],[\n          4,{}\n        ],[\n          5,{\n            \"basic_fee\": 500000,\n            \"premium_fee\": 200000000,\n            \"price_per_kbyte\": 100000\n          }\n        ],[\n          6,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 100000\n          }\n        ],[\n          7,{\n            \"fee\": 300000\n          }\n        ],[\n          8,{\n            \"membership_annual_fee\": 200000000,\n            \"membership_lifetime_fee\": 1000000000\n          }\n        ],[\n          9,{\n            \"fee\": 50000000\n          }\n        ],[\n          10,{\n            \"symbol3\": \"50000000000\",\n            \"symbol4\": \"30000000000\",\n            \"long_symbol\": 500000000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          11,{\n            \"fee\": 50000000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          12,{\n            \"fee\": 50000000\n          }\n        ],[\n          13,{\n            \"fee\": 50000000\n          }\n        ],[\n          14,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 100000\n          }\n        ],[\n          15,{\n            \"fee\": 2000000\n          }\n        ],[\n          16,{\n            \"fee\": 100000\n          }\n        ],[\n          17,{\n            \"fee\": 10000000\n          }\n        ],[\n          18,{\n            \"fee\": 50000000\n          }\n        ],[\n          19,{\n            \"fee\": 100000\n          }\n        ],[\n          20,{\n            \"fee\": 500000000\n          }\n        ],[\n          21,{\n            \"fee\": 2000000\n          }\n        ],[\n          22,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          23,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          24,{\n            \"fee\": 100000\n          }\n        ],[\n          25,{\n            \"fee\": 100000\n          }\n        ],[\n          26,{\n            \"fee\": 100000\n          }\n        ],[\n          27,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          28,{\n            \"fee\": 0\n          }\n        ],[\n          29,{\n            \"fee\": 500000000\n          }\n        ],[\n          30,{\n            \"fee\": 2000000\n          }\n        ],[\n          31,{\n            \"fee\": 100000\n          }\n        ],[\n          32,{\n            \"fee\": 100000\n          }\n        ],[\n          33,{\n            \"fee\": 2000000\n          }\n        ],[\n          34,{\n            \"fee\": 500000000\n          }\n        ],[\n          35,{\n            \"fee\": 100000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          36,{\n            \"fee\": 100000\n          }\n        ],[\n          37,{}\n        ],[\n          38,{\n            \"fee\": 2000000,\n            \"price_per_kbyte\": 10\n          }\n        ],[\n          39,{\n            \"fee\": 500000,\n            \"price_per_output\": 500000\n          }\n        ],[\n          40,{\n            \"fee\": 500000,\n            \"price_per_output\": 500000\n          }\n        ],[\n          41,{\n            \"fee\": 500000\n          }\n        ],[\n          42,{}\n        ],[\n          43,{\n            \"fee\": 2000000\n          }\n        ],[\n          44,{}\n        ],[\n          45,{\n            \"fee\": 2000000\n          }\n        ],[\n          46,{}\n        ],[\n          47,{\n            \"fee\": 2000000\n          }\n        ],[\n          48,{\n            \"fee\": 2000000\n          }\n        ]\n      ],\n      \"scale\": 10000\n    },\n    \"block_interval\": 5,\n    \"maintenance_interval\": 86400,\n    \"maintenance_skip_slots\": 3,\n    \"committee_proposal_review_period\": 1209600,\n    \"maximum_transaction_size\": 2048,\n    \"maximum_block_size\": 2000000,\n    \"maximum_time_until_expiration\": 86400,\n    \"maximum_proposal_lifetime\": 2419200,\n    \"maximum_asset_whitelist_authorities\": 10,\n    \"maximum_asset_feed_publishers\": 10,\n    \"maximum_witness_count\": 1001,\n    \"maximum_committee_count\": 1001,\n    \"maximum_authority_membership\": 10,\n    \"reserve_percent_of_fee\": 2000,\n    \"network_percent_of_fee\": 2000,\n    \"lifetime_referrer_percent_of_fee\": 3000,\n    \"cashback_vesting_period_seconds\": 31536000,\n    \"cashback_vesting_threshold\": 10000000,\n    \"count_non_member_votes\": true,\n    \"allow_non_member_whitelists\": false,\n    \"witness_pay_per_block\": 1000000,\n    \"worker_budget_per_day\": \"50000000000\",\n    \"max_predicate_opcode\": 1,\n    \"fee_liquidation_threshold\": 10000000,\n    \"accounts_per_fee_scale\": 1000,\n    \"account_fee_scale_bitshifts\": 4,\n    \"max_authority_depth\": 2,\n    \"extensions\": {\n        \"updatable_htlc_options\": {\n            \"max_timeout_secs\": 2592000,\n            \"max_preimage_size\": 1024000\n        }\n    }\n  },\n  \"initial_accounts\": [{\n      \"name\": \"init0\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init1\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init2\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init3\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init4\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init5\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init6\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init7\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init8\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init9\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"init10\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": true\n    },{\n      \"name\": \"nathan\",\n      \"owner_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"active_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\",\n      \"is_lifetime_member\": false\n    }\n  ],\n  \"initial_assets\": [],\n  \"initial_balances\": [{\n      \"owner\": \"BTSFAbAx7yuxt725qSZvfwWqkdCwp9ZnUama\",\n      \"asset_symbol\": \"BTS\",\n      \"amount\": \"1000000000000000\"\n    }\n  ],\n  \"initial_vesting_balances\": [],\n  \"initial_active_witnesses\": 11,\n  \"initial_witness_candidates\": [{\n      \"owner_name\": \"init0\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init1\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init2\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init3\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init4\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init5\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init6\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init7\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init8\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init9\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    },{\n      \"owner_name\": \"init10\",\n      \"block_signing_key\": \"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\"\n    }\n  ],\n  \"initial_committee_candidates\": [{\n      \"owner_name\": \"init0\"\n    },{\n      \"owner_name\": \"init1\"\n    },{\n      \"owner_name\": \"init2\"\n    },{\n      \"owner_name\": \"init3\"\n    },{\n      \"owner_name\": \"init4\"\n    },{\n      \"owner_name\": \"init5\"\n    },{\n      \"owner_name\": \"init6\"\n    },{\n      \"owner_name\": \"init7\"\n    },{\n      \"owner_name\": \"init8\"\n    },{\n      \"owner_name\": \"init9\"\n    },{\n      \"owner_name\": \"init10\"\n    }\n  ],\n  \"initial_worker_candidates\": [],\n  \"immutable_parameters\": {\n    \"min_committee_member_count\": 11,\n    \"min_witness_count\": 11,\n    \"num_special_accounts\": 0,\n    \"num_special_assets\": 0\n  }\n}\n"
  },
  {
    "path": "libraries/egenesis/include/graphene/egenesis/egenesis.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#pragma once\n\n#include <string>\n\n#include <fc/crypto/sha256.hpp>\n#include <graphene/protocol/types.hpp>\n#include <graphene/chain/genesis_state.hpp>\n\nnamespace graphene { namespace egenesis {\n\n/**\n * Get the chain ID of the built-in egenesis, or chain_id_type()\n * if none was compiled in.\n */\ngraphene::chain::chain_id_type get_egenesis_chain_id();\n\n/**\n * Get the egenesis JSON, or the empty string if none was compiled in.\n */\nvoid compute_egenesis_json( std::string& result );\n\n/**\n * The file returned by compute_egenesis_json() should have this hash.\n */\nfc::sha256 get_egenesis_json_hash();\n\n} } // graphene::egenesis\n"
  },
  {
    "path": "libraries/egenesis/seed-nodes.txt",
    "content": "\"bts-seed1.abit-more.com:62015\",     // abit           (Germany)\n\"seed.roelandp.nl:1776\",             // roelandp       (Canada)\n\"seed1.xbts.io:1776\",                // xbts.io        (Germany)\n\"seed2.xbts.io:1776\",                // xbts.io        (Germany)\n\"seed4.xbts.io:1776\",                // xbts.io        (Germany)\n\"seeds.btsnodes.com:1776\",           // Community\n"
  },
  {
    "path": "libraries/egenesis/test.json",
    "content": ""
  },
  {
    "path": "libraries/net/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/net/*.hpp\")\n\nset(SOURCES node.cpp\n            stcp_socket.cpp\n            core_messages.cpp\n            exceptions.cpp\n            peer_database.cpp\n            peer_connection.cpp\n            message.cpp\n            message_oriented_connection.cpp)\n\nadd_library( graphene_net ${SOURCES} ${HEADERS} )\n\ntarget_link_libraries( graphene_net \n  PUBLIC graphene_db graphene_protocol fc )\ntarget_include_directories( graphene_net \n  PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\"\n  PRIVATE \"${CMAKE_SOURCE_DIR}/libraries/chain/include\"\n)\n\nif(MSVC)\n  set_source_files_properties( node.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\nif (USE_PCH)\n  set_target_properties(graphene_net PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)\n  cotire(graphene_net)\nendif(USE_PCH)\n\ninstall( TARGETS\n   graphene_net\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\ninstall( FILES ${HEADERS} DESTINATION \"include/graphene/net\" )\n"
  },
  {
    "path": "libraries/net/core_messages.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/net/core_messages.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace net {\n\n  const core_message_type_enum trx_message::type                             = core_message_type_enum::trx_message_type;\n  const core_message_type_enum block_message::type                           = core_message_type_enum::block_message_type;\n  const core_message_type_enum item_ids_inventory_message::type              = core_message_type_enum::item_ids_inventory_message_type;\n  const core_message_type_enum blockchain_item_ids_inventory_message::type   = core_message_type_enum::blockchain_item_ids_inventory_message_type;\n  const core_message_type_enum fetch_blockchain_item_ids_message::type       = core_message_type_enum::fetch_blockchain_item_ids_message_type;\n  const core_message_type_enum fetch_items_message::type                     = core_message_type_enum::fetch_items_message_type;\n  const core_message_type_enum item_not_available_message::type              = core_message_type_enum::item_not_available_message_type;\n  const core_message_type_enum hello_message::type                           = core_message_type_enum::hello_message_type;\n  const core_message_type_enum connection_accepted_message::type             = core_message_type_enum::connection_accepted_message_type;\n  const core_message_type_enum connection_rejected_message::type             = core_message_type_enum::connection_rejected_message_type;\n  const core_message_type_enum address_request_message::type                 = core_message_type_enum::address_request_message_type;\n  const core_message_type_enum address_message::type                         = core_message_type_enum::address_message_type;\n  const core_message_type_enum closing_connection_message::type              = core_message_type_enum::closing_connection_message_type;\n  const core_message_type_enum current_time_request_message::type            = core_message_type_enum::current_time_request_message_type;\n  const core_message_type_enum current_time_reply_message::type              = core_message_type_enum::current_time_reply_message_type;\n  const core_message_type_enum check_firewall_message::type                  = core_message_type_enum::check_firewall_message_type;\n  const core_message_type_enum check_firewall_reply_message::type            = core_message_type_enum::check_firewall_reply_message_type;\n  const core_message_type_enum get_current_connections_request_message::type = core_message_type_enum::get_current_connections_request_message_type;\n  const core_message_type_enum get_current_connections_reply_message::type   = core_message_type_enum::get_current_connections_reply_message_type;\n\n} } // graphene::net\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::trx_message, BOOST_PP_SEQ_NIL, (trx) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::block_message, BOOST_PP_SEQ_NIL, (block)(block_id) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::item_id, BOOST_PP_SEQ_NIL,\n                               (item_type)\n                               (item_hash) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::item_ids_inventory_message, BOOST_PP_SEQ_NIL,\n                                                  (item_type)\n                                                  (item_hashes_available) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::blockchain_item_ids_inventory_message, BOOST_PP_SEQ_NIL,\n                                                             (total_remaining_item_count)\n                                                             (item_type)\n                                                             (item_hashes_available) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::fetch_blockchain_item_ids_message, BOOST_PP_SEQ_NIL,\n                                                         (item_type)\n                                                         (blockchain_synopsis) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::fetch_items_message, BOOST_PP_SEQ_NIL,\n                                           (item_type)\n                                           (items_to_fetch) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::item_not_available_message, BOOST_PP_SEQ_NIL, (requested_item) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::hello_message, BOOST_PP_SEQ_NIL,\n                                     (user_agent)\n                                     (core_protocol_version)\n                                     (inbound_address)\n                                     (inbound_port)\n                                     (outbound_port)\n                                     (node_public_key)\n                                     (signed_shared_secret)\n                                     (chain_id)\n                                     (user_data) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::connection_accepted_message, BOOST_PP_SEQ_NIL, BOOST_PP_SEQ_NIL )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::connection_rejected_message, BOOST_PP_SEQ_NIL,\n                                                   (user_agent)\n                                                   (core_protocol_version)\n                                                   (remote_endpoint)\n                                                   (reason_code)\n                                                   (reason_string))\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::address_request_message, BOOST_PP_SEQ_NIL, BOOST_PP_SEQ_NIL )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::address_info, BOOST_PP_SEQ_NIL,\n                                    (remote_endpoint)\n                                    (last_seen_time)\n                                    (latency)\n                                    (node_id)\n                                    (direction)\n                                    (firewalled) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::address_message, BOOST_PP_SEQ_NIL, (addresses) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::closing_connection_message, BOOST_PP_SEQ_NIL,\n                                                  (reason_for_closing)\n                                                  (closing_due_to_error)\n                                                  (error) )\n\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::current_time_request_message, BOOST_PP_SEQ_NIL, (request_sent_time))\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::current_time_reply_message, BOOST_PP_SEQ_NIL,\n                                                 (request_sent_time)\n                                                 (request_received_time)\n                                                 (reply_transmitted_time))\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::check_firewall_message, BOOST_PP_SEQ_NIL, (node_id)(endpoint_to_check))\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::check_firewall_reply_message, BOOST_PP_SEQ_NIL,\n                                (node_id)(endpoint_checked)(result))\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::get_current_connections_request_message,\n                                BOOST_PP_SEQ_NIL, BOOST_PP_SEQ_NIL)\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::current_connection_data, BOOST_PP_SEQ_NIL,\n                                              (connection_duration)\n                                              (remote_endpoint)\n                                              (node_id)\n                                              (clock_offset)\n                                              (round_trip_delay)\n                                              (connection_direction)\n                                              (firewalled)\n                                              (user_data))\nFC_REFLECT_DERIVED_NO_TYPENAME(graphene::net::get_current_connections_reply_message, BOOST_PP_SEQ_NIL,\n                                                            (upload_rate_one_minute)\n                                                            (download_rate_one_minute)\n                                                            (upload_rate_fifteen_minutes)\n                                                            (download_rate_fifteen_minutes)\n                                                            (upload_rate_one_hour)\n                                                            (download_rate_one_hour)\n                                                            (current_connections))\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::trx_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::block_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::item_id )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::item_ids_inventory_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::blockchain_item_ids_inventory_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::fetch_blockchain_item_ids_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::fetch_items_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::item_not_available_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::hello_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::connection_accepted_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::connection_rejected_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::address_request_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::address_info )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::address_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::closing_connection_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::current_time_request_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::current_time_reply_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::check_firewall_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::check_firewall_reply_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::get_current_connections_request_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::current_connection_data )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::get_current_connections_reply_message )\n"
  },
  {
    "path": "libraries/net/exceptions.cpp",
    "content": "/*\n * Copyright (c) 2019 BitShares Blockchain Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/net/exceptions.hpp>\n\nnamespace graphene { namespace net {\n\n   FC_IMPLEMENT_EXCEPTION( net_exception, 90000, \"P2P Networking Exception\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( send_queue_overflow,                 net_exception, 90001,\n                                   \"send queue for this peer exceeded maximum size\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( insufficient_relay_fee,              net_exception, 90002,\n                                   \"insufficient relay fee\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( already_connected_to_requested_peer, net_exception, 90003,\n                                   \"already connected to requested peer\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( block_older_than_undo_history,       net_exception, 90004,\n                                   \"block is older than our undo history allows us to process\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( peer_is_on_an_unreachable_fork,      net_exception, 90005,\n                                   \"peer is on another fork\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( unlinkable_block_exception,          net_exception, 90006, \"unlinkable block\" )\n   FC_IMPLEMENT_DERIVED_EXCEPTION( block_timestamp_in_future_exception, net_exception, 90007,\n                                   \"block timestamp in the future\" )\n\n} }\n"
  },
  {
    "path": "libraries/net/include/graphene/net/config.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <stddef.h>\n\n#define GRAPHENE_NET_PROTOCOL_VERSION                        106\n\n/**\n * Define this to enable debugging code in the p2p network interface.\n * This is code that would never be executed in normal operation, but is\n * used for automated testing (creating artificial net splits,\n * tracking where messages came from and when)\n */\n#define ENABLE_P2P_DEBUGGING_API                             1\n\n/**\n * 2MiB\n */\n#define MAX_MESSAGE_SIZE                                     1024*1024*2\n#define GRAPHENE_NET_DEFAULT_PEER_CONNECTION_RETRY_TIME      30 // seconds\n\n/**\n * AFter trying all peers, how long to wait before we check to\n * see if there are peers we can try again.\n */\n#define GRAPHENE_PEER_DATABASE_RETRY_DELAY                   15 // seconds\n\n#define GRAPHENE_NET_PEER_HANDSHAKE_INACTIVITY_TIMEOUT       5\n\n#define GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT                 20\n\n/* uncomment next line to use testnet seed ip and port */\n//#define GRAPHENE_TEST_NETWORK                                1\n\n#define GRAPHENE_NET_TEST_SEED_IP                            \"104.236.44.210\" // autogenerated\n#define GRAPHENE_NET_TEST_P2P_PORT                           1700\n#define GRAPHENE_NET_DEFAULT_P2P_PORT                        1776\n#define GRAPHENE_NET_DEFAULT_DESIRED_CONNECTIONS             20\n#define GRAPHENE_NET_DEFAULT_MAX_CONNECTIONS                 200\n\n#define GRAPHENE_NET_MAXIMUM_QUEUED_MESSAGES_IN_BYTES        (1024 * 1024)\n\n/**\n * When we receive a message from the network, we advertise it to\n * our peers and save a copy in a cache were we will find it if\n * a peer requests it.  We expire out old items out of the cache\n * after this number of blocks go by.\n *\n * Recently lowered from 30 to match the default expiration time\n * the web wallet imposes on transactions.\n */\n#define GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS        5\n\n/**\n * We prevent a peer from offering us a list of blocks which, if we fetched them\n * all, would result in a blockchain that extended into the future.\n * This parameter gives us some wiggle room, allowing a peer to give us blocks\n * that would put our blockchain up to an hour in the future, just in case\n * our clock is a bit off.\n */\n#define GRAPHENE_NET_FUTURE_SYNC_BLOCKS_GRACE_PERIOD_SEC     (60 * 60)\n\n#define GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES           2\n\n#define GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING      200\n\n/**\n * During normal operation, how many items will be fetched from each\n * peer at a time.  This will only come into play when the network\n * is being flooded -- typically transactions will be fetched as soon\n * as we find out about them, so only one item will be requested\n * at a time.\n *\n * No tests have been done to find the optimal value for this\n * parameter, so consider increasing or decreasing it if performance\n * during flooding is lacking.\n */\n#define GRAPHENE_NET_MAX_ITEMS_PER_PEER_DURING_NORMAL_OPERATION  1\n\n/**\n * Instead of fetching all item IDs from a peer, then fetching all blocks\n * from a peer, we will interleave them.  Fetch at least this many block IDs,\n * then switch into block-fetching mode until the number of blocks we know about\n * but haven't yet fetched drops below this\n */\n#define GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH               10000\n\n#define GRAPHENE_NET_MAX_TRX_PER_SECOND                      1000\n\n#define GRAPHENE_NET_MAX_NESTED_OBJECTS                      (250)\n\n#define MAXIMUM_PEERDB_SIZE 1000\n\nconstexpr size_t MAX_ADDRESSES_TO_HANDLE_AT_ONCE = 200;\n\nconstexpr size_t MAX_BLOCKS_TO_HANDLE_AT_ONCE = 200;\nconstexpr size_t MAX_SYNC_BLOCKS_TO_PREFETCH = 10 * MAX_BLOCKS_TO_HANDLE_AT_ONCE;\n"
  },
  {
    "path": "libraries/net/include/graphene/net/core_messages.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/net/config.hpp>\n\n#include <fc/crypto/ripemd160.hpp>\n#include <fc/crypto/elliptic.hpp>\n#include <fc/crypto/sha256.hpp>\n#include <fc/network/ip.hpp>\n#include <fc/time.hpp>\n#include <fc/variant_object.hpp>\n#include <fc/exception/exception.hpp>\n#include <fc/io/enum_type.hpp>\n\n#include <graphene/protocol/block.hpp>\n\n#include <vector>\n\nnamespace graphene { namespace net {\n  using graphene::protocol::signed_transaction;\n  using graphene::protocol::block_id_type;\n  using graphene::protocol::transaction_id_type;\n  using graphene::protocol::signed_block;\n\n  typedef fc::ecc::public_key_data node_id_t;\n  typedef fc::ripemd160 item_hash_t;\n  struct item_id\n  {\n      uint32_t      item_type;\n      item_hash_t   item_hash;\n\n      item_id() {}\n      item_id(uint32_t type, const item_hash_t& hash) :\n        item_type(type),\n        item_hash(hash)\n      {}\n      bool operator==(const item_id& other) const\n      {\n        return item_type == other.item_type &&\n               item_hash == other.item_hash;\n      }\n  };\n\n  enum core_message_type_enum\n  {\n    trx_message_type                             = 1000,\n    block_message_type                           = 1001,\n    core_message_type_first                      = 5000,\n    item_ids_inventory_message_type              = 5001,\n    blockchain_item_ids_inventory_message_type   = 5002,\n    fetch_blockchain_item_ids_message_type       = 5003,\n    fetch_items_message_type                     = 5004,\n    item_not_available_message_type              = 5005,\n    hello_message_type                           = 5006,\n    connection_accepted_message_type             = 5007,\n    connection_rejected_message_type             = 5008,\n    address_request_message_type                 = 5009,\n    address_message_type                         = 5010,\n    closing_connection_message_type              = 5011,\n    current_time_request_message_type            = 5012,\n    current_time_reply_message_type              = 5013,\n    check_firewall_message_type                  = 5014,\n    check_firewall_reply_message_type            = 5015,\n    get_current_connections_request_message_type = 5016,\n    get_current_connections_reply_message_type   = 5017,\n    core_message_type_last                       = 5099\n  };\n\n  const uint32_t core_protocol_version = GRAPHENE_NET_PROTOCOL_VERSION;\n\n   struct trx_message\n   {\n      static const core_message_type_enum type;\n\n      graphene::protocol::precomputable_transaction trx;\n      trx_message() = default;\n      explicit trx_message(const graphene::protocol::signed_transaction& signed_trx) :\n        trx(signed_trx)\n      {}\n   };\n\n   struct block_message\n   {\n      static const core_message_type_enum type;\n\n      block_message(){}\n      block_message(const signed_block& blk )\n      :block(blk),block_id(blk.id()){}\n\n      signed_block    block;\n      block_id_type   block_id;\n\n   };\n\n  struct item_ids_inventory_message\n  {\n    static const core_message_type_enum type;\n\n    uint32_t item_type;\n    std::vector<item_hash_t> item_hashes_available;\n\n    item_ids_inventory_message() {}\n    item_ids_inventory_message(uint32_t item_type, const std::vector<item_hash_t>& item_hashes_available) :\n      item_type(item_type),\n      item_hashes_available(item_hashes_available)\n    {}\n  };\n\n  struct blockchain_item_ids_inventory_message\n  {\n    static const core_message_type_enum type;\n\n    uint32_t total_remaining_item_count;\n    uint32_t item_type;\n    std::vector<item_hash_t> item_hashes_available;\n\n    blockchain_item_ids_inventory_message() {}\n    blockchain_item_ids_inventory_message(uint32_t total_remaining_item_count,\n                                          uint32_t item_type,\n                                          const std::vector<item_hash_t>& item_hashes_available) :\n      total_remaining_item_count(total_remaining_item_count),\n      item_type(item_type),\n      item_hashes_available(item_hashes_available)\n    {}\n  };\n\n  struct fetch_blockchain_item_ids_message\n  {\n    static const core_message_type_enum type;\n\n    uint32_t item_type;\n    std::vector<item_hash_t> blockchain_synopsis;\n\n    fetch_blockchain_item_ids_message() {}\n    fetch_blockchain_item_ids_message(uint32_t item_type, const std::vector<item_hash_t>& blockchain_synopsis) :\n      item_type(item_type),\n      blockchain_synopsis(blockchain_synopsis)\n    {}\n  };\n\n  struct fetch_items_message\n  {\n    static const core_message_type_enum type;\n\n    uint32_t item_type;\n    std::vector<item_hash_t> items_to_fetch;\n\n    fetch_items_message() {}\n    fetch_items_message(uint32_t item_type, const std::vector<item_hash_t>& items_to_fetch) :\n      item_type(item_type),\n      items_to_fetch(items_to_fetch)\n    {}\n  };\n\n  struct item_not_available_message\n  {\n    static const core_message_type_enum type;\n\n    item_id requested_item;\n\n    item_not_available_message() {}\n    item_not_available_message(const item_id& requested_item) :\n      requested_item(requested_item)\n    {}\n  };\n\n  struct hello_message\n  {\n    static const core_message_type_enum type;\n\n    std::string                user_agent;\n    uint32_t                   core_protocol_version;\n    fc::ip::address            inbound_address;\n    uint16_t                   inbound_port;\n    uint16_t                   outbound_port;\n    node_id_t                  node_public_key;\n    fc::ecc::compact_signature signed_shared_secret;\n    fc::sha256                 chain_id;\n    fc::variant_object         user_data;\n\n    hello_message() {}\n    hello_message(const std::string& user_agent,\n                  uint32_t core_protocol_version,\n                  const fc::ip::address& inbound_address,\n                  uint16_t inbound_port,\n                  uint16_t outbound_port,\n                  const node_id_t& node_public_key,\n                  const fc::ecc::compact_signature& signed_shared_secret,\n                  const fc::sha256& chain_id_arg,\n                  const fc::variant_object& user_data ) :\n      user_agent(user_agent),\n      core_protocol_version(core_protocol_version),\n      inbound_address(inbound_address),\n      inbound_port(inbound_port),\n      outbound_port(outbound_port),\n      node_public_key(node_public_key),\n      signed_shared_secret(signed_shared_secret),\n      chain_id(chain_id_arg),\n      user_data(user_data)\n    {}\n  };\n\n  struct connection_accepted_message\n  {\n    static const core_message_type_enum type;\n\n    connection_accepted_message() {}\n  };\n\n  enum class rejection_reason_code { unspecified,\n                                     different_chain,\n                                     already_connected,\n                                     connected_to_self,\n                                     not_accepting_connections,\n                                     blocked,\n                                     invalid_hello_message,\n                                     client_too_old };\n\n  struct connection_rejected_message\n  {\n    static const core_message_type_enum type;\n\n    std::string                                   user_agent;\n    uint32_t                                      core_protocol_version;\n    fc::ip::endpoint                              remote_endpoint;\n    fc::enum_type<uint8_t, rejection_reason_code> reason_code;\n    std::string                                   reason_string;\n\n    connection_rejected_message() {}\n    connection_rejected_message(const std::string& user_agent, uint32_t core_protocol_version,\n                                const fc::ip::endpoint& remote_endpoint, rejection_reason_code reason_code,\n                                const std::string& reason_string) :\n      user_agent(user_agent),\n      core_protocol_version(core_protocol_version),\n      remote_endpoint(remote_endpoint),\n      reason_code(reason_code),\n      reason_string(reason_string)\n    {}\n  };\n\n  struct address_request_message\n  {\n    static const core_message_type_enum type;\n\n    address_request_message() {}\n  };\n\n  enum class peer_connection_direction { unknown, inbound, outbound };\n  enum class firewalled_state { unknown, firewalled, not_firewalled };\n\n  struct address_info\n  {\n    fc::ip::endpoint          remote_endpoint;\n    fc::time_point_sec        last_seen_time;\n    fc::microseconds          latency;\n    node_id_t                 node_id;\n    fc::enum_type<uint8_t, peer_connection_direction> direction;\n    fc::enum_type<uint8_t, firewalled_state> firewalled;\n\n    address_info() {}\n    address_info(const fc::ip::endpoint& remote_endpoint,\n                 const fc::time_point_sec last_seen_time,\n                 const fc::microseconds latency,\n                 const node_id_t& node_id,\n                 peer_connection_direction direction,\n                 firewalled_state firewalled) :\n      remote_endpoint(remote_endpoint),\n      last_seen_time(last_seen_time),\n      latency(latency),\n      node_id(node_id),\n      direction(direction),\n      firewalled(firewalled)\n    {}\n  };\n\n  struct address_message\n  {\n    static const core_message_type_enum type;\n\n    std::vector<address_info> addresses;\n  };\n\n  struct closing_connection_message\n  {\n    static const core_message_type_enum type;\n\n    std::string        reason_for_closing;\n    bool               closing_due_to_error;\n    fc::oexception     error;\n\n    closing_connection_message() : closing_due_to_error(false) {}\n    closing_connection_message(const std::string& reason_for_closing,\n                               bool closing_due_to_error = false,\n                               const fc::oexception& error = fc::oexception()) :\n      reason_for_closing(reason_for_closing),\n      closing_due_to_error(closing_due_to_error),\n      error(error)\n    {}\n  };\n\n  struct current_time_request_message\n  {\n    static const core_message_type_enum type;\n    fc::time_point request_sent_time;\n\n    current_time_request_message(){}\n    current_time_request_message(const fc::time_point request_sent_time) :\n      request_sent_time(request_sent_time)\n    {}\n  };\n\n  struct current_time_reply_message\n  {\n    static const core_message_type_enum type;\n    fc::time_point request_sent_time;\n    fc::time_point request_received_time;\n    fc::time_point reply_transmitted_time;\n\n    current_time_reply_message(){}\n    current_time_reply_message(const fc::time_point request_sent_time,\n                               const fc::time_point request_received_time,\n                               const fc::time_point reply_transmitted_time = fc::time_point()) :\n      request_sent_time(request_sent_time),\n      request_received_time(request_received_time),\n      reply_transmitted_time(reply_transmitted_time)\n    {}\n  };\n\n  struct check_firewall_message\n  {\n    static const core_message_type_enum type;\n    node_id_t node_id;\n    fc::ip::endpoint endpoint_to_check;\n  };\n\n  enum class firewall_check_result\n  {\n    unable_to_check,\n    unable_to_connect,\n    connection_successful\n  };\n\n  struct check_firewall_reply_message\n  {\n    static const core_message_type_enum type;\n    node_id_t node_id;\n    fc::ip::endpoint endpoint_checked;\n    fc::enum_type<uint8_t, firewall_check_result> result;\n  };\n\n  struct get_current_connections_request_message\n  {\n    static const core_message_type_enum type;\n  };\n\n  struct current_connection_data\n  {\n    uint32_t           connection_duration; // in seconds\n    fc::ip::endpoint   remote_endpoint;\n    node_id_t          node_id;\n    fc::microseconds   clock_offset;\n    fc::microseconds   round_trip_delay;\n    fc::enum_type<uint8_t, peer_connection_direction> connection_direction;\n    fc::enum_type<uint8_t, firewalled_state> firewalled;\n    fc::variant_object user_data;\n  };\n\n  struct get_current_connections_reply_message\n  {\n    static const core_message_type_enum type;\n    uint32_t upload_rate_one_minute;\n    uint32_t download_rate_one_minute;\n    uint32_t upload_rate_fifteen_minutes;\n    uint32_t download_rate_fifteen_minutes;\n    uint32_t upload_rate_one_hour;\n    uint32_t download_rate_one_hour;\n    std::vector<current_connection_data> current_connections;\n  };\n\n} } // graphene::net\n\nFC_REFLECT_ENUM( graphene::net::core_message_type_enum,\n                 (trx_message_type)\n                 (block_message_type)\n                 (core_message_type_first)\n                 (item_ids_inventory_message_type)\n                 (blockchain_item_ids_inventory_message_type)\n                 (fetch_blockchain_item_ids_message_type)\n                 (fetch_items_message_type)\n                 (item_not_available_message_type)\n                 (hello_message_type)\n                 (connection_accepted_message_type)\n                 (connection_rejected_message_type)\n                 (address_request_message_type)\n                 (address_message_type)\n                 (closing_connection_message_type)\n                 (current_time_request_message_type)\n                 (current_time_reply_message_type)\n                 (check_firewall_message_type)\n                 (check_firewall_reply_message_type)\n                 (get_current_connections_request_message_type)\n                 (get_current_connections_reply_message_type)\n                 (core_message_type_last) )\nFC_REFLECT_ENUM(graphene::net::rejection_reason_code, (unspecified)\n                                                 (different_chain)\n                                                 (already_connected)\n                                                 (connected_to_self)\n                                                 (not_accepting_connections)\n                                                 (blocked)\n                                                 (invalid_hello_message)\n                                                 (client_too_old))\nFC_REFLECT_ENUM(graphene::net::peer_connection_direction, (unknown)\n                                                     (inbound)\n                                                     (outbound))\nFC_REFLECT_ENUM(graphene::net::firewalled_state, (unknown)\n                                            (firewalled)\n                                            (not_firewalled))\nFC_REFLECT_ENUM(graphene::net::firewall_check_result, (unable_to_check)\n                                                 (unable_to_connect)\n                                                 (connection_successful))\n\nFC_REFLECT_TYPENAME( graphene::net::trx_message )\nFC_REFLECT_TYPENAME( graphene::net::block_message )\nFC_REFLECT_TYPENAME( graphene::net::item_id )\nFC_REFLECT_TYPENAME( graphene::net::item_ids_inventory_message )\nFC_REFLECT_TYPENAME( graphene::net::blockchain_item_ids_inventory_message )\nFC_REFLECT_TYPENAME( graphene::net::fetch_blockchain_item_ids_message )\nFC_REFLECT_TYPENAME( graphene::net::fetch_items_message )\nFC_REFLECT_TYPENAME( graphene::net::item_not_available_message )\nFC_REFLECT_TYPENAME( graphene::net::hello_message )\nFC_REFLECT_TYPENAME( graphene::net::connection_accepted_message )\nFC_REFLECT_TYPENAME( graphene::net::connection_rejected_message )\nFC_REFLECT_TYPENAME( graphene::net::address_request_message )\nFC_REFLECT_TYPENAME( graphene::net::address_info )\nFC_REFLECT_TYPENAME( graphene::net::address_message )\nFC_REFLECT_TYPENAME( graphene::net::closing_connection_message )\nFC_REFLECT_TYPENAME( graphene::net::current_time_request_message )\nFC_REFLECT_TYPENAME( graphene::net::current_time_reply_message )\nFC_REFLECT_TYPENAME( graphene::net::check_firewall_message )\nFC_REFLECT_TYPENAME( graphene::net::check_firewall_reply_message )\nFC_REFLECT_TYPENAME( graphene::net::get_current_connections_request_message )\nFC_REFLECT_TYPENAME( graphene::net::current_connection_data )\nFC_REFLECT_TYPENAME( graphene::net::get_current_connections_reply_message )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::trx_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::block_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::item_id )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::item_ids_inventory_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::blockchain_item_ids_inventory_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::fetch_blockchain_item_ids_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::fetch_items_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::item_not_available_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::hello_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::connection_accepted_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::connection_rejected_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::address_request_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::address_info )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::address_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::closing_connection_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::current_time_request_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::current_time_reply_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::check_firewall_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::check_firewall_reply_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::get_current_connections_request_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::current_connection_data )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::get_current_connections_reply_message )\n\n#include <unordered_map>\n#include <fc/crypto/city.hpp>\n#include <fc/crypto/sha224.hpp>\nnamespace std\n{\n    template<>\n    struct hash<graphene::net::item_id>\n    {\n       size_t operator()(const graphene::net::item_id& item_to_hash) const\n       {\n          return fc::city_hash_size_t((char*)&item_to_hash, sizeof(item_to_hash));\n       }\n    };\n}\n"
  },
  {
    "path": "libraries/net/include/graphene/net/exceptions.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <fc/exception/exception.hpp>\n\nnamespace graphene { namespace net {\n   // registered in node.cpp \n   \n   FC_DECLARE_EXCEPTION( net_exception, 90000 )\n   FC_DECLARE_DERIVED_EXCEPTION( send_queue_overflow,                 net_exception, 90001 ) \n   FC_DECLARE_DERIVED_EXCEPTION( insufficient_relay_fee,              net_exception, 90002 )\n   FC_DECLARE_DERIVED_EXCEPTION( already_connected_to_requested_peer, net_exception, 90003 )\n   FC_DECLARE_DERIVED_EXCEPTION( block_older_than_undo_history,       net_exception, 90004 )\n   FC_DECLARE_DERIVED_EXCEPTION( peer_is_on_an_unreachable_fork,      net_exception, 90005 )\n   FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception,          net_exception, 90006 )\n   FC_DECLARE_DERIVED_EXCEPTION( block_timestamp_in_future_exception, net_exception, 90007 )\n\n} }\n"
  },
  {
    "path": "libraries/net/include/graphene/net/message.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <boost/endian/buffers.hpp>\n\n#include <graphene/protocol/types.hpp>\n\n#include <fc/io/varint.hpp>\n#include <fc/network/ip.hpp>\n#include <fc/crypto/ripemd160.hpp>\n\nnamespace graphene { namespace net {\n\n  /**\n   *  Defines an 8 byte header that is always present because the minimum encrypted packet\n   *  size is 8 bytes (blowfish).  The maximum message size is defined in config.hpp. The channel,\n   *  and message type is also included because almost every channel will have a message type\n   *  field and we might as well include it in the 8 byte header to save space.\n   */\n  struct message_header\n  {\n     boost::endian::little_uint32_buf_t size;   // number of bytes in message, capped at MAX_MESSAGE_SIZE\n     boost::endian::little_uint32_buf_t msg_type;  // every channel gets a 16 bit message type specifier\n     message_header()\n     {\n        size = 0;\n        msg_type = 0;\n     }\n  };\n\n  using message_hash_type = fc::ripemd160;\n\n  /**\n   *  Abstracts the process of packing/unpacking a message for a\n   *  particular channel.\n   */\n  struct message : public message_header\n  {\n     std::vector<char> data;\n\n     message(){}\n\n     message( message&& m )\n     :message_header(m),data( std::move(m.data) ){}\n\n     message( const message& m )\n     :message_header(m),data( m.data ){}\n\n     /**\n      *  Assumes that T::type specifies the message type\n      */\n     template<typename T>\n     message( const T& m )\n     {\n        msg_type = T::type;\n        data     = fc::raw::pack(m);\n        size     = (uint32_t)data.size();\n     }\n\n     message_hash_type id()const\n     {\n        return fc::ripemd160::hash( data.data(), (uint32_t)data.size() );\n     }\n\n     /**\n      *  Automatically checks the type and deserializes T in the\n      *  opposite process from the constructor.\n      */\n     template<typename T>\n     T as()const\n     {\n         try {\n          FC_ASSERT( msg_type.value() == T::type );\n          T tmp;\n          if( data.size() )\n          {\n             fc::datastream<const char*> ds( data.data(), data.size() );\n             fc::raw::unpack( ds, tmp );\n          }\n          else\n          {\n             // just to make sure that tmp shouldn't have any data\n             fc::datastream<const char*> ds( nullptr, 0 );\n             fc::raw::unpack( ds, tmp );\n          }\n          return tmp;\n         } FC_RETHROW_EXCEPTIONS( warn,\n              \"error unpacking network message as a '${type}'  ${x} !=? ${msg_type}\",\n              (\"type\", fc::get_typename<T>::name() )\n              (\"x\", T::type)\n              (\"msg_type\", msg_type.value())\n              );\n     }\n  };\n\n} } // graphene::net\n\nFC_REFLECT_TYPENAME( graphene::net::message_header )\nFC_REFLECT_TYPENAME( graphene::net::message )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::message_header)\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::message)\n"
  },
  {
    "path": "libraries/net/include/graphene/net/message_oriented_connection.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <fc/network/tcp_socket.hpp>\n#include <graphene/net/message.hpp>\n\nnamespace graphene { namespace net {\n\n  namespace detail { class message_oriented_connection_impl; }\n\n  class message_oriented_connection;\n\n  /** receives incoming messages from a message_oriented_connection object */\n  class message_oriented_connection_delegate \n  {\n  public:\n    virtual void on_message(message_oriented_connection* originating_connection, const message& received_message) = 0;\n    virtual void on_connection_closed(message_oriented_connection* originating_connection) = 0;\n  };\n\n  /** uses a secure socket to create a connection that reads and writes a stream of `fc::net::message` objects */\n  class message_oriented_connection\n  {\n     public:\n       message_oriented_connection(message_oriented_connection_delegate* delegate = nullptr);\n       ~message_oriented_connection();\n       fc::tcp_socket& get_socket();\n\n       void accept();\n       void bind(const fc::ip::endpoint& local_endpoint);\n       void connect_to(const fc::ip::endpoint& remote_endpoint);\n\n       void send_message(const message& message_to_send);\n       void close_connection();\n       void destroy_connection();\n\n       uint64_t       get_total_bytes_sent() const;\n       uint64_t       get_total_bytes_received() const;\n       fc::time_point get_last_message_sent_time() const;\n       fc::time_point get_last_message_received_time() const;\n       fc::time_point get_connection_time() const;\n       fc::sha512     get_shared_secret() const;\n     private:\n       std::unique_ptr<detail::message_oriented_connection_impl> my;\n  };\n  typedef std::shared_ptr<message_oriented_connection> message_oriented_connection_ptr;\n\n} } // graphene::net\n"
  },
  {
    "path": "libraries/net/include/graphene/net/node.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/net/core_messages.hpp>\n#include <graphene/net/message.hpp>\n#include <graphene/net/peer_database.hpp>\n\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace net {\n\n  using fc::variant_object;\n  using graphene::protocol::chain_id_type;\n\n  namespace detail\n  {\n    class node_impl;\n  }\n  using node_impl_ptr = std::shared_ptr<detail::node_impl>;\n\n  // during network development, we need to track message propagation across the network\n  // using a structure like this:\n  struct message_propagation_data\n  {\n    fc::time_point received_time;\n    fc::time_point validated_time;\n    node_id_t originating_peer;\n  };\n\n   /**\n    *  @class node_delegate\n    *  @brief used by node reports status to client or fetch data from client\n    */\n   class node_delegate\n   {\n      public:\n         virtual ~node_delegate() = default;\n\n         /**\n          *  If delegate has the item, the network has no need to fetch it.\n          */\n         virtual bool has_item( const net::item_id& id ) = 0;\n\n         /**\n          *  @brief Called when a new block comes in from the network\n          *\n          *  @param blk_msg the message which contains the block\n          *  @param sync_mode true if the message was fetched through the sync process, false during normal operation\n          *  @param contained_transaction_msg_ids container for the transactions to write back into\n          *  @returns true if this message caused the blockchain to switch forks, false if it did not\n          *\n          *  @throws exception if error validating the item, otherwise the item is\n          *          safe to broadcast on.\n          */\n         virtual bool handle_block( const graphene::net::block_message& blk_msg, bool sync_mode,\n                                    std::vector<message_hash_type>& contained_transaction_msg_ids ) = 0;\n\n         /**\n          *  @brief Called when a new transaction comes in from the network\n          *\n          *  @throws exception if error validating the item, otherwise the item is\n          *          safe to broadcast on.\n          */\n         virtual void handle_transaction( const graphene::net::trx_message& trx_msg ) = 0;\n\n         /**\n          *  @brief Called when a new message comes in from the network other than a\n          *         block or a transaction.  Currently there are no other possible\n          *         messages, so this should never be called.\n          *\n          *  @throws exception if error validating the item, otherwise the item is\n          *          safe to broadcast on.\n          */\n         virtual void handle_message( const message& message_to_process ) = 0;\n\n         /**\n          *  Assuming all data elements are ordered in some way, this method should\n          *  return up to limit ids that occur *after* from_id.\n          *  On return, remaining_item_count will be set to the number of items\n          *  in our blockchain after the last item returned in the result,\n          *  or 0 if the result contains the last item in the blockchain\n          */\n         virtual std::vector<item_hash_t> get_block_ids(const std::vector<item_hash_t>& blockchain_synopsis,\n                                                        uint32_t& remaining_item_count,\n                                                        uint32_t limit = 2000) = 0;\n\n         /**\n          *  Given the hash of the requested data, fetch the body.\n          */\n         virtual message get_item( const item_id& id ) = 0;\n\n         virtual chain_id_type get_chain_id()const = 0;\n\n         /**\n          * Returns a synopsis of the blockchain used for syncing.\n          * This consists of a list of selected item hashes from our current preferred\n          * blockchain, exponentially falling off into the past.  Horrible explanation.\n          *\n          * If the blockchain is empty, it will return the empty list.\n          * If the blockchain has one block, it will return a list containing just that block.\n          * If it contains more than one block:\n          *   the first element in the list will be the hash of the highest numbered block that\n          *       we cannot undo\n          *   the second element will be the hash of an item at the half way point in the undoable\n          *       segment of the blockchain\n          *   the third will be ~3/4 of the way through the undoable segment of the block chain\n          *   the fourth will be at ~7/8...\n          *     &c.\n          *   the last item in the list will be the hash of the most recent block on our preferred chain\n          */\n         virtual std::vector<item_hash_t> get_blockchain_synopsis(const item_hash_t& reference_point,\n                                                               uint32_t number_of_blocks_after_reference_point) = 0;\n\n         /**\n          *  Call this after the call to handle_message succeeds.\n          *\n          *  @param item_type the type of the item we're synchronizing, will be the same as item passed to the\n          *                   sync_from() call\n          *  @param item_count the number of items known to the node that haven't been sent to handle_item() yet.\n          *                    After `item_count` more calls to handle_item(), the node will be in sync\n          */\n         virtual void     sync_status( uint32_t item_type, uint32_t item_count ) = 0;\n\n         /**\n          *  Call any time the number of connected peers changes.\n          */\n         virtual void     connection_count_changed( uint32_t c ) = 0;\n\n         virtual uint32_t get_block_number(const item_hash_t& block_id) = 0;\n\n         /**\n          * Returns the time a block was produced (if block_id = 0, returns genesis time).\n          * If we don't know about the block, returns time_point_sec::min()\n          */\n         virtual fc::time_point_sec get_block_time(const item_hash_t& block_id) = 0;\n\n         virtual item_hash_t get_head_block_id() const = 0;\n\n         virtual uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const = 0;\n\n         virtual void error_encountered(const std::string& message, const fc::oexception& error) = 0;\n         virtual uint8_t get_current_block_interval_in_seconds() const = 0;\n\n   };\n\n   /**\n    *  Information about connected peers that the client may want to make\n    *  available to the user.\n    */\n   struct peer_status\n   {\n      uint32_t         version;\n      fc::ip::endpoint host;\n      /** info contains the fields required by bitcoin-rpc's getpeerinfo call, we will likely\n          extend it with our own fields. */\n      fc::variant_object info;\n   };\n\n   /**\n    *  @class node\n    *  @brief provides application independent P2P broadcast and data synchronization\n    *\n    *  Unanswered questions:\n    *    when does the node start establishing network connections and accepting peers?\n    *    we don't have enough info to start synchronizing until sync_from() is called,\n    *    would we have any reason to connect before that?\n    */\n   class node : public std::enable_shared_from_this<node>\n   {\n      public:\n        explicit node(const std::string& user_agent);\n        virtual ~node();\n\n        void close() const;\n\n        void      set_node_delegate( std::shared_ptr<node_delegate> del ) const;\n\n        void      load_configuration( const fc::path& configuration_directory ) const;\n\n        virtual void      listen_to_p2p_network() const;\n        virtual void      connect_to_p2p_network() const;\n\n        /**\n         *  Add endpoint to internal level_map database of potential nodes\n         *  to attempt to connect to.  This database is consulted any time\n         *  the number connected peers falls below the target.\n         */\n        void      add_node( const fc::ip::endpoint& ep ) const;\n\n        /*****\n         * @brief add a list of nodes to seed the p2p network\n         * @param seeds a vector of url strings\n         */\n        void add_seed_nodes( const std::vector<std::string>& seeds ) const;\n\n        /****\n         * @brief add a node to seed the p2p network\n         * @param in the url as a string\n         */\n        void add_seed_node( const std::string& in) const;\n\n        /**\n         *  Attempt to connect to the specified endpoint immediately.\n         */\n        virtual void connect_to_endpoint( const fc::ip::endpoint& ep ) const;\n\n         /**\n          * Specifies the IP address and port on the \"local machine\" that should accept incoming connections.\n          * @note To listen on all IPv4 addresses on the local machine, specify 0.0.0.0 as the address.\n          * @param ep the endpoint (IP address and port)\n          * @param wait_if_not_available keep retrying if port is not available\n          */\n         void set_listen_endpoint( const fc::ip::endpoint& ep, bool wait_if_not_available ) const;\n\n         /**\n          * Specifies the IP address and port on the \"external network\" which other peers should connect to.\n          * @note If the address is unknown (E.G. dynamically allocated), specify 0.0.0.0 as the address.\n          * @param ep the endpoint (IP address and port)\n          */\n         void set_inbound_endpoint( const fc::ip::endpoint& ep ) const;\n\n         /**\n          * Enable or disable listening for incoming connections\n          * @param accept set to true to listen for incoming connections, false otherwise\n          */\n         void set_accept_incoming_connections( bool accept ) const;\n\n         /***\n          * Enable or disable connection attempts when new connections are advertised\n          * @param connect true to attempt new connections, false otherwise\n          */\n         void set_connect_to_new_peers( bool connect ) const;\n\n        /**\n         * Returns the endpoint the node is listening on.  This is usually the same\n         * as the value previously passed in to set_listen_endpoint, unless we\n         * were unable to bind to that port.\n         */\n        virtual fc::ip::endpoint get_actual_listening_endpoint() const;\n\n        /***\n         * Allows the caller to determine how to respond to requests for peers\n         * @param algo the algorithm to use (\"exclude_list\", \"list\", \"nothing\", \"all\")\n         * @param advertise_or_exclude_list a list of nodes to\n         *     advertise (if algo is \"list\") or exclude (if algo is \"exclude_list\")\n         */\n        void set_advertise_algorithm( const std::string& algo,\n            const std::vector<std::string>& advertise_or_exclude_list = std::vector<std::string>() ) const;\n\n        /**\n         *  @return a list of peers that are currently connected.\n         */\n        std::vector<peer_status> get_connected_peers() const;\n\n        /** return the number of peers we're actively connected to */\n        virtual uint32_t get_connection_count() const;\n\n        /**\n         *  Add message to outgoing inventory list, notify peers that\n         *  I have a message ready.\n         */\n        virtual void  broadcast( const message& item_to_broadcast ) const;\n        virtual void  broadcast_transaction( const signed_transaction& trx ) const\n        {\n           broadcast( trx_message(trx) );\n        }\n\n        /**\n         *  Node starts the process of fetching all items after item_id of the\n         *  given item_type.   During this process messages are not broadcast.\n         */\n        virtual void sync_from( const item_id& current_head_block,\n                                const std::vector<uint32_t>& hard_fork_block_numbers ) const;\n\n        bool      is_connected() const;\n\n        void set_advanced_node_parameters(const fc::variant_object& params) const;\n        fc::variant_object get_advanced_node_parameters() const;\n        message_propagation_data get_tx_propagation_data(\n              const graphene::protocol::transaction_id_type& transaction_id) const;\n        message_propagation_data get_block_propagation_data(\n              const graphene::protocol::block_id_type& block_id) const;\n        node_id_t get_node_id() const;\n        void set_allowed_peers(const std::vector<node_id_t>& allowed_peers) const;\n\n        /**\n         * Instructs the node to forget everything in its peer database, mostly for debugging\n         * problems where nodes are failing to connect to the network\n         */\n        void clear_peer_database() const;\n\n        void set_total_bandwidth_limit(uint32_t upload_bytes_per_second, uint32_t download_bytes_per_second) const;\n\n        fc::variant_object network_get_info() const;\n        fc::variant_object network_get_usage_stats() const;\n\n        std::vector<potential_peer_record> get_potential_peers() const;\n\n        fc::variant_object get_call_statistics() const;\n      protected:\n        node_impl_ptr my;\n   };\n\n   using node_ptr = std::shared_ptr<node>;\n\n} } // graphene::net\n\nFC_REFLECT(graphene::net::message_propagation_data, (received_time)(validated_time)(originating_peer));\nFC_REFLECT( graphene::net::peer_status, (version)(host)(info) );\n"
  },
  {
    "path": "libraries/net/include/graphene/net/peer_connection.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/net/node.hpp>\n#include <graphene/net/peer_database.hpp>\n#include <graphene/net/message_oriented_connection.hpp>\n#include <graphene/net/config.hpp>\n\n#include <boost/tuple/tuple.hpp>\n\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/tag.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n\n#include <queue>\n#include <boost/container/deque.hpp>\n#include <fc/thread/future.hpp>\n\nnamespace graphene { namespace net\n  {\n    class peer_connection;\n    class peer_connection_delegate\n    {\n    public:\n      virtual ~peer_connection_delegate() = default;\n      virtual void on_message(peer_connection* originating_peer,\n                              const message& received_message) = 0;\n      virtual void on_connection_closed(peer_connection* originating_peer) = 0;\n      virtual message get_message_for_item(const item_id& item) = 0;\n    };\n\n    using peer_connection_ptr = std::shared_ptr<peer_connection>;\n    class peer_connection : public message_oriented_connection_delegate,\n                            public std::enable_shared_from_this<peer_connection>\n    {\n    public:\n      enum class our_connection_state\n      {\n        disconnected,\n        just_connected, ///< If in this state, we have sent a hello_message\n        connection_accepted, ///< Remote side has sent us a connection_accepted, we're operating normally with them\n        /// Remote side has sent us a connection_rejected, we may be exchanging address with them or may just\n        /// be waiting for them to close\n        connection_rejected\n      };\n      enum class their_connection_state\n      {\n        disconnected,\n        just_connected, ///< We have not yet received a hello_message\n        connection_accepted, ///< We have sent them a connection_accepted\n        connection_rejected ///< We have sent them a connection_rejected\n      };\n      enum class connection_negotiation_status\n      {\n        disconnected,\n        connecting,\n        connected,\n        accepting,\n        accepted,\n        hello_sent,\n        peer_connection_accepted,\n        peer_connection_rejected,\n        negotiation_complete,\n        closing,\n        closed\n      };\n    private:\n      peer_connection_delegate*      _node;\n      fc::optional<fc::ip::endpoint> _remote_endpoint;\n      message_oriented_connection    _message_connection;\n\n      /* a base class for messages on the queue, to hide the fact that some\n       * messages are complete messages and some are only hashes of messages.\n       */\n      struct queued_message\n      {\n        fc::time_point enqueue_time;\n        fc::time_point transmission_start_time;\n        fc::time_point transmission_finish_time;\n\n        explicit queued_message(fc::time_point enqueue_time = fc::time_point::now()) :\n          enqueue_time(enqueue_time)\n        {}\n\n        virtual message get_message(peer_connection_delegate* node) = 0;\n        /** returns roughly the number of bytes of memory the message is consuming while\n         * it is sitting on the queue\n         */\n        virtual size_t get_size_in_queue() = 0;\n        virtual ~queued_message() = default;\n      };\n\n      /* when you queue up a 'real_queued_message', a full copy of the message is\n       * stored on the heap until it is sent\n       */\n      struct real_queued_message : queued_message\n      {\n        message        message_to_send;\n        size_t         message_send_time_field_offset;\n\n        real_queued_message(message message_to_send,\n                            size_t message_send_time_field_offset = (size_t)-1) :\n          message_to_send(std::move(message_to_send)),\n          message_send_time_field_offset(message_send_time_field_offset)\n        {}\n\n        message get_message(peer_connection_delegate* node) override;\n        size_t get_size_in_queue() override;\n      };\n\n      /* when you queue up a 'virtual_queued_message', we just queue up the hash of the\n       * item we want to send.  When it reaches the top of the queue, we make a callback\n       * to the node to generate the message.\n       */\n      struct virtual_queued_message : queued_message\n      {\n        item_id item_to_send;\n\n        explicit virtual_queued_message(item_id the_item_to_send) :\n          item_to_send(std::move(the_item_to_send))\n        {}\n\n        message get_message(peer_connection_delegate* node) override;\n        size_t get_size_in_queue() override;\n      };\n\n\n      size_t _total_queued_messages_size = 0;\n      std::queue<std::unique_ptr<queued_message>, std::list<std::unique_ptr<queued_message> > > _queued_messages;\n      fc::future<void> _send_queued_messages_done;\n    public:\n      fc::time_point connection_initiation_time;\n      fc::time_point connection_closed_time;\n      fc::time_point connection_terminated_time;\n      peer_connection_direction direction = peer_connection_direction::unknown;\n      firewalled_state is_firewalled = firewalled_state::unknown;\n      fc::microseconds clock_offset;\n      fc::microseconds round_trip_delay;\n\n      our_connection_state our_state = our_connection_state::disconnected;\n      bool they_have_requested_close = false;\n      their_connection_state their_state = their_connection_state::disconnected;\n      bool we_have_requested_close = false;\n\n      connection_negotiation_status negotiation_status = connection_negotiation_status::disconnected;\n      fc::oexception connection_closed_error;\n\n      fc::time_point get_connection_time()const { return _message_connection.get_connection_time(); }\n      fc::time_point get_connection_terminated_time()const { return connection_terminated_time; }\n\n      /// data about the peer node\n      /// @{\n      /** node_public_key from the hello message, zero-initialized before we get the hello */\n      node_id_t        node_public_key;\n      /** the unique identifier we'll use to refer to the node with.  zero-initialized before\n       * we receive the hello message, at which time it will be filled with either the \"node_id\"\n       * from the user_data field of the hello, or if none is present it will be filled with a\n       * copy of node_public_key */\n      node_id_t        node_id;\n      uint32_t         core_protocol_version = 0;\n      std::string      user_agent;\n      fc::optional<std::string> graphene_git_revision_sha;\n      fc::optional<fc::time_point_sec> graphene_git_revision_unix_timestamp;\n      fc::optional<std::string> fc_git_revision_sha;\n      fc::optional<fc::time_point_sec> fc_git_revision_unix_timestamp;\n      fc::optional<std::string> platform;\n      fc::optional<uint32_t> bitness;\n\n      // Initially, these fields record info about our local socket,\n      // they are useless (except the remote_inbound_endpoint field for outbound connections).\n      // After we receive a hello message, they are replaced with the info in the hello message.\n      fc::ip::address inbound_address;\n      uint16_t inbound_port = 0;\n      uint16_t outbound_port = 0;\n      /// The inbound endpoint of the remote peer (our best guess)\n      fc::optional<fc::ip::endpoint> remote_inbound_endpoint;\n      /// Some nodes may be listening on multiple endpoints\n      fc::flat_set<fc::ip::endpoint> additional_inbound_endpoints;\n      /// Potential inbound endpoints of the peer\n      fc::flat_map<fc::ip::endpoint, firewalled_state> potential_inbound_endpoints;\n      /// @}\n\n      using item_to_time_map_type = std::unordered_map<item_id, fc::time_point>;\n\n      /// Blockchain synchronization state data\n      /// @{\n      /// ID of items in the blockchain that this peer has told us about\n      boost::container::deque<item_hash_t> ids_of_items_to_get;\n      /// List of all items this peer has offered use that we've already handed to the client but the client\n      /// hasn't finished processing\n      std::set<item_hash_t> ids_of_items_being_processed;\n      /// Number of items in the blockchain that follow ids_of_items_to_get but the peer hasn't yet told us their IDs\n      uint32_t number_of_unfetched_item_ids = 0;\n      bool peer_needs_sync_items_from_us = false;\n      bool we_need_sync_items_from_peer = false;\n      /// We check this to detect a timed-out request and in busy()\n      fc::optional<boost::tuple<std::vector<item_hash_t>, fc::time_point> > item_ids_requested_from_peer;\n      /// The time we received the last sync item or the time we sent the last batch of sync item requests\n      /// to this peer\n      fc::time_point last_sync_item_received_time;\n      /// IDs of blocks we've requested from this peer during sync. Fetch from another peer if this peer disconnects\n      std::set<item_hash_t> sync_items_requested_from_peer;\n      /// The hash of the last block  this peer has told us about that the peer knows\n      item_hash_t last_block_delegate_has_seen;\n      fc::time_point_sec last_block_time_delegate_has_seen;\n      bool inhibit_fetching_sync_blocks = false;\n      /// @}\n\n      /// non-synchronization state data\n      /// @{\n      struct timestamped_item_id\n      {\n        item_id            item;\n        fc::time_point_sec timestamp;\n        timestamped_item_id(const item_id& item, const fc::time_point_sec timestamp) :\n          item(item),\n          timestamp(timestamp)\n        {}\n      };\n      struct timestamp_index{};\n      using timestamped_items_set_type = boost::multi_index_container< timestamped_item_id,\n         boost::multi_index::indexed_by<\n            boost::multi_index::hashed_unique<\n               boost::multi_index::member<timestamped_item_id, item_id, &timestamped_item_id::item>,\n               std::hash<item_id>\n            >,\n            boost::multi_index::ordered_non_unique<\n               boost::multi_index::tag<timestamp_index>,\n               boost::multi_index::member<timestamped_item_id, fc::time_point_sec, &timestamped_item_id::timestamp>\n            >\n         >\n      >;\n      timestamped_items_set_type inventory_peer_advertised_to_us;\n      timestamped_items_set_type inventory_advertised_to_peer;\n\n      /// Items we've requested from this peer during normal operation.\n      /// Fetch from another peer if this peer disconnects\n      item_to_time_map_type items_requested_from_peer;\n      /// @}\n\n      // if they're flooding us with transactions, we set this to avoid fetching for a few seconds to let the\n      // blockchain catch up\n      fc::time_point transaction_fetching_inhibited_until;\n\n      uint32_t last_known_fork_block_number = 0;\n\n      fc::future<void> accept_or_connect_task_done;\n\n      /// Whether we're waiting for an address message\n      bool expecting_address_message = false;\n\n    private:\n#ifndef NDEBUG\n      fc::thread* _thread = nullptr;\n      unsigned _send_message_queue_tasks_running = 0; // temporary debugging\n#endif\n      /// true while we're in the middle of handling a message from the remote system\n      bool _currently_handling_message = false;\n    protected:\n      peer_connection(peer_connection_delegate* delegate);\n    private:\n      void destroy();\n    public:\n      /// Use this instead of the constructor\n      static peer_connection_ptr make_shared(peer_connection_delegate* delegate);\n      virtual ~peer_connection();\n\n      fc::tcp_socket& get_socket();\n      void accept_connection();\n      void connect_to(const fc::ip::endpoint& remote_endpoint,\n                      const fc::optional<fc::ip::endpoint>& local_endpoint = fc::optional<fc::ip::endpoint>());\n\n      void on_message(message_oriented_connection* originating_connection, const message& received_message) override;\n      void on_connection_closed(message_oriented_connection* originating_connection) override;\n\n      void send_queueable_message(std::unique_ptr<queued_message>&& message_to_send);\n      virtual void send_message( const message& message_to_send, size_t message_send_time_field_offset = (size_t)-1 );\n      void send_item(const item_id& item_to_send);\n      void close_connection();\n      void destroy_connection();\n\n      uint64_t get_total_bytes_sent() const;\n      uint64_t get_total_bytes_received() const;\n\n      fc::time_point get_last_message_sent_time() const;\n      fc::time_point get_last_message_received_time() const;\n\n      fc::optional<fc::ip::endpoint> get_remote_endpoint();\n      fc::ip::endpoint get_local_endpoint();\n      void set_remote_endpoint(fc::optional<fc::ip::endpoint> new_remote_endpoint);\n\n      bool busy() const;\n      bool idle() const;\n      bool is_currently_handling_message() const;\n\n      bool is_transaction_fetching_inhibited() const;\n      fc::sha512 get_shared_secret() const;\n      void clear_old_inventory();\n      bool is_inventory_advertised_to_us_list_full_for_transactions() const;\n      bool is_inventory_advertised_to_us_list_full() const;\n      fc::optional<fc::ip::endpoint> get_endpoint_for_connecting() const;\n    private:\n      void send_queued_messages_task();\n      void accept_connection_task();\n      void connect_to_task(const fc::ip::endpoint& remote_endpoint);\n    };\n    typedef std::shared_ptr<peer_connection> peer_connection_ptr;\n\n } } // end namespace graphene::net\n\n// not sent over the wire, just reflected for logging\nFC_REFLECT_ENUM(graphene::net::peer_connection::our_connection_state, (disconnected)\n                                                                 (just_connected)\n                                                                 (connection_accepted)\n                                                                 (connection_rejected))\nFC_REFLECT_ENUM(graphene::net::peer_connection::their_connection_state, (disconnected)\n                                                                   (just_connected)\n                                                                   (connection_accepted)\n                                                                   (connection_rejected))\nFC_REFLECT_ENUM(graphene::net::peer_connection::connection_negotiation_status, (disconnected)\n                                                                          (connecting)\n                                                                          (connected)\n                                                                          (accepting)\n                                                                          (accepted)\n                                                                          (hello_sent)\n                                                                          (peer_connection_accepted)\n                                                                          (peer_connection_rejected)\n                                                                          (negotiation_complete)\n                                                                          (closing)\n                                                                          (closed) )\n\nFC_REFLECT( graphene::net::peer_connection::timestamped_item_id, (item)(timestamp) )\n"
  },
  {
    "path": "libraries/net/include/graphene/net/peer_database.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <boost/iterator/iterator_facade.hpp>\n\n#include <graphene/protocol/types.hpp>\n\n#include <fc/network/ip.hpp>\n#include <fc/time.hpp>\n#include <fc/io/enum_type.hpp>\n#include <fc/reflect/reflect.hpp>\n#include <fc/reflect/variant.hpp>\n#include <fc/exception/exception.hpp>\n\nnamespace graphene { namespace net {\n\n  enum potential_peer_last_connection_disposition\n  {\n    never_attempted_to_connect,\n    last_connection_failed,\n    last_connection_rejected,\n    last_connection_handshaking_failed,\n    last_connection_succeeded\n  };\n\n  struct potential_peer_record\n  {\n    fc::ip::endpoint                  endpoint;\n    fc::time_point_sec                last_seen_time;\n    fc::enum_type<uint8_t,potential_peer_last_connection_disposition> last_connection_disposition;\n    fc::time_point_sec                last_connection_attempt_time;\n    uint32_t                          number_of_successful_connection_attempts = 0;\n    uint32_t                          number_of_failed_connection_attempts = 0;\n    fc::optional<fc::exception>       last_error;\n\n    potential_peer_record() = default;\n\n    explicit potential_peer_record(\n       const fc::ip::endpoint& endpoint,\n       const fc::time_point_sec& last_seen_time = fc::time_point_sec(),\n       potential_peer_last_connection_disposition last_connection_disposition = never_attempted_to_connect )\n    : endpoint(endpoint),\n      last_seen_time(last_seen_time),\n      last_connection_disposition(last_connection_disposition)\n    {}\n  };\n\n  namespace detail\n  {\n    class peer_database_impl;\n\n    class peer_database_iterator_impl;\n    class peer_database_iterator : public boost::iterator_facade< peer_database_iterator,\n                                                                  const potential_peer_record,\n                                                                  boost::forward_traversal_tag>\n    {\n    public:\n      peer_database_iterator();\n      ~peer_database_iterator();\n      explicit peer_database_iterator( std::unique_ptr<peer_database_iterator_impl>&& impl );\n      peer_database_iterator( const peer_database_iterator& c );\n\n    private:\n      friend class boost::iterator_core_access;\n      void increment();\n      bool equal(const peer_database_iterator& other) const;\n      const potential_peer_record& dereference() const;\n    private:\n      std::unique_ptr<peer_database_iterator_impl> my;\n    };\n  }\n\n\n  class peer_database\n  {\n  public:\n    peer_database();\n    virtual ~peer_database();\n\n    void open(const fc::path& databaseFilename);\n    void close();\n    void clear();\n\n    void erase(const fc::ip::endpoint& endpointToErase);\n\n    void update_entry(const potential_peer_record& updatedRecord);\n    potential_peer_record lookup_or_create_entry_for_ep(const fc::ip::endpoint& endpointToLookup)const;\n    fc::optional<potential_peer_record> lookup_entry_for_endpoint(const fc::ip::endpoint& endpointToLookup)const;\n\n    using iterator = detail::peer_database_iterator;\n    iterator begin() const;\n    iterator end() const;\n    size_t size() const;\n  private:\n    std::unique_ptr<detail::peer_database_impl> my;\n  };\n\n} } // end namespace graphene::net\n\nFC_REFLECT_TYPENAME( graphene::net::potential_peer_record )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::net::potential_peer_record)\n"
  },
  {
    "path": "libraries/net/include/graphene/net/stcp_socket.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <fc/network/tcp_socket.hpp>\n#include <fc/crypto/aes.hpp>\n#include <fc/crypto/elliptic.hpp>\n\nnamespace graphene { namespace net {\n\n/**\n *  Uses ECDH to negotiate a aes key for communicating\n *  with other nodes on the network.\n */\nclass stcp_socket : public virtual fc::iostream\n{\n  public:\n    stcp_socket();\n    ~stcp_socket();\n    fc::tcp_socket&  get_socket() { return _sock; }\n    void             accept();\n\n    void             connect_to( const fc::ip::endpoint& remote_endpoint );\n    void             bind( const fc::ip::endpoint& local_endpoint );\n\n    virtual size_t   readsome( char* buffer, size_t max );\n    virtual size_t   readsome( const std::shared_ptr<char>& buf, size_t len, size_t offset );\n    virtual bool     eof()const;\n\n    virtual size_t   writesome( const char* buffer, size_t len );\n    virtual size_t   writesome( const std::shared_ptr<const char>& buf, size_t len, size_t offset );\n\n    virtual void     flush();\n    virtual void     close();\n\n    using istream::get;\n    void             get( char& c ) { read( &c, 1 ); }\n    fc::sha512       get_shared_secret() const { return _shared_secret; }\n  private:\n    void do_key_exchange();\n\n    fc::sha512           _shared_secret;\n    fc::ecc::private_key _priv_key;\n    fc::tcp_socket       _sock;\n    fc::aes_encoder      _send_aes;\n    fc::aes_decoder      _recv_aes;\n    std::shared_ptr<char> _read_buffer;\n    std::shared_ptr<char> _write_buffer;\n#ifndef NDEBUG\n    bool _read_buffer_in_use;\n    bool _write_buffer_in_use;\n#endif\n};\n\ntypedef std::shared_ptr<stcp_socket> stcp_socket_ptr;\n\n} } // graphene::net\n"
  },
  {
    "path": "libraries/net/message.cpp",
    "content": "/*\n * Copyright (c) 2019 BitShares Blockchain Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <fc/io/raw.hpp>\n\n#include <graphene/net/message.hpp>\n\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::message_header, BOOST_PP_SEQ_NIL, (size)(msg_type) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::message, (graphene::net::message_header), (data) )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::message_header)\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::message)\n"
  },
  {
    "path": "libraries/net/message_oriented_connection.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <fc/thread/thread.hpp>\n#include <fc/thread/mutex.hpp>\n#include <fc/thread/scoped_lock.hpp>\n#include <fc/thread/future.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/io/enum_type.hpp>\n\n#include <graphene/net/message_oriented_connection.hpp>\n#include <graphene/net/stcp_socket.hpp>\n#include <graphene/net/config.hpp>\n\n#include <atomic>\n\n#ifdef DEFAULT_LOGGER\n# undef DEFAULT_LOGGER\n#endif\n#define DEFAULT_LOGGER \"p2p\"\n\n#ifndef NDEBUG\n# define VERIFY_CORRECT_THREAD() assert(_thread->is_current())\n#else\n# define VERIFY_CORRECT_THREAD() do {} while (0)\n#endif\n\nnamespace graphene { namespace net {\n  namespace detail\n  {\n    class message_oriented_connection_impl\n    {\n    private:\n      message_oriented_connection* _self;\n      message_oriented_connection_delegate *_delegate;\n      stcp_socket _sock;\n      fc::promise<void>::ptr _ready_for_sending;\n      fc::future<void> _read_loop_done;\n      uint64_t _bytes_received;\n      uint64_t _bytes_sent;\n\n      fc::time_point _connected_time;\n      fc::time_point _last_message_received_time;\n      fc::time_point _last_message_sent_time;\n\n      std::atomic_bool _send_message_in_progress;\n      std::atomic_bool _read_loop_in_progress;\n#ifndef NDEBUG\n      fc::thread* _thread;\n#endif\n\n      void read_loop();\n      void start_read_loop();\n    public:\n      fc::tcp_socket& get_socket();\n      void accept();\n      void connect_to(const fc::ip::endpoint& remote_endpoint);\n      void bind(const fc::ip::endpoint& local_endpoint);\n\n      message_oriented_connection_impl(message_oriented_connection* self,\n                                       message_oriented_connection_delegate* delegate = nullptr);\n      ~message_oriented_connection_impl();\n\n      void send_message(const message& message_to_send);\n      void close_connection();\n      void destroy_connection();\n\n      uint64_t get_total_bytes_sent() const;\n      uint64_t get_total_bytes_received() const;\n\n      fc::time_point get_last_message_sent_time() const;\n      fc::time_point get_last_message_received_time() const;\n      fc::time_point get_connection_time() const { return _connected_time; }\n      fc::sha512 get_shared_secret() const;\n    };\n\n    message_oriented_connection_impl::message_oriented_connection_impl(message_oriented_connection* self,\n                                                                       message_oriented_connection_delegate* delegate)\n    : _self(self),\n      _delegate(delegate),\n      _ready_for_sending(fc::promise<void>::create()),\n      _bytes_received(0),\n      _bytes_sent(0),\n      _send_message_in_progress(false),\n      _read_loop_in_progress(false)\n#ifndef NDEBUG\n      ,_thread(&fc::thread::current())\n#endif\n    {\n    }\n    message_oriented_connection_impl::~message_oriented_connection_impl()\n    {\n      VERIFY_CORRECT_THREAD();\n      destroy_connection();\n    }\n\n    fc::tcp_socket& message_oriented_connection_impl::get_socket()\n    {\n      VERIFY_CORRECT_THREAD();\n      return _sock.get_socket();\n    }\n\n    void message_oriented_connection_impl::accept()\n    {\n      VERIFY_CORRECT_THREAD();\n      _sock.accept();\n      assert(!_read_loop_done.valid()); // check to be sure we never launch two read loops\n      _read_loop_done = fc::async([=](){ read_loop(); }, \"message read_loop\");\n      _ready_for_sending->set_value();\n    }\n\n    void message_oriented_connection_impl::connect_to(const fc::ip::endpoint& remote_endpoint)\n    {\n      VERIFY_CORRECT_THREAD();\n      _sock.connect_to(remote_endpoint);\n      assert(!_read_loop_done.valid()); // check to be sure we never launch two read loops\n      _read_loop_done = fc::async([=](){ read_loop(); }, \"message read_loop\");\n      _ready_for_sending->set_value();\n    }\n\n    void message_oriented_connection_impl::bind(const fc::ip::endpoint& local_endpoint)\n    {\n      VERIFY_CORRECT_THREAD();\n      _sock.bind(local_endpoint);\n    }\n\n    class no_parallel_execution_guard final\n    {\n      std::atomic_bool* _flag;\n    public:\n      explicit no_parallel_execution_guard(std::atomic_bool* flag) : _flag(flag)\n      {\n         bool expected = false;\n         FC_ASSERT( flag->compare_exchange_strong( expected, true ), \"Only one thread at time can visit it\");\n      }\n      ~no_parallel_execution_guard()\n      {\n         *_flag = false;\n      }\n    };\n\n    void message_oriented_connection_impl::read_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      const int BUFFER_SIZE = 16;\n      const int LEFTOVER = BUFFER_SIZE - sizeof(message_header);\n      static_assert(BUFFER_SIZE >= sizeof(message_header), \"insufficient buffer\");\n\n      no_parallel_execution_guard guard( &_read_loop_in_progress );\n\n      _connected_time = fc::time_point::now();\n\n      fc::oexception exception_to_rethrow;\n      bool call_on_connection_closed = false;\n      bool io_error = false;\n\n      try\n      {\n        message m;\n        char buffer[BUFFER_SIZE];\n        while( true )\n        {\n          try {\n            _sock.read(buffer, BUFFER_SIZE);\n          } catch ( const fc::canceled_exception& ) {\n            io_error = true;\n            throw;\n          }\n          _bytes_received += BUFFER_SIZE;\n          memcpy((char*)&m, buffer, sizeof(message_header));\n          FC_ASSERT( m.size.value() <= MAX_MESSAGE_SIZE, \"\", (\"m.size\",m.size.value())(\"MAX_MESSAGE_SIZE\",MAX_MESSAGE_SIZE) );\n\n          size_t remaining_bytes_with_padding = 16 * ((m.size.value() - LEFTOVER + 15) / 16);\n          m.data.resize(LEFTOVER + remaining_bytes_with_padding); //give extra 16 bytes to allow for padding added in send call\n          std::copy(buffer + sizeof(message_header), buffer + sizeof(buffer), m.data.begin());\n          if (remaining_bytes_with_padding)\n          {\n            try {\n              _sock.read(&m.data[LEFTOVER], remaining_bytes_with_padding);\n            } catch ( const fc::canceled_exception& ) {\n              io_error = true;\n              throw;\n            }\n            _bytes_received += remaining_bytes_with_padding;\n          }\n          m.data.resize(m.size.value()); // truncate off the padding bytes\n\n          _last_message_received_time = fc::time_point::now();\n\n          try\n          {\n            // message handling errors are warnings...\n            _delegate->on_message(_self, m);\n          }\n          /// Dedicated catches needed to distinguish from general fc::exception\n          catch ( const fc::canceled_exception& e ) { throw; }\n          catch ( const fc::eof_exception& e ) { throw; }\n          catch ( const fc::exception& e)\n          {\n            /// Here loop should be continued so exception should be just caught locally.\n            wlog( \"message transmission failed ${er}\", (\"er\", e.to_detail_string() ) );\n            throw;\n          }\n        }\n      }\n      catch ( const fc::canceled_exception& e )\n      {\n        if( io_error )\n        {\n          wlog( \"disconnected on io error ${e}\", (\"e\", e.to_detail_string() ) );\n          call_on_connection_closed = true;\n          exception_to_rethrow = fc::unhandled_exception(FC_LOG_MESSAGE(warn, \"disconnected on io error: ${e}\",\n                                                         (\"e\", e.to_detail_string())));\n        }\n        else\n        {\n          wlog( \"caught a canceled_exception in read_loop.  this should mean we're in the process of deleting \"\n                \"this object already, so there's no need to notify the delegate: ${e}\",\n                (\"e\", e.to_detail_string() ) );\n          throw;\n        }\n      }\n      catch ( const fc::eof_exception& e )\n      {\n        wlog( \"disconnected ${e}\", (\"e\", e.to_detail_string() ) );\n        call_on_connection_closed = true;\n      }\n      catch ( const fc::exception& e )\n      {\n        elog( \"disconnected ${er}\", (\"er\", e.to_detail_string() ) );\n        call_on_connection_closed = true;\n        exception_to_rethrow = fc::unhandled_exception(FC_LOG_MESSAGE(warn, \"disconnected: ${e}\", (\"e\", e.to_detail_string())));\n      }\n      catch ( const std::exception& e )\n      {\n        elog( \"disconnected ${er}\", (\"er\", e.what() ) );\n        call_on_connection_closed = true;\n        exception_to_rethrow = fc::unhandled_exception(FC_LOG_MESSAGE(warn, \"disconnected: ${e}\", (\"e\", e.what())));\n      }\n      catch ( ... )\n      {\n        elog( \"unexpected exception\" );\n        call_on_connection_closed = true;\n        exception_to_rethrow = fc::unhandled_exception(FC_LOG_MESSAGE(warn, \"disconnected: ${e}\", (\"e\", fc::except_str())));\n      }\n\n      if (call_on_connection_closed)\n        _delegate->on_connection_closed(_self);\n\n      if (exception_to_rethrow)\n        throw *exception_to_rethrow;\n    }\n\n    void message_oriented_connection_impl::send_message(const message& message_to_send)\n    {\n      VERIFY_CORRECT_THREAD();\n#if 0 // this gets too verbose\n#ifndef NDEBUG\n      fc::optional<fc::ip::endpoint> remote_endpoint;\n      if (_sock.get_socket().is_open())\n        remote_endpoint = _sock.get_socket().remote_endpoint();\n      struct scope_logger {\n        const fc::optional<fc::ip::endpoint>& endpoint;\n        scope_logger(const fc::optional<fc::ip::endpoint>& endpoint) : endpoint(endpoint) { dlog(\"entering message_oriented_connection::send_message() for peer ${endpoint}\", (\"endpoint\", endpoint)); }\n        ~scope_logger() { dlog(\"leaving message_oriented_connection::send_message() for peer ${endpoint}\", (\"endpoint\", endpoint)); }\n      } send_message_scope_logger(remote_endpoint);\n#endif\n#endif\n      no_parallel_execution_guard guard( &_send_message_in_progress );\n      _ready_for_sending->wait();\n\n      try\n      {\n        size_t size_of_message_and_header = sizeof(message_header) + message_to_send.size.value();\n        if( message_to_send.size.value() > MAX_MESSAGE_SIZE )\n           elog(\"Trying to send a message larger than MAX_MESSAGE_SIZE. This probably won't work...\");\n        //pad the message we send to a multiple of 16 bytes\n        size_t size_with_padding = 16 * ((size_of_message_and_header + 15) / 16);\n        std::vector<char> padded_message( size_with_padding );\n\n        memcpy( padded_message.data(), (const char*)&message_to_send, sizeof(message_header) );\n        memcpy( padded_message.data() + sizeof(message_header), message_to_send.data.data(),\n                message_to_send.size.value() );\n        char* padding_space = padded_message.data() + sizeof(message_header) + message_to_send.size.value();\n        memset(padding_space, 0, size_with_padding - size_of_message_and_header);\n        _sock.write( padded_message.data(), size_with_padding );\n        _sock.flush();\n        _bytes_sent += size_with_padding;\n        _last_message_sent_time = fc::time_point::now();\n      } FC_RETHROW_EXCEPTIONS( warn, \"unable to send message\" )\n    }\n\n    void message_oriented_connection_impl::close_connection()\n    {\n      VERIFY_CORRECT_THREAD();\n      _sock.close();\n    }\n\n    void message_oriented_connection_impl::destroy_connection()\n    {\n      VERIFY_CORRECT_THREAD();\n\n      fc::optional<fc::ip::endpoint> remote_endpoint;\n      if (_sock.get_socket().is_open())\n        remote_endpoint = _sock.get_socket().remote_endpoint();\n      ilog( \"in destroy_connection() for ${endpoint}\", (\"endpoint\", remote_endpoint) );\n\n      if (_send_message_in_progress)\n        elog(\"Error: message_oriented_connection is being destroyed while a send_message is in progress.  \"\n             \"The task calling send_message() should have been canceled already\");\n      assert(!_send_message_in_progress);\n\n      try\n      {\n        _read_loop_done.cancel_and_wait(__FUNCTION__);\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while canceling message_oriented_connection's read_loop, ignoring: ${e}\", (\"e\",e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while canceling message_oriented_connection's read_loop, ignoring\" );\n      }\n      _ready_for_sending->set_exception( std::make_shared<fc::canceled_exception>() );\n    }\n\n    uint64_t message_oriented_connection_impl::get_total_bytes_sent() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _bytes_sent;\n    }\n\n    uint64_t message_oriented_connection_impl::get_total_bytes_received() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _bytes_received;\n    }\n\n    fc::time_point message_oriented_connection_impl::get_last_message_sent_time() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _last_message_sent_time;\n    }\n\n    fc::time_point message_oriented_connection_impl::get_last_message_received_time() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _last_message_received_time;\n    }\n\n    fc::sha512 message_oriented_connection_impl::get_shared_secret() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _sock.get_shared_secret();\n    }\n\n  } // end namespace graphene::net::detail\n\n\n  message_oriented_connection::message_oriented_connection(message_oriented_connection_delegate* delegate) :\n    my( std::make_unique<detail::message_oriented_connection_impl>(this, delegate) )\n  {\n  }\n\n  message_oriented_connection::~message_oriented_connection()\n  {\n  }\n\n  fc::tcp_socket& message_oriented_connection::get_socket()\n  {\n    return my->get_socket();\n  }\n\n  void message_oriented_connection::accept()\n  {\n    my->accept();\n  }\n\n  void message_oriented_connection::connect_to(const fc::ip::endpoint& remote_endpoint)\n  {\n    my->connect_to(remote_endpoint);\n  }\n\n  void message_oriented_connection::bind(const fc::ip::endpoint& local_endpoint)\n  {\n    my->bind(local_endpoint);\n  }\n\n  void message_oriented_connection::send_message(const message& message_to_send)\n  {\n    my->send_message(message_to_send);\n  }\n\n  void message_oriented_connection::close_connection()\n  {\n    my->close_connection();\n  }\n\n  void message_oriented_connection::destroy_connection()\n  {\n    my->destroy_connection();\n  }\n\n  uint64_t message_oriented_connection::get_total_bytes_sent() const\n  {\n    return my->get_total_bytes_sent();\n  }\n\n  uint64_t message_oriented_connection::get_total_bytes_received() const\n  {\n    return my->get_total_bytes_received();\n  }\n\n  fc::time_point message_oriented_connection::get_last_message_sent_time() const\n  {\n    return my->get_last_message_sent_time();\n  }\n\n  fc::time_point message_oriented_connection::get_last_message_received_time() const\n  {\n    return my->get_last_message_received_time();\n  }\n  fc::time_point message_oriented_connection::get_connection_time() const\n  {\n    return my->get_connection_time();\n  }\n  fc::sha512 message_oriented_connection::get_shared_secret() const\n  {\n    return my->get_shared_secret();\n  }\n\n} } // end namespace graphene::net\n"
  },
  {
    "path": "libraries/net/node.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <sstream>\n#include <iomanip>\n#include <deque>\n#include <unordered_set>\n#include <list>\n#include <forward_list>\n#include <iostream>\n#include <algorithm>\n#include <tuple>\n#include <string>\n#include <boost/tuple/tuple.hpp>\n#include <boost/circular_buffer.hpp>\n\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/random_access_index.hpp>\n#include <boost/multi_index/tag.hpp>\n#include <boost/multi_index/sequenced_index.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n#include <boost/logic/tribool.hpp>\n#include <boost/range/algorithm_ext/push_back.hpp>\n#include <boost/range/algorithm/find.hpp>\n#include <boost/range/numeric.hpp>\n\n#include <boost/preprocessor/seq/for_each.hpp>\n#include <boost/preprocessor/cat.hpp>\n#include <boost/preprocessor/stringize.hpp>\n\n#include <fc/thread/thread.hpp>\n#include <fc/thread/future.hpp>\n#include <fc/thread/non_preemptable_scope_check.hpp>\n#include <fc/thread/mutex.hpp>\n#include <fc/thread/scoped_lock.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/io/json.hpp>\n#include <fc/io/enum_type.hpp>\n#include <fc/io/raw.hpp>\n#include <fc/crypto/rand.hpp>\n#include <fc/network/ip.hpp>\n#include <fc/network/resolve.hpp>\n\n#include <graphene/net/node.hpp>\n#include <graphene/net/peer_database.hpp>\n#include <graphene/net/peer_connection.hpp>\n#include <graphene/net/stcp_socket.hpp>\n#include <graphene/net/config.hpp>\n#include <graphene/net/exceptions.hpp>\n\n#include <graphene/chain/config.hpp>\n#include <graphene/chain/exceptions.hpp>\n// Nasty hack: A circular dependency around fee_schedule is resolved by fwd-declaring it and using a shared_ptr\n// to it in chain_parameters, which is used in an operation and thus must be serialized by the net library.\n// Resolving that forward declaration doesn't happen until now:\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/git_revision.hpp>\n\n#include \"node_impl.hxx\"\n\nnamespace graphene { namespace net { namespace detail {\n\n   void blockchain_tied_message_cache::block_accepted()\n   {\n      ++block_clock;\n      if( block_clock > cache_duration_in_blocks )\n         _message_cache.get<block_clock_index>().erase(_message_cache.get<block_clock_index>().begin(),\n               _message_cache.get<block_clock_index>().lower_bound(block_clock - cache_duration_in_blocks ) );\n   }\n\n   void blockchain_tied_message_cache::cache_message( const message& message_to_cache,\n                                                      const message_hash_type& hash_of_message_to_cache,\n                                                      const message_propagation_data& propagation_data,\n                                                      const message_hash_type& message_content_hash )\n   {\n      _message_cache.insert( message_info(hash_of_message_to_cache,\n                                         message_to_cache,\n                                         block_clock,\n                                         propagation_data,\n                                         message_content_hash ) );\n   }\n\n   message blockchain_tied_message_cache::get_message( const message_hash_type& hash_of_message_to_lookup ) const\n   {\n      message_cache_container::index<message_hash_index>::type::const_iterator iter =\n         _message_cache.get<message_hash_index>().find(hash_of_message_to_lookup );\n      if( iter != _message_cache.get<message_hash_index>().end() )\n         return iter->message_body;\n      FC_THROW_EXCEPTION(  fc::key_not_found_exception, \"Requested message not in cache\" );\n   }\n\n    message_propagation_data blockchain_tied_message_cache::get_message_propagation_data(\n             const message_hash_type& hash_of_msg_contents_to_lookup ) const\n    {\n      if( hash_of_msg_contents_to_lookup != message_hash_type() )\n      {\n        message_cache_container::index<message_contents_hash_index>::type::const_iterator iter =\n           _message_cache.get<message_contents_hash_index>().find(hash_of_msg_contents_to_lookup );\n        if( iter != _message_cache.get<message_contents_hash_index>().end() )\n          return iter->propagation_data;\n      }\n      FC_THROW_EXCEPTION(  fc::key_not_found_exception, \"Requested message not in cache\" );\n    }\n\n    void node_impl_deleter::operator()(node_impl* impl_to_delete)\n    {\n#ifdef P2P_IN_DEDICATED_THREAD\n      std::weak_ptr<fc::thread> weak_thread;\n      if (impl_to_delete)\n      {\n        std::shared_ptr<fc::thread> impl_thread(impl_to_delete->_thread);\n        weak_thread = impl_thread;\n        impl_thread->async([impl_to_delete](){ delete impl_to_delete; }, \"delete node_impl\").wait();\n        dlog(\"deleting the p2p thread\");\n      }\n      if (weak_thread.expired())\n        dlog(\"done deleting the p2p thread\");\n      else\n        dlog(\"failed to delete the p2p thread, we must be leaking a smart pointer somewhere\");\n#else // P2P_IN_DEDICATED_THREAD\n      delete impl_to_delete;\n#endif // P2P_IN_DEDICATED_THREAD\n    }\n\n#ifdef P2P_IN_DEDICATED_THREAD\n# define VERIFY_CORRECT_THREAD() assert(_thread->is_current())\n#else\n# define VERIFY_CORRECT_THREAD() do {} while (0)\n#endif\n\n   /// Greatly delays the next connection to the endpoint\n   static void greatly_delay_next_conn_to( node_impl* impl, const fc::ip::endpoint& ep )\n   {\n      dlog( \"Greatly delaying the next connection to endpoint ${ep}\", (\"ep\", ep) );\n      fc::optional<potential_peer_record> updated_peer_record\n            = impl->_potential_peer_db.lookup_entry_for_endpoint( ep );\n      if( updated_peer_record )\n      {\n         updated_peer_record->last_connection_disposition = last_connection_rejected;\n         updated_peer_record->last_connection_attempt_time = fc::time_point::now();\n         constexpr uint32_t failed_attempts_to_add = 120; // * 30 seconds = 1 hour\n         updated_peer_record->number_of_failed_connection_attempts += failed_attempts_to_add;\n         impl->_potential_peer_db.update_entry( *updated_peer_record );\n      }\n   }\n   /// Saves a successfully connected endpoint to the peer database\n   static void save_successful_address( node_impl* impl, const fc::ip::endpoint& ep )\n   {\n      dlog( \"Saving successfully connected endpoint ${ep} to peer database\", (\"ep\", ep) );\n      auto updated_peer_record = impl->_potential_peer_db.lookup_or_create_entry_for_ep( ep );\n      updated_peer_record.last_connection_disposition = last_connection_succeeded;\n      updated_peer_record.last_connection_attempt_time = fc::time_point::now();\n      // halve number_of_failed_connection_attempts\n      constexpr uint16_t two = 2;\n      updated_peer_record.number_of_failed_connection_attempts /= two;\n      updated_peer_record.last_seen_time = fc::time_point::now();\n      impl->_potential_peer_db.update_entry(updated_peer_record);\n   }\n   static void update_address_seen_time( node_impl* impl, const peer_connection* active_peer )\n   {\n      fc::optional<fc::ip::endpoint> inbound_endpoint = active_peer->get_endpoint_for_connecting();\n      if( inbound_endpoint.valid() && inbound_endpoint->port() != 0 )\n      {\n         fc::optional<potential_peer_record> updated_peer_record\n               = impl->_potential_peer_db.lookup_entry_for_endpoint( *inbound_endpoint );\n         if( updated_peer_record )\n         {\n            updated_peer_record->last_seen_time = fc::time_point::now();\n            impl->_potential_peer_db.update_entry( *updated_peer_record );\n         }\n      }\n   }\n   static void update_address_seen_time( node_impl* impl, const peer_connection_ptr& active_peer )\n   {\n      update_address_seen_time( impl, active_peer.get() );\n   }\n\n   /// Base class for list address builder and exclude_list address builder\n   class generic_list_address_builder : public node_impl::address_builder\n   {\n   public:\n      fc::flat_set<fc::ip::endpoint> list;\n\n      explicit generic_list_address_builder(const std::vector<std::string>& address_list)\n      {\n         FC_ASSERT( !address_list.empty(), \"The peer node list must not be empty\" );\n\n         std::for_each( address_list.begin(), address_list.end(), [&list = list]( const std::string& str )\n            {\n               // ignore fc exceptions (like poorly formatted endpoints)\n               try\n               {\n                  list.insert( fc::ip::endpoint::from_string(str) );\n               }\n               catch(const fc::exception& )\n               {\n                  wlog( \"Address ${addr} invalid.\", (\"addr\", str) );\n               }\n            } );\n      }\n   };\n\n   /******\n    * Use information passed from command line or config file to advertise nodes\n    */\n   class list_address_builder : public generic_list_address_builder\n   {\n   public:\n      using generic_list_address_builder::generic_list_address_builder;\n\n      bool should_advertise( const fc::ip::endpoint& in ) const override\n      {\n         return !( list.find(in) == list.end() );\n      }\n   };\n\n   /****\n    * Advertise all nodes except a predefined list\n    */\n   class exclude_address_builder : public generic_list_address_builder\n   {\n   public:\n      using generic_list_address_builder::generic_list_address_builder;\n\n      bool should_advertise( const fc::ip::endpoint& in ) const override\n      {\n         return ( list.find( in ) == list.end() );\n      }\n   };\n\n   /***\n    * Return all peers when node asks\n    */\n   class all_address_builder : public node_impl::address_builder\n   {\n      bool should_advertise( const fc::ip::endpoint& in ) const override\n      {\n         return true;\n      }\n   };\n\n   std::shared_ptr<node_impl::address_builder> node_impl::address_builder::create_default_address_builder()\n   {\n      return std::make_shared<all_address_builder>();\n   }\n\n   void node_impl::address_builder::build(node_impl* impl, address_message& reply) const\n   {\n      reply.addresses.clear();\n      reply.addresses.reserve( impl->_active_connections.size() );\n      fc::scoped_lock<fc::mutex> lock(impl->_active_connections.get_mutex());\n      fc::time_point_sec now = fc::time_point::now();\n      // only pass those that are allowed to advertise AND we are connected to\n      for( const peer_connection_ptr& active_peer : impl->_active_connections )\n      {\n         // Note:\n         // * We want to advertise the peer's inbound endpoint, but not necessarily the remote endpoint.\n         // * If the peer's inbound port is 0, we still advertise it so that observers know about it.\n         //   The peer is marked as \"firewalled\", so peers running older versions should be able to handle it too.\n         //\n         // If it is an outbound connection, we know that the remote endpoint works (at least for us),\n         //   and we have assigned it to the inbound endpoint, so just use either of them.\n         // If it is an inbound connection, we just advertise what we have.\n         fc::optional<fc::ip::endpoint> inbound_endpoint = active_peer->get_endpoint_for_connecting();\n         if( inbound_endpoint.valid() && should_advertise( *inbound_endpoint ) )\n         {\n            update_address_seen_time( impl, active_peer );\n            reply.addresses.emplace_back( *inbound_endpoint,\n                                          now,\n                                          active_peer->round_trip_delay,\n                                          active_peer->node_id,\n                                          active_peer->direction,\n                                          active_peer->is_firewalled );\n         }\n      }\n   }\n\n   node_impl::node_impl(const std::string& user_agent) :\n      _user_agent_string(user_agent)\n   {\n      _rate_limiter.set_actual_rate_time_constant(fc::seconds(2));\n      // Note: this means that the node gets a new node_id every time it restarts\n      fc::rand_bytes((char*) _node_id.data(), (int)_node_id.size());\n   }\n\n    node_impl::~node_impl()\n    {\n      VERIFY_CORRECT_THREAD();\n      ilog( \"cleaning up node\" );\n      _node_is_shutting_down = true;\n\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& active_peer : _active_connections)\n         {\n            update_address_seen_time( this, active_peer );\n         }\n      }\n\n      try\n      {\n        ilog( \"close\" );\n        close();\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"unexpected exception on close ${e}\", (\"e\", e) );\n      }\n      ilog( \"done\" );\n    }\n\n   void node_impl::save_node_configuration()\n   {\n      VERIFY_CORRECT_THREAD();\n\n      fc::path configuration_file_name( _node_configuration_directory / NODE_CONFIGURATION_FILENAME );\n      try\n      {\n         if( !fc::exists(_node_configuration_directory ) )\n            fc::create_directories( _node_configuration_directory );\n         fc::json::save_to_file( _node_configuration, configuration_file_name );\n         dlog( \"Saved node configuration to file ${filename}\", ( \"filename\", configuration_file_name ) );\n      }\n      catch (const fc::canceled_exception&)\n      {\n         throw;\n      }\n      catch ( const fc::exception& except )\n      {\n         wlog( \"error writing node configuration to file ${filename}: ${error}\",\n               ( \"filename\", configuration_file_name )(\"error\", except.to_detail_string() ) );\n      }\n   }\n\n    void node_impl::p2p_network_connect_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      while (!_p2p_network_connect_loop_done.canceled())\n      {\n        try\n        {\n          dlog(\"Starting an iteration of p2p_network_connect_loop().\");\n          display_current_connections();\n\n          // add-once peers bypass our checks on the maximum/desired number of connections\n          // (but they will still be counted against the totals once they're connected)\n          if (!_add_once_node_list.empty())\n          {\n            std::list<potential_peer_record> add_once_node_list;\n            add_once_node_list.swap(_add_once_node_list);\n            dlog(\"Processing \\\"add once\\\" node list containing ${count} peers:\",\n                 (\"count\", add_once_node_list.size()));\n            for (const potential_peer_record& add_once_peer : add_once_node_list)\n            {\n              dlog(\"    ${peer}\", (\"peer\", add_once_peer.endpoint));\n            }\n            for (const potential_peer_record& add_once_peer : add_once_node_list)\n            {\n              // If we have an existing connection to that peer, skip it.\n              peer_connection_ptr existing_connection_ptr = get_connection_for_endpoint( add_once_peer.endpoint );\n              if(!existing_connection_ptr)\n                connect_to_endpoint(add_once_peer.endpoint);\n            }\n            dlog(\"Done processing \\\"add once\\\" node list\");\n          }\n\n          while (is_wanting_new_connections())\n          {\n            bool initiated_connection_this_pass = false;\n            _potential_peer_db_updated = false;\n\n            for (peer_database::iterator iter = _potential_peer_db.begin();\n                 iter != _potential_peer_db.end() && is_wanting_new_connections();\n                 ++iter)\n            {\n              fc::microseconds delay_until_retry = fc::seconds( (iter->number_of_failed_connection_attempts + 1)\n                                                                * _peer_connection_retry_timeout );\n\n              bool last_connection_not_ok = ( iter->last_connection_disposition == last_connection_failed ||\n                         iter->last_connection_disposition == last_connection_rejected ||\n                         iter->last_connection_disposition == last_connection_handshaking_failed );\n\n              if( !is_connected_to_endpoint( iter->endpoint )\n                  && ( !last_connection_not_ok\n                       || ( fc::time_point::now() - iter->last_connection_attempt_time ) > delay_until_retry ) )\n              {\n                connect_to_endpoint(iter->endpoint);\n                initiated_connection_this_pass = true;\n              }\n            }\n\n            if (!initiated_connection_this_pass && !_potential_peer_db_updated)\n              break;\n          }\n\n          display_current_connections();\n\n          // if we broke out of the while loop, that means either we have connected to enough nodes, or\n          // we don't have any good candidates to connect to right now.\n#if 0\n          try\n          {\n            _retrigger_connect_loop_promise = fc::promise<void>::create(\"graphene::net::retrigger_connect_loop\");\n            if( is_wanting_new_connections() || !_add_once_node_list.empty() )\n            {\n              if( is_wanting_new_connections() )\n                dlog( \"Still want to connect to more nodes, but I don't have any good candidates.  Trying again in 15 seconds\" );\n              else\n                dlog( \"I still have some \\\"add once\\\" nodes to connect to.  Trying again in 15 seconds\" );\n              _retrigger_connect_loop_promise->wait_until( fc::time_point::now() + fc::seconds(GRAPHENE_PEER_DATABASE_RETRY_DELAY ) );\n            }\n            else\n            {\n              dlog( \"I don't need any more connections, waiting forever until something changes\" );\n              _retrigger_connect_loop_promise->wait();\n            }\n          }\n          catch ( fc::timeout_exception& ) //intentionally not logged\n          {\n          }  // catch\n#else\n          fc::usleep(fc::seconds(10));\n#endif\n        }\n        catch (const fc::canceled_exception&)\n        {\n          ilog( \"p2p_network_connect_loop canceled\" );\n          throw;\n        }\n        FC_CAPTURE_AND_LOG( (0) ) // GCOVR_EXCL_LINE\n      }// while !canceled\n    }\n\n    void node_impl::trigger_p2p_network_connect_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      dlog( \"Triggering connect loop now\" );\n      _potential_peer_db_updated = true;\n      //if( _retrigger_connect_loop_promise )\n      //  _retrigger_connect_loop_promise->set_value();\n    }\n\n   void node_impl::update_seed_nodes_task()\n   {\n      VERIFY_CORRECT_THREAD();\n\n      try\n      {\n         ilog(\"Starting an iteration of update_seed_nodes loop.\");\n         for( const std::string& endpoint_string : _seed_nodes )\n         {\n            resolve_seed_node_and_add( endpoint_string );\n         }\n         ilog(\"Done an iteration of update_seed_nodes loop.\");\n      }\n      catch (const fc::canceled_exception&)\n      {\n         ilog( \"update_seed_nodes_task canceled\" );\n         throw;\n      }\n      FC_CAPTURE_AND_LOG( (_seed_nodes) ) // GCOVR_EXCL_LINE\n\n      schedule_next_update_seed_nodes_task();\n   }\n\n   void node_impl::schedule_next_update_seed_nodes_task()\n   {\n      VERIFY_CORRECT_THREAD();\n\n      if( _node_is_shutting_down )\n         return;\n\n      if( _update_seed_nodes_loop_done.valid() && _update_seed_nodes_loop_done.canceled() )\n         return;\n\n      constexpr uint32_t five = 5;\n      auto interval = _active_connections.empty() ? fc::minutes(five) : fc::hours(1);\n      _update_seed_nodes_loop_done = fc::schedule( [this]() { update_seed_nodes_task(); },\n                                                   fc::time_point::now() + interval,\n                                                   \"update_seed_nodes_loop\" );\n   }\n\n    bool node_impl::have_already_received_sync_item( const item_hash_t& item_hash )\n    {\n      VERIFY_CORRECT_THREAD();\n      return std::find_if(_received_sync_items.begin(), _received_sync_items.end(),\n                          [&item_hash]( const graphene::net::block_message& message ) { return message.block_id == item_hash; } ) != _received_sync_items.end() ||\n             std::find_if(_new_received_sync_items.begin(), _new_received_sync_items.end(),\n                          [&item_hash]( const graphene::net::block_message& message ) { return message.block_id == item_hash; } ) != _new_received_sync_items.end();                          ;\n    }\n\n    void node_impl::request_sync_item_from_peer( const peer_connection_ptr& peer, const item_hash_t& item_to_request )\n    {\n      VERIFY_CORRECT_THREAD();\n      dlog( \"requesting item ${item_hash} from peer ${endpoint}\", (\"item_hash\", item_to_request )(\"endpoint\", peer->get_remote_endpoint() ) );\n      item_id item_id_to_request( graphene::net::block_message_type, item_to_request );\n      _active_sync_requests.insert( active_sync_requests_map::value_type(item_to_request, fc::time_point::now() ) );\n      peer->last_sync_item_received_time = fc::time_point::now();\n      peer->sync_items_requested_from_peer.insert(item_to_request);\n      peer->send_message( fetch_items_message(item_id_to_request.item_type, std::vector<item_hash_t>{item_id_to_request.item_hash} ) );\n    }\n\n    void node_impl::request_sync_items_from_peer( const peer_connection_ptr& peer, const std::vector<item_hash_t>& items_to_request )\n    {\n      VERIFY_CORRECT_THREAD();\n      dlog( \"requesting ${item_count} item(s) ${items_to_request} from peer ${endpoint}\",\n            (\"item_count\", items_to_request.size())(\"items_to_request\", items_to_request)(\"endpoint\", peer->get_remote_endpoint()) );\n      for (const item_hash_t& item_to_request : items_to_request)\n      {\n        _active_sync_requests.insert( active_sync_requests_map::value_type(item_to_request, fc::time_point::now() ) );\n        peer->last_sync_item_received_time = fc::time_point::now();\n        peer->sync_items_requested_from_peer.insert(item_to_request);\n      }\n      peer->send_message(fetch_items_message(graphene::net::block_message_type, items_to_request));\n    }\n\n    void node_impl::fetch_sync_items_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      while( !_fetch_sync_items_loop_done.canceled() )\n      {\n        _sync_items_to_fetch_updated = false;\n        dlog( \"beginning another iteration of the sync items loop\" );\n\n        if (!_suspend_fetching_sync_blocks)\n        {\n          std::map<peer_connection_ptr, std::vector<item_hash_t> > sync_item_requests_to_send;\n\n          {\n            std::set<item_hash_t> sync_items_to_request;\n\n            // for each idle peer that we're syncing with\n            fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n            for( const peer_connection_ptr& peer : _active_connections )\n            {\n              if( peer->we_need_sync_items_from_peer &&\n                  // if we've already scheduled a request for this peer, don't consider scheduling another\n                  sync_item_requests_to_send.find(peer) == sync_item_requests_to_send.end() &&\n                  peer->idle() )\n              {\n                if (!peer->inhibit_fetching_sync_blocks)\n                {\n                  // loop through the items it has that we don't yet have on our blockchain\n                  for( const auto& item_to_potentially_request : peer->ids_of_items_to_get )\n                  {\n                    // if we don't already have this item in our temporary storage\n                    // and we haven't requested from another syncing peer\n                    if( // already got it, but for some reson it's still in our list of items to fetch\n                        !have_already_received_sync_item(item_to_potentially_request) &&\n                        // we have already decided to request it from another peer during this iteration\n                        sync_items_to_request.find(item_to_potentially_request) == sync_items_to_request.end() &&\n                        // we've requested it in a previous iteration and we're still waiting for it to arrive\n                        _active_sync_requests.find(item_to_potentially_request) == _active_sync_requests.end() )\n                    {\n                      // then schedule a request from this peer\n                      sync_item_requests_to_send[peer].push_back(item_to_potentially_request);\n                      sync_items_to_request.insert( item_to_potentially_request );\n                      if (sync_item_requests_to_send[peer].size() >= _max_sync_blocks_per_peer)\n                        break;\n                    }\n                  }\n                }\n              }\n            }\n          } // end non-preemptable section\n\n          // make all the requests we scheduled in the loop above\n          for( auto sync_item_request : sync_item_requests_to_send )\n            request_sync_items_from_peer( sync_item_request.first, sync_item_request.second );\n          sync_item_requests_to_send.clear();\n        }\n        else\n          dlog(\"fetch_sync_items_loop is suspended pending backlog processing\");\n\n        if( !_sync_items_to_fetch_updated )\n        {\n          dlog( \"no sync items to fetch right now, going to sleep\" );\n          _retrigger_fetch_sync_items_loop_promise\n                = fc::promise<void>::create(\"graphene::net::retrigger_fetch_sync_items_loop\");\n          _retrigger_fetch_sync_items_loop_promise->wait();\n          _retrigger_fetch_sync_items_loop_promise.reset();\n        }\n      } // while( !canceled )\n    }\n\n    void node_impl::trigger_fetch_sync_items_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      dlog( \"Triggering fetch sync items loop now\" );\n      _sync_items_to_fetch_updated = true;\n      if( _retrigger_fetch_sync_items_loop_promise )\n        _retrigger_fetch_sync_items_loop_promise->set_value();\n    }\n\n    bool node_impl::is_item_in_any_peers_inventory(const item_id& item) const\n    {\n      fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n      for( const peer_connection_ptr& peer : _active_connections )\n      {\n        if (peer->inventory_peer_advertised_to_us.find(item) != peer->inventory_peer_advertised_to_us.end() )\n          return true;\n      }\n      return false;\n    }\n\n    void node_impl::fetch_items_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      while (!_fetch_item_loop_done.canceled())\n      {\n        _items_to_fetch_updated = false;\n        dlog(\"beginning an iteration of fetch items (${count} items to fetch)\",\n             (\"count\", _items_to_fetch.size()));\n\n        fc::time_point oldest_timestamp_to_fetch = fc::time_point::now()\n              - fc::seconds(_recent_block_interval_seconds * GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS);\n        fc::time_point next_peer_unblocked_time = fc::time_point::maximum();\n\n        // we need to construct a list of items to request from each peer first,\n        // then send the messages (in two steps, to avoid yielding while iterating)\n        // we want to evenly distribute our requests among our peers.\n        struct requested_item_count_index {};\n        struct peer_and_items_to_fetch\n        {\n          peer_connection_ptr peer;\n          std::vector<item_id> item_ids;\n          peer_and_items_to_fetch(const peer_connection_ptr& peer) : peer(peer) {}\n          bool operator<(const peer_and_items_to_fetch& rhs) const { return peer < rhs.peer; }\n          size_t number_of_items() const { return item_ids.size(); }\n        };\n        using fetch_messages_to_send_set = boost::multi_index_container< peer_and_items_to_fetch, bmi::indexed_by<\n                 bmi::ordered_unique<\n                    bmi::member<peer_and_items_to_fetch, peer_connection_ptr, &peer_and_items_to_fetch::peer> >,\n                 bmi::ordered_non_unique< bmi::tag<requested_item_count_index>,\n                    bmi::const_mem_fun<peer_and_items_to_fetch, size_t, &peer_and_items_to_fetch::number_of_items> >\n                 > >;\n        fetch_messages_to_send_set items_by_peer;\n\n        // initialize the fetch_messages_to_send with an empty set of items for all idle peers\n        {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& peer : _active_connections)\n            if (peer->idle())\n               items_by_peer.insert(peer_and_items_to_fetch(peer));\n        }\n\n        // now loop over all items we want to fetch\n        for (auto item_iter = _items_to_fetch.begin(); item_iter != _items_to_fetch.end();)\n        {\n          if (item_iter->timestamp < oldest_timestamp_to_fetch)\n          {\n            // this item has probably already fallen out of our peers' caches, we'll just ignore it.\n            // this can happen during flooding, and the _items_to_fetch could otherwise get clogged\n            // with a bunch of items that we'll never be able to request from any peer\n            wlog(\"Unable to fetch item ${item} before its likely expiration time, \"\n                 \"removing it from our list of items to fetch\",\n                 (\"item\", item_iter->item));\n            item_iter = _items_to_fetch.erase(item_iter);\n          }\n          else\n          {\n            // find a peer that has it, we'll use the one who has the least requests going to it to load balance\n            bool item_fetched = false;\n            for (auto peer_iter = items_by_peer.get<requested_item_count_index>().begin(); peer_iter != items_by_peer.get<requested_item_count_index>().end(); ++peer_iter)\n            {\n              const peer_connection_ptr& peer = peer_iter->peer;\n              // if they have the item and we haven't already decided to ask them for too many other items\n              if (peer_iter->item_ids.size() < GRAPHENE_NET_MAX_ITEMS_PER_PEER_DURING_NORMAL_OPERATION &&\n                  peer->inventory_peer_advertised_to_us.find(item_iter->item) != peer->inventory_peer_advertised_to_us.end())\n              {\n                if (item_iter->item.item_type == graphene::net::trx_message_type && peer->is_transaction_fetching_inhibited())\n                  next_peer_unblocked_time = std::min(peer->transaction_fetching_inhibited_until, next_peer_unblocked_time);\n                else\n                {\n                  //dlog(\"requesting item ${hash} from peer ${endpoint}\",\n                  //     (\"hash\", iter->item.item_hash)(\"endpoint\", peer->get_remote_endpoint()));\n                  item_id item_id_to_fetch = item_iter->item;\n                  peer->items_requested_from_peer.insert(peer_connection::item_to_time_map_type::value_type(\n                        item_id_to_fetch, fc::time_point::now()));\n                  item_iter = _items_to_fetch.erase(item_iter);\n                  item_fetched = true;\n                  items_by_peer.get<requested_item_count_index>().modify(peer_iter,\n                        [&item_id_to_fetch](peer_and_items_to_fetch& peer_and_items) {\n                           peer_and_items.item_ids.push_back(item_id_to_fetch);\n                  });\n                  break;\n                }\n              }\n            }\n            if (!item_fetched)\n              ++item_iter;\n          }\n        }\n\n        // we've figured out which peer will be providing each item, now send the messages.\n        for (const peer_and_items_to_fetch& peer_and_items : items_by_peer)\n        {\n          // the item lists are heterogenous and\n          // the fetch_items_message can only deal with one item type at a time.\n          std::map<uint32_t, std::vector<item_hash_t> > items_to_fetch_by_type;\n          for (const item_id& item : peer_and_items.item_ids)\n            items_to_fetch_by_type[item.item_type].push_back(item.item_hash);\n          for (auto& items_by_type : items_to_fetch_by_type)\n          {\n            dlog(\"requesting ${count} items of type ${type} from peer ${endpoint}: ${hashes}\",\n                 (\"count\", items_by_type.second.size())(\"type\", (uint32_t)items_by_type.first)\n                 (\"endpoint\", peer_and_items.peer->get_remote_endpoint())\n                 (\"hashes\", items_by_type.second));\n            peer_and_items.peer->send_message(fetch_items_message(items_by_type.first,\n                                                                  items_by_type.second));\n          }\n        }\n        items_by_peer.clear();\n\n        if (!_items_to_fetch_updated)\n        {\n          _retrigger_fetch_item_loop_promise = fc::promise<void>::create(\"graphene::net::retrigger_fetch_item_loop\");\n          fc::microseconds time_until_retrigger = fc::microseconds::maximum();\n          if (next_peer_unblocked_time != fc::time_point::maximum())\n            time_until_retrigger = next_peer_unblocked_time - fc::time_point::now();\n          try\n          {\n            if (time_until_retrigger > fc::microseconds(0))\n              _retrigger_fetch_item_loop_promise->wait(time_until_retrigger);\n          }\n          catch (const fc::timeout_exception&)\n          {\n            dlog(\"Resuming fetch_items_loop due to timeout -- one of our peers should no longer be throttled\");\n          }\n          _retrigger_fetch_item_loop_promise.reset();\n        }\n      } // while !canceled\n    }\n\n    void node_impl::trigger_fetch_items_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      _items_to_fetch_updated = true;\n      if( _retrigger_fetch_item_loop_promise )\n        _retrigger_fetch_item_loop_promise->set_value();\n    }\n\n    void node_impl::advertise_inventory_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      while (!_advertise_inventory_loop_done.canceled())\n      {\n        dlog(\"beginning an iteration of advertise inventory\");\n        // swap inventory into local variable, clearing the node's copy\n        std::unordered_set<item_id> inventory_to_advertise;\n        _new_inventory.swap( inventory_to_advertise );\n\n        // process all inventory to advertise and construct the inventory messages we'll send\n        // first, then send them all in a batch (to avoid any fiber interruption points while\n        // we're computing the messages)\n        std::list<std::pair<peer_connection_ptr, item_ids_inventory_message> > inventory_messages_to_send;\n        {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& peer : _active_connections)\n         {\n          // only advertise to peers who are in sync with us\n          //idump((peer->peer_needs_sync_items_from_us)); // for debug\n          if( !peer->peer_needs_sync_items_from_us )\n          {\n            std::map<uint32_t, std::vector<item_hash_t> > items_to_advertise_by_type;\n            // don't send the peer anything we've already advertised to it\n            // or anything it has advertised to us\n            // group the items we need to send by type, because we'll need to send one inventory message per type\n            size_t total_items_to_send = 0;\n            //idump((inventory_to_advertise)); // for debug\n            for (const item_id& item_to_advertise : inventory_to_advertise)\n            {\n               auto adv_to_peer = peer->inventory_advertised_to_peer.find(item_to_advertise);\n               auto adv_to_us   = peer->inventory_peer_advertised_to_us.find(item_to_advertise);\n\n              if (adv_to_peer == peer->inventory_advertised_to_peer.end() &&\n                  adv_to_us == peer->inventory_peer_advertised_to_us.end())\n              {\n                items_to_advertise_by_type[item_to_advertise.item_type].push_back(item_to_advertise.item_hash);\n                peer->inventory_advertised_to_peer.insert(\n                         peer_connection::timestamped_item_id(item_to_advertise, fc::time_point::now()));\n                ++total_items_to_send;\n                if (item_to_advertise.item_type == trx_message_type)\n                  testnetlog(\"advertising transaction ${id} to peer ${endpoint}\",\n                             (\"id\", item_to_advertise.item_hash)(\"endpoint\", peer->get_remote_endpoint()));\n                dlog(\"advertising item ${id} to peer ${endpoint}\",\n                     (\"id\", item_to_advertise.item_hash)(\"endpoint\", peer->get_remote_endpoint()));\n              }\n              else\n              {\n                 if( adv_to_peer != peer->inventory_advertised_to_peer.end() )\n                    dlog( \"adv_to_peer != peer->inventory_advertised_to_peer.end() : ${adv_to_peer}\",\n                          (\"adv_to_peer\", *adv_to_peer) );\n                 if( adv_to_us != peer->inventory_peer_advertised_to_us.end() )\n                    dlog( \"adv_to_us != peer->inventory_peer_advertised_to_us.end() : ${adv_to_us}\",\n                          (\"adv_to_us\", *adv_to_us) );\n              }\n            }\n              dlog(\"advertising ${count} new item(s) of ${types} type(s) to peer ${endpoint}\",\n                   (\"count\", total_items_to_send)\n                   (\"types\", items_to_advertise_by_type.size())\n                   (\"endpoint\", peer->get_remote_endpoint()));\n            for (auto items_group : items_to_advertise_by_type)\n            {\n               inventory_messages_to_send.emplace_back(std::make_pair(\n                     peer, item_ids_inventory_message(items_group.first, items_group.second)));\n            }\n          }\n          peer->clear_old_inventory();\n         }\n        } // lock_guard\n\n        for (auto iter = inventory_messages_to_send.begin(); iter != inventory_messages_to_send.end(); ++iter)\n          iter->first->send_message(iter->second);\n        inventory_messages_to_send.clear();\n\n        if (_new_inventory.empty())\n        {\n          _retrigger_advertise_inventory_loop_promise\n                = fc::promise<void>::create(\"graphene::net::retrigger_advertise_inventory_loop\");\n          _retrigger_advertise_inventory_loop_promise->wait();\n          _retrigger_advertise_inventory_loop_promise.reset();\n        }\n      } // while(!canceled)\n    }\n\n    void node_impl::trigger_advertise_inventory_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      if( _retrigger_advertise_inventory_loop_promise )\n        _retrigger_advertise_inventory_loop_promise->set_value();\n    }\n\n    void node_impl::kill_inactive_conns_loop(node_impl_ptr self)\n    {\n      VERIFY_CORRECT_THREAD();\n      std::list<peer_connection_ptr> peers_to_disconnect_gently;\n      std::list<peer_connection_ptr> peers_to_disconnect_forcibly;\n      std::list<peer_connection_ptr> peers_to_send_keep_alive;\n      std::list<peer_connection_ptr> peers_to_terminate;\n\n     try {\n      // Note: if the node is shutting down, it's possible that _delegate is already unusable,\n      //       in this case, we'll get an exception\n      _recent_block_interval_seconds = _delegate->get_current_block_interval_in_seconds();\n\n      // Disconnect peers that haven't sent us any data recently\n      // These numbers are just guesses and we need to think through how this works better.\n      // If we and our peers get disconnected from the rest of the network, we will not\n      // receive any blocks or transactions from the rest of the world, and that will\n      // probably make us disconnect from our peers even though we have working connections to\n      // them (but they won't have sent us anything since they aren't getting blocks either).\n      // This might not be so bad because it could make us initiate more connections and\n      // reconnect with the rest of the network, or it might just futher isolate us.\n      // As usual, the first step is to walk through all our peers and figure out which\n      // peers need action (disconneting, sending keepalives, etc), then we walk through\n      // those lists yielding at our leisure later.\n\n      uint32_t handshaking_timeout = _peer_inactivity_timeout;\n      fc::time_point handshaking_disconnect_threshold = fc::time_point::now() - fc::seconds(handshaking_timeout);\n      {\n         fc::scoped_lock<fc::mutex> lock(_handshaking_connections.get_mutex());\n         for( const peer_connection_ptr& handshaking_peer : _handshaking_connections )\n         {\n            if( handshaking_peer->connection_initiation_time < handshaking_disconnect_threshold &&\n                  handshaking_peer->get_last_message_received_time() < handshaking_disconnect_threshold &&\n               handshaking_peer->get_last_message_sent_time() < handshaking_disconnect_threshold )\n            {\n               wlog( \"Forcibly disconnecting from handshaking peer ${peer} due to inactivity of at least ${timeout} seconds\",\n                     ( \"peer\", handshaking_peer->get_remote_endpoint() )(\"timeout\", handshaking_timeout ) );\n               wlog(\"Peer's negotiating status: ${status}, bytes sent: ${sent}, bytes received: ${received}\",\n                     (\"status\", handshaking_peer->negotiation_status)\n                     (\"sent\", handshaking_peer->get_total_bytes_sent())\n                     (\"received\", handshaking_peer->get_total_bytes_received()));\n               handshaking_peer->connection_closed_error = fc::exception(FC_LOG_MESSAGE(warn, \n                     \"Terminating handshaking connection due to inactivity of ${timeout} seconds.  Negotiating status: ${status}, bytes sent: ${sent}, bytes received: ${received}\",\n                     (\"peer\", handshaking_peer->get_remote_endpoint())\n                     (\"timeout\", handshaking_timeout)\n                     (\"status\", handshaking_peer->negotiation_status)\n                     (\"sent\", handshaking_peer->get_total_bytes_sent())\n                     (\"received\", handshaking_peer->get_total_bytes_received())));\n               peers_to_disconnect_forcibly.push_back( handshaking_peer );\n            } // if\n         } // for\n      } // scoped_lock\n      // timeout for any active peers is two block intervals\n      uint32_t active_disconnect_timeout = 10 * _recent_block_interval_seconds;\n      uint32_t active_send_keepalive_timeout = active_disconnect_timeout / 2;\n\n      // set the ignored request time out to 6 second.  When we request a block\n      // or transaction from a peer, this timeout determines how long we wait for them\n      // to reply before we give up and ask another peer for the item.\n      // Ideally this should be significantly shorter than the block interval, because\n      // we'd like to realize the block isn't coming and fetch it from a different\n      // peer before the next block comes in.\n      // Increased to 6 from 1 in #1660 due to heavy load. May need to adjust further\n      // Note: #1660 is https://github.com/steemit/steem/issues/1660\n      fc::microseconds active_ignored_request_timeout = fc::seconds(6);\n\n      fc::time_point active_disconnect_threshold = fc::time_point::now() - fc::seconds(active_disconnect_timeout);\n      fc::time_point active_send_keepalive_threshold = fc::time_point::now() - fc::seconds(active_send_keepalive_timeout);\n      fc::time_point active_ignored_request_threshold = fc::time_point::now() - active_ignored_request_timeout;\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n\n         for( const peer_connection_ptr& active_peer : _active_connections )\n         {\n            if( active_peer->connection_initiation_time < active_disconnect_threshold &&\n                  active_peer->get_last_message_received_time() < active_disconnect_threshold )\n            {\n               wlog( \"Closing connection with peer ${peer} due to inactivity of at least ${timeout} seconds\",\n                     ( \"peer\", active_peer->get_remote_endpoint() )(\"timeout\", active_disconnect_timeout ) );\n               peers_to_disconnect_gently.push_back( active_peer );\n            }\n            else\n            {\n               bool disconnect_due_to_request_timeout = false;\n               if (!active_peer->sync_items_requested_from_peer.empty() &&\n                  active_peer->last_sync_item_received_time < active_ignored_request_threshold)\n               {\n                  wlog(\"Disconnecting peer ${peer} because they haven't made any progress on my remaining ${count} sync item requests\",\n                        (\"peer\", active_peer->get_remote_endpoint())(\"count\",\n                        active_peer->sync_items_requested_from_peer.size()));\n                  disconnect_due_to_request_timeout = true;\n               }\n               if (!disconnect_due_to_request_timeout &&\n                  active_peer->item_ids_requested_from_peer &&\n                  active_peer->item_ids_requested_from_peer->get<1>() < active_ignored_request_threshold)\n               {\n                  wlog(\"Disconnecting peer ${peer} because they didn't respond to my request for sync item ids after ${synopsis}\",\n                        (\"peer\", active_peer->get_remote_endpoint())\n                        (\"synopsis\", active_peer->item_ids_requested_from_peer->get<0>()));\n                  disconnect_due_to_request_timeout = true;\n               }\n               if (!disconnect_due_to_request_timeout)\n                  for (const peer_connection::item_to_time_map_type::value_type& item_and_time : active_peer->items_requested_from_peer)\n                     if (item_and_time.second < active_ignored_request_threshold)\n                  {\n                     wlog(\"Disconnecting peer ${peer} because they didn't respond to my request for item ${id}\",\n                           (\"peer\", active_peer->get_remote_endpoint())(\"id\", item_and_time.first.item_hash));\n                     disconnect_due_to_request_timeout = true;\n                     break;\n                  }\n               if (disconnect_due_to_request_timeout)\n               {\n                  // we should probably disconnect nicely and give them a reason, but right now the logic\n                  // for rescheduling the requests only executes when the connection is fully closed,\n                  // and we want to get those requests rescheduled as soon as possible\n                  peers_to_disconnect_forcibly.push_back(active_peer);\n               }\n               else if (active_peer->connection_initiation_time < active_send_keepalive_threshold &&\n                     active_peer->get_last_message_received_time() < active_send_keepalive_threshold)\n               {\n                  wlog( \"Sending a keepalive message to peer ${peer} who hasn't sent us any messages in the last ${timeout} seconds\",\n                        ( \"peer\", active_peer->get_remote_endpoint() )(\"timeout\", active_send_keepalive_timeout ) );\n                  peers_to_send_keep_alive.push_back(active_peer);\n               }\n               else if (active_peer->we_need_sync_items_from_peer && \n                     !active_peer->is_currently_handling_message() &&\n                     !active_peer->item_ids_requested_from_peer &&\n                     active_peer->ids_of_items_to_get.empty())\n               {\n                  // This is a state we should never get into in the first place, but if we do, we should disconnect the peer\n                  // to re-establish the connection.\n                  fc_wlog(fc::logger::get(\"sync\"), \"Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.\",\n                        (\"peer\", active_peer->get_remote_endpoint()));\n                  wlog(\"Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.\",\n                        (\"peer\", active_peer->get_remote_endpoint()));\n                  peers_to_disconnect_forcibly.push_back(active_peer);\n               }\n            } // else\n         } // for\n      } // scoped_lock\n\n      fc::time_point closing_disconnect_threshold = fc::time_point::now() - fc::seconds(GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT);\n      {\n         fc::scoped_lock<fc::mutex> lock(_closing_connections.get_mutex());\n         for( const peer_connection_ptr& closing_peer : _closing_connections )\n         {\n            if( closing_peer->connection_closed_time < closing_disconnect_threshold )\n            {\n               // we asked this peer to close their connectoin to us at least GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT\n               // seconds ago, but they haven't done it yet.  Terminate the connection now\n               wlog( \"Forcibly disconnecting peer ${peer} who failed to close their connection in a timely manner\",\n                     ( \"peer\", closing_peer->get_remote_endpoint() ) );\n               peers_to_disconnect_forcibly.push_back( closing_peer );\n            }\n         } // for\n      } // scoped_lock\n      uint32_t failed_terminate_timeout_seconds = 120;\n      fc::time_point failed_terminate_threshold = fc::time_point::now() - fc::seconds(failed_terminate_timeout_seconds);\n      {\n         fc::scoped_lock<fc::mutex> lock(_terminating_connections.get_mutex());\n         for (const peer_connection_ptr& peer : _terminating_connections )\n         {\n            if (peer->get_connection_terminated_time() != fc::time_point::min() &&\n               peer->get_connection_terminated_time() < failed_terminate_threshold)\n            {\n               wlog(\"Terminating connection with peer ${peer}, closing the connection didn't work\", (\"peer\", peer->get_remote_endpoint()));\n               peers_to_terminate.push_back(peer);\n            }\n         }\n      } // scoped_lock\n      // That's the end of the sorting step; now all peers that require further processing are now in one of the\n      // lists peers_to_disconnect_gently,  peers_to_disconnect_forcibly, peers_to_send_keep_alive, or peers_to_terminate\n\n      // if we've decided to delete any peers, do it now; in its current implementation this doesn't yield,\n      // and once we start yielding, we may find that we've moved that peer to another list (closed or active)\n      // and that triggers assertions, maybe even errors\n      {\n         fc::scoped_lock<fc::mutex> lock(_terminating_connections.get_mutex());\n         for (const peer_connection_ptr& peer : peers_to_terminate )\n         {\n            assert(_terminating_connections.find(peer) != _terminating_connections.end());\n            _terminating_connections.erase(peer);\n            schedule_peer_for_deletion(peer);\n         }\n      } // scoped_lock\n      peers_to_terminate.clear();\n\n      // if we're going to abruptly disconnect anyone, do it here\n      // (it doesn't yield).  I don't think there would be any harm if this were\n      // moved to the yielding section\n      for( const peer_connection_ptr& peer : peers_to_disconnect_forcibly )\n      {\n         move_peer_to_terminating_list(peer);\n         peer->close_connection();\n      }\n      peers_to_disconnect_forcibly.clear();\n\n      // Now process the peers that we need to do yielding functions with (disconnect sends a message with the\n      // disconnect reason, so it may yield)\n      for( const peer_connection_ptr& peer : peers_to_disconnect_gently )\n      {\n         {\n            fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n            fc::exception detailed_error( FC_LOG_MESSAGE(warn, \"Disconnecting due to inactivity\",\n                  ( \"last_message_received_seconds_ago\", (peer->get_last_message_received_time()\n                  - fc::time_point::now() ).count() / fc::seconds(1 ).count() )\n                  ( \"last_message_sent_seconds_ago\", (peer->get_last_message_sent_time()\n                  - fc::time_point::now() ).count() / fc::seconds(1 ).count() )\n                  ( \"inactivity_timeout\", _active_connections.find(peer ) != _active_connections.end()\n                  ? _peer_inactivity_timeout * 10 : _peer_inactivity_timeout ) ) );\n            disconnect_from_peer( peer.get(), \"Disconnecting due to inactivity\", false, detailed_error );\n         }\n      }\n      peers_to_disconnect_gently.clear();\n\n      for( const peer_connection_ptr& peer : peers_to_send_keep_alive )\n        peer->send_message(current_time_request_message(),\n                           offsetof(current_time_request_message, request_sent_time));\n      peers_to_send_keep_alive.clear();\n\n     } catch( const fc::exception& e ) {\n         wlog( \"Exception caught in kill_inactive_conns_loop: ${e}\", (\"e\",e.to_detail_string()) );\n         // If the node is shutting down, we just quit, no need to throw.\n         // If the node is not shutting down, the old code will throw, which means we won't schedule a new loop,\n         // likely it's unexpected behavior.\n         // Thus we don't throw here.\n     }\n\n      if (!_node_is_shutting_down && !_kill_inactive_conns_loop_done.canceled())\n      {\n         _kill_inactive_conns_loop_done = fc::schedule(\n               [this,self](){ kill_inactive_conns_loop(self); },\n               fc::time_point::now() + fc::seconds(GRAPHENE_NET_PEER_HANDSHAKE_INACTIVITY_TIMEOUT / 2),\n               \"kill_inactive_conns_loop\" );\n      }\n    }\n\n    void node_impl::fetch_updated_peer_lists_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         // JMJ 2018-10-22 Unsure why we're making a copy here, but this is probably unnecessary\n         std::list<peer_connection_ptr> original_active_peers(_active_connections.begin(), _active_connections.end());\n         for( const peer_connection_ptr& active_peer : original_active_peers )\n         {\n            try\n            {\n               active_peer->expecting_address_message = true;\n               active_peer->send_message(address_request_message());\n            }\n            catch ( const fc::canceled_exception& )\n            {\n               throw;\n            }\n            catch (const fc::exception& e)\n            {\n               dlog(\"Caught exception while sending address request message to peer ${peer} : ${e}\",\n                     (\"peer\", active_peer->get_remote_endpoint())(\"e\", e));\n            }\n         }\n      }\n\n      // this has nothing to do with updating the peer list, but we need to prune this list\n      // at regular intervals, this is a fine place to do it.\n      fc::time_point_sec oldest_failed_ids_to_keep(fc::time_point::now() - fc::minutes(15));\n      auto oldest_failed_ids_to_keep_iter = _recently_failed_items.get<peer_connection::timestamp_index>()\n                                                                  .lower_bound(oldest_failed_ids_to_keep);\n      auto begin_iter = _recently_failed_items.get<peer_connection::timestamp_index>().begin();\n      _recently_failed_items.get<peer_connection::timestamp_index>()\n                            .erase(begin_iter, oldest_failed_ids_to_keep_iter);\n\n      if (!_node_is_shutting_down && !_fetch_updated_peer_lists_loop_done.canceled() )\n         _fetch_updated_peer_lists_loop_done = fc::schedule( [this](){ fetch_updated_peer_lists_loop(); },\n                                                             fc::time_point::now() + fc::minutes(15),\n                                                             \"fetch_updated_peer_lists_loop\" );\n    }\n    void node_impl::update_bandwidth_data(uint32_t bytes_read_this_second, uint32_t bytes_written_this_second)\n    {\n      VERIFY_CORRECT_THREAD();\n      _avg_net_read_speed_seconds.push_back(bytes_read_this_second);\n      _avg_net_write_speed_seconds.push_back(bytes_written_this_second);\n      ++_avg_net_usage_second_counter;\n      constexpr uint8_t seconds_per_minute = 60;\n      constexpr uint8_t minutes_per_hour = 60;\n      if (_avg_net_usage_second_counter >= seconds_per_minute)\n      {\n        _avg_net_usage_second_counter = 0;\n        ++_avg_net_usage_minute_counter;\n        uint32_t average_read_this_minute = (uint32_t)boost::accumulate(_avg_net_read_speed_seconds, uint64_t(0))\n                                          / (uint32_t)_avg_net_read_speed_seconds.size();\n        _avg_net_read_speed_minutes.push_back(average_read_this_minute);\n        uint32_t average_written_this_minute = (uint32_t)boost::accumulate(_avg_net_write_speed_seconds, uint64_t(0))\n                                             / (uint32_t)_avg_net_write_speed_seconds.size();\n        _avg_net_write_speed_minutes.push_back(average_written_this_minute);\n        if (_avg_net_usage_minute_counter >= minutes_per_hour)\n        {\n          _avg_net_usage_minute_counter = 0;\n          uint32_t average_read_this_hour = (uint32_t)boost::accumulate(_avg_net_read_speed_minutes, uint64_t(0))\n                                          / (uint32_t)_avg_net_read_speed_minutes.size();\n          _avg_net_read_speed_hours.push_back(average_read_this_hour);\n          uint32_t average_written_this_hour = (uint32_t)boost::accumulate(_avg_net_write_speed_minutes, uint64_t(0))\n                                             / (uint32_t)_avg_net_write_speed_minutes.size();\n          _avg_net_write_speed_hours.push_back(average_written_this_hour);\n        }\n      }\n    }\n    void node_impl::bandwidth_monitor_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::time_point_sec current_time = fc::time_point::now();\n\n      if (_bandwidth_monitor_last_update_time == fc::time_point_sec::min())\n        _bandwidth_monitor_last_update_time = current_time;\n\n      uint32_t seconds_since_last_update = current_time.sec_since_epoch()\n                                         - _bandwidth_monitor_last_update_time.sec_since_epoch();\n      seconds_since_last_update = std::max(UINT32_C(1), seconds_since_last_update);\n      uint32_t bytes_read_this_second = _rate_limiter.get_actual_download_rate();\n      uint32_t bytes_written_this_second = _rate_limiter.get_actual_upload_rate();\n      for (uint32_t i = 0; i < seconds_since_last_update - 1; ++i)\n        update_bandwidth_data(0, 0);\n      update_bandwidth_data(bytes_read_this_second, bytes_written_this_second);\n      _bandwidth_monitor_last_update_time = current_time;\n\n      if (!_node_is_shutting_down && !_bandwidth_monitor_loop_done.canceled())\n        _bandwidth_monitor_loop_done = fc::schedule( [=](){ bandwidth_monitor_loop(); },\n                                                     fc::time_point::now() + fc::seconds(1),\n                                                     \"bandwidth_monitor_loop\" );\n    }\n\n    void node_impl::dump_node_status_task()\n    {\n      VERIFY_CORRECT_THREAD();\n      dump_node_status();\n      if (!_node_is_shutting_down && !_dump_node_status_task_done.canceled())\n        _dump_node_status_task_done = fc::schedule([=](){ dump_node_status_task(); },\n                                                   fc::time_point::now() + fc::minutes(1),\n                                                   \"dump_node_status_task\");\n    }\n\n    void node_impl::delayed_peer_deletion_task()\n    {\n      VERIFY_CORRECT_THREAD();\n#ifdef USE_PEERS_TO_DELETE_MUTEX\n      fc::scoped_lock<fc::mutex> lock(_peers_to_delete_mutex);\n      dlog(\"in delayed_peer_deletion_task with ${count} in queue\", (\"count\", _peers_to_delete.size()));\n      _peers_to_delete.clear();\n      dlog(\"_peers_to_delete cleared\");\n#else\n      while (!_peers_to_delete.empty())\n      {\n        std::list<peer_connection_ptr> peers_to_delete_copy;\n        dlog(\"beginning an iteration of delayed_peer_deletion_task with ${count} in queue\",\n             (\"count\", _peers_to_delete.size()));\n        peers_to_delete_copy.swap(_peers_to_delete);\n      }\n      dlog(\"leaving delayed_peer_deletion_task\");\n#endif\n    }\n\n    void node_impl::schedule_peer_for_deletion(const peer_connection_ptr& peer_to_delete)\n    {\n      VERIFY_CORRECT_THREAD();\n\n      assert(_handshaking_connections.find(peer_to_delete) == _handshaking_connections.end());\n      assert(_active_connections.find(peer_to_delete) == _active_connections.end());\n      assert(_closing_connections.find(peer_to_delete) == _closing_connections.end());\n      assert(_terminating_connections.find(peer_to_delete) == _terminating_connections.end());\n\n#ifdef USE_PEERS_TO_DELETE_MUTEX\n      dlog(\"scheduling peer for deletion: ${peer} (may block on a mutex here)\",\n           (\"peer\", peer_to_delete->get_remote_endpoint()));\n\n      size_t number_of_peers_to_delete;\n      {\n        fc::scoped_lock<fc::mutex> lock(_peers_to_delete_mutex);\n        _peers_to_delete.emplace_back(peer_to_delete);\n        number_of_peers_to_delete = _peers_to_delete.size();\n      }\n      dlog(\"peer scheduled for deletion: ${peer}\", (\"peer\", peer_to_delete->get_remote_endpoint()));\n\n      if (!_node_is_shutting_down &&\n          (!_delayed_peer_deletion_task_done.valid() || _delayed_peer_deletion_task_done.ready()))\n      {\n        dlog(\"asyncing delayed_peer_deletion_task to delete ${size} peers\",\n             (\"size\", number_of_peers_to_delete));\n        _delayed_peer_deletion_task_done = fc::async([this](){ delayed_peer_deletion_task(); },\n                                                     \"delayed_peer_deletion_task\" );\n    }\n      else\n        dlog(\"delayed_peer_deletion_task is already scheduled (current size of _peers_to_delete is ${size})\",\n             (\"size\", number_of_peers_to_delete));\n#else\n      dlog(\"scheduling peer for deletion: ${peer} (this will not block)\",\n           (\"peer\", peer_to_delete->get_remote_endpoint()));\n      _peers_to_delete.push_back(peer_to_delete);\n      if (!_node_is_shutting_down &&\n          (!_delayed_peer_deletion_task_done.valid() || _delayed_peer_deletion_task_done.ready()))\n      {\n        dlog(\"asyncing delayed_peer_deletion_task to delete ${size} peers\", (\"size\", _peers_to_delete.size()));\n        _delayed_peer_deletion_task_done = fc::async([this](){ delayed_peer_deletion_task(); },\n                                                     \"delayed_peer_deletion_task\" );\n      }\n      else\n        dlog(\"delayed_peer_deletion_task is already scheduled (current size of _peers_to_delete is ${size})\",\n             (\"size\", _peers_to_delete.size()));\n\n#endif\n    }\n\n    bool node_impl::is_accepting_new_connections()\n    {\n      VERIFY_CORRECT_THREAD();\n      return !_p2p_network_connect_loop_done.canceled()\n             && get_number_of_connections() <= _maximum_number_of_connections;\n    }\n\n    bool node_impl::is_wanting_new_connections()\n    {\n      VERIFY_CORRECT_THREAD();\n      return !_p2p_network_connect_loop_done.canceled()\n             && get_number_of_connections() < _desired_number_of_connections;\n    }\n\n    uint32_t node_impl::get_number_of_connections()\n    {\n      VERIFY_CORRECT_THREAD();\n      return (uint32_t)(_handshaking_connections.size() + _active_connections.size());\n    }\n\n    peer_connection_ptr node_impl::get_peer_by_node_id(const node_id_t& node_id) const\n    {\n      VERIFY_CORRECT_THREAD();\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& active_peer : _active_connections)\n            if (node_id == active_peer->node_id)\n               return active_peer;\n      }\n      {\n         fc::scoped_lock<fc::mutex> lock(_handshaking_connections.get_mutex());\n         for (const peer_connection_ptr& handshaking_peer : _handshaking_connections)\n            if (node_id == handshaking_peer->node_id)\n               return handshaking_peer;\n      }\n      return peer_connection_ptr();\n    }\n\n    // merge addresses received from a peer into our database\n    bool node_impl::merge_address_info_with_potential_peer_database(const std::vector<address_info> addresses)\n    {\n      VERIFY_CORRECT_THREAD();\n      bool new_information_received = false;\n      for (const address_info& address : addresses)\n      {\n         // If the peer's inbound port is 0, we don't add it to our peer database.\n         // Although it should have been handled by the caller, be defensive here.\n         if( 0 == address.remote_endpoint.port() )\n            continue;\n         // Note: if found, a copy is returned\n         auto updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_ep(address.remote_endpoint);\n         // Note:\n         // We don't save node_id in the peer database so far\n         // 1. node_id of that peer may have changed, but we don't check or update\n         // 2. we don't check by node_id either, in case when a peer's IP address has changed, we don't handle it\n         // 3. if the peer's inbound port is not 0, no matter if the address is reported as firewalled or not,\n         //    we add it to our database and check by ourselves later\n         if (address.last_seen_time > updated_peer_record.last_seen_time) // usually true, except when received from\n                                                                          // multiple peers in the same second\n         {\n            new_information_received = true;\n            updated_peer_record.last_seen_time = address.last_seen_time;\n            _potential_peer_db.update_entry(updated_peer_record);\n         }\n      }\n      // TODO maybe delete too old info by the way\n      return new_information_received;\n    }\n\n    void node_impl::display_current_connections()\n    {\n      VERIFY_CORRECT_THREAD();\n      dlog(\"Currently have ${current} of [${desired}/${max}] connections\",\n           (\"current\", get_number_of_connections())\n           (\"desired\", _desired_number_of_connections)\n           (\"max\", _maximum_number_of_connections));\n      dlog(\"   my id is ${id}\", (\"id\", _node_id));\n\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& active_connection : _active_connections)\n         {\n            dlog(\"        active: ${endpoint} with ${id}   [${direction}]\",\n                  (\"endpoint\", active_connection->get_remote_endpoint())\n                  (\"id\", active_connection->node_id)\n                  (\"direction\", active_connection->direction));\n         }\n      }\n      {\n         fc::scoped_lock<fc::mutex> lock(_handshaking_connections.get_mutex());\n         for (const peer_connection_ptr& handshaking_connection : _handshaking_connections)\n         {\n            dlog(\"   handshaking: ${endpoint} with ${id}  [${direction}]\",\n                  (\"endpoint\", handshaking_connection->get_remote_endpoint())\n                  (\"id\", handshaking_connection->node_id)\n                  (\"direction\", handshaking_connection->direction));\n         }\n      }\n    }\n\n    void node_impl::on_message( peer_connection* originating_peer, const message& received_message )\n    {\n      VERIFY_CORRECT_THREAD();\n      message_hash_type message_hash = received_message.id();\n      dlog(\"handling message ${type} ${hash} size ${size} from peer ${endpoint}\",\n           (\"type\", graphene::net::core_message_type_enum(received_message.msg_type.value()))(\"hash\", message_hash)\n           (\"size\", received_message.size)\n           (\"endpoint\", originating_peer->get_remote_endpoint()));\n      // Gatekeeping code\n      if( originating_peer->we_have_requested_close\n          // allow hello_message so we can learn more about the peer\n          && received_message.msg_type.value() != core_message_type_enum::hello_message_type\n          // allow closing_connection_message so we can finish disconnecting\n          && received_message.msg_type.value() != core_message_type_enum::closing_connection_message_type )\n      {\n         dlog( \"Unexpected message from peer ${peer} while we have requested to close connection\",\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         return;\n      }\n      switch ( received_message.msg_type.value() )\n      {\n      case core_message_type_enum::hello_message_type:\n        on_hello_message(originating_peer, received_message.as<hello_message>());\n        break;\n      case core_message_type_enum::connection_accepted_message_type:\n        on_connection_accepted_message(originating_peer, received_message.as<connection_accepted_message>());\n        break;\n      case core_message_type_enum::connection_rejected_message_type:\n        on_connection_rejected_message(originating_peer, received_message.as<connection_rejected_message>());\n        break;\n      case core_message_type_enum::address_request_message_type:\n        on_address_request_message(originating_peer, received_message.as<address_request_message>());\n        break;\n      case core_message_type_enum::address_message_type:\n        on_address_message(originating_peer, received_message.as<address_message>());\n        break;\n      case core_message_type_enum::fetch_blockchain_item_ids_message_type:\n        on_fetch_blockchain_item_ids_message(\n              originating_peer, received_message.as<fetch_blockchain_item_ids_message>());\n        break;\n      case core_message_type_enum::blockchain_item_ids_inventory_message_type:\n        on_blockchain_item_ids_inventory_message(\n              originating_peer, received_message.as<blockchain_item_ids_inventory_message>());\n        break;\n      case core_message_type_enum::fetch_items_message_type:\n        on_fetch_items_message(originating_peer, received_message.as<fetch_items_message>());\n        break;\n      case core_message_type_enum::item_not_available_message_type:\n        on_item_not_available_message(originating_peer, received_message.as<item_not_available_message>());\n        break;\n      case core_message_type_enum::item_ids_inventory_message_type:\n        on_item_ids_inventory_message(originating_peer, received_message.as<item_ids_inventory_message>());\n        break;\n      case core_message_type_enum::closing_connection_message_type:\n        on_closing_connection_message(originating_peer, received_message.as<closing_connection_message>());\n        break;\n      case core_message_type_enum::block_message_type:\n        process_block_message(originating_peer, received_message, message_hash);\n        break;\n      case core_message_type_enum::current_time_request_message_type:\n        on_current_time_request_message(originating_peer, received_message.as<current_time_request_message>());\n        break;\n      case core_message_type_enum::current_time_reply_message_type:\n        on_current_time_reply_message(originating_peer, received_message.as<current_time_reply_message>());\n        break;\n      case core_message_type_enum::check_firewall_message_type:\n        break;\n      case core_message_type_enum::check_firewall_reply_message_type:\n        break;\n      case core_message_type_enum::get_current_connections_request_message_type:\n        break;\n      case core_message_type_enum::get_current_connections_reply_message_type:\n        break;\n\n      default:\n        // ignore any message in between core_message_type_first and _last that we don't handle above\n        // to allow us to add messages in the future\n        if (received_message.msg_type.value() < core_message_type_enum::core_message_type_first ||\n            received_message.msg_type.value() > core_message_type_enum::core_message_type_last)\n          process_ordinary_message(originating_peer, received_message, message_hash);\n        break;\n      }\n    }\n\n\n    fc::variant_object node_impl::generate_hello_user_data()\n    {\n      VERIFY_CORRECT_THREAD();\n      // for the time being, shoehorn a bunch of properties into the user_data variant object,\n      // which lets us add and remove fields without changing the protocol.  Once we\n      // settle on what we really want in there, we'll likely promote them to first\n      // class fields in the hello message\n      fc::mutable_variant_object user_data;\n      user_data[\"fc_git_revision_sha\"] = fc::git_revision_sha;\n      user_data[\"fc_git_revision_unix_timestamp\"] = fc::git_revision_unix_timestamp;\n#if defined( __APPLE__ )\n      user_data[\"platform\"] = \"osx\";\n#elif defined( __OpenBSD__ )\n      user_data[\"platform\"] = \"obsd\";\n#elif defined( __linux__ )\n      user_data[\"platform\"] = \"linux\";\n#elif defined( _MSC_VER )\n      user_data[\"platform\"] = \"win32\";\n#else\n      user_data[\"platform\"] = \"other\";\n#endif\n      user_data[\"bitness\"] = sizeof(void*) * 8;\n\n      user_data[\"node_id\"] = fc::variant( _node_id, 1 );\n\n      item_hash_t head_block_id = _delegate->get_head_block_id();\n      user_data[\"last_known_block_hash\"] = fc::variant( head_block_id, 1 );\n      user_data[\"last_known_block_number\"] = _delegate->get_block_number(head_block_id);\n      user_data[\"last_known_block_time\"] = _delegate->get_block_time(head_block_id);\n\n      if (!_hard_fork_block_numbers.empty())\n        user_data[\"last_known_fork_block_number\"] = _hard_fork_block_numbers.back();\n\n      return user_data;\n    }\n    void node_impl::parse_hello_user_data_for_peer(peer_connection* originating_peer, const fc::variant_object& user_data)\n    {\n      VERIFY_CORRECT_THREAD();\n      // try to parse data out of the user_agent string\n      if (user_data.contains(\"graphene_git_revision_sha\"))\n        originating_peer->graphene_git_revision_sha = user_data[\"graphene_git_revision_sha\"].as_string();\n      if (user_data.contains(\"graphene_git_revision_unix_timestamp\"))\n        originating_peer->graphene_git_revision_unix_timestamp = fc::time_point_sec(\n              user_data[\"graphene_git_revision_unix_timestamp\"].as<uint32_t>(1));\n      if (user_data.contains(\"fc_git_revision_sha\"))\n        originating_peer->fc_git_revision_sha = user_data[\"fc_git_revision_sha\"].as_string();\n      if (user_data.contains(\"fc_git_revision_unix_timestamp\"))\n        originating_peer->fc_git_revision_unix_timestamp = fc::time_point_sec(\n              user_data[\"fc_git_revision_unix_timestamp\"].as<uint32_t>(1));\n      if (user_data.contains(\"platform\"))\n        originating_peer->platform = user_data[\"platform\"].as_string();\n      if (user_data.contains(\"bitness\"))\n        originating_peer->bitness = user_data[\"bitness\"].as<uint32_t>(1);\n      if (user_data.contains(\"node_id\"))\n        originating_peer->node_id = user_data[\"node_id\"].as<node_id_t>(1);\n      if (user_data.contains(\"last_known_fork_block_number\"))\n        originating_peer->last_known_fork_block_number = user_data[\"last_known_fork_block_number\"].as<uint32_t>(1);\n    }\n\n   void node_impl::on_hello_message( peer_connection* originating_peer, const hello_message& hello_message_received )\n   {\n      VERIFY_CORRECT_THREAD();\n      auto remote_endpoint = originating_peer->get_remote_endpoint(); // Note: this returns a copy\n      // Do gatekeeping first\n      if( originating_peer->their_state != peer_connection::their_connection_state::just_connected )\n      {\n         // we can wind up here if we've connected to ourselves, and the source and\n         // destination endpoints are the same, causing messages we send out\n         // to arrive back on the initiating socket instead of the receiving\n         // socket.  If we did a complete job of enumerating local addresses,\n         // we could avoid directly connecting to ourselves, or at least detect\n         // immediately when we did it and disconnect.\n\n         // The only way I know of that we'd get an unexpected hello that we\n         //  can't really guard against is if we do a simulatenous open, we\n         // probably need to think through that case.  We're not attempting that\n         // yet, though, so it's ok to just disconnect here.\n         wlog( \"Unexpected hello_message from peer ${peer}, disconnecting\",\n               (\"peer\", remote_endpoint) );\n         disconnect_from_peer( originating_peer, \"Received an unexpected hello_message\" );\n         return;\n      }\n\n      // Check chain_id\n      if( hello_message_received.chain_id != _chain_id )\n      {\n         wlog( \"Received hello message from peer ${peer} on a different chain: ${message}\",\n               (\"peer\", remote_endpoint)\n               (\"message\", hello_message_received) );\n         // If it is an outbound connection, make sure we won't reconnect to the peer soon\n         if( peer_connection_direction::outbound == originating_peer->direction )\n         {\n            // Note: deleting is not the best approach since it can be readded soon and we will reconnect soon.\n            //       Marking it \"permanently rejected\" is also not good enough since the peer can be \"fixed\".\n            //       It seems the best approach is to reduce its weight significantly.\n            greatly_delay_next_conn_to( this, *remote_endpoint );\n         }\n         // Now reject\n         std::ostringstream rejection_message;\n         rejection_message << \"You're on a different chain than I am.  I'm on \" << _chain_id.str() <<\n                              \" and you're on \" << hello_message_received.chain_id.str();\n         connection_rejected_message connection_rejected( _user_agent_string, core_protocol_version,\n                                                          *remote_endpoint,\n                                                          rejection_reason_code::different_chain,\n                                                          rejection_message.str() );\n         originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n         originating_peer->send_message( message(connection_rejected) );\n         // for this type of message, we're immediately disconnecting this peer, instead of trying to\n         // allowing her to ask us for peers (any of our peers will be on the same chain as us, so there's no\n         // benefit of sharing them)\n         disconnect_from_peer( originating_peer, \"You are on a different chain from me\" );\n         return;\n      }\n\n      // Validate the peer's public key.\n      // Note: the node_id in user_data is not verified.\n      fc::optional<fc::ecc::public_key> expected_node_public_key;\n      try\n      {\n         fc::sha256::encoder shared_secret_encoder;\n         fc::sha512 shared_secret = originating_peer->get_shared_secret();\n         shared_secret_encoder.write(shared_secret.data(), sizeof(shared_secret));\n         expected_node_public_key = fc::ecc::public_key( hello_message_received.signed_shared_secret,\n                                                         shared_secret_encoder.result(), false );\n      }\n      catch( const fc::exception& e )\n      {\n         wlog( \"Error when validating signature in hello message from peer ${peer}: ${e}\",\n               (\"peer\", remote_endpoint)(\"e\", e.to_detail_string()) );\n      }\n\n      if( !expected_node_public_key\n          || hello_message_received.node_public_key != expected_node_public_key->serialize() )\n      {\n         wlog( \"Invalid signature in hello message from peer ${peer}\",\n               (\"peer\", remote_endpoint) );\n         connection_rejected_message connection_rejected( _user_agent_string, core_protocol_version,\n                                                          *remote_endpoint,\n                                                          rejection_reason_code::invalid_hello_message,\n                                                          \"Invalid signature in hello message\" );\n         originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n         originating_peer->send_message( message(connection_rejected) );\n         // for this type of message, we're immediately disconnecting this peer\n         disconnect_from_peer( originating_peer, connection_rejected.reason_string );\n         return;\n      }\n\n      // this already_connected check must come before we fill in peer data below\n      node_id_t peer_node_id = hello_message_received.node_public_key;\n      try\n      {\n        peer_node_id = hello_message_received.user_data[\"node_id\"].as<node_id_t>(1);\n      }\n      catch (const fc::exception&)\n      {\n        // either it's not there or it's not a valid session id.  either way, ignore.\n        dlog( \"Peer ${endpoint} sent us a hello message without a valid node_id in user_data\",\n              (\"endpoint\", remote_endpoint ) );\n      }\n      // The peer's node_id should not be null\n      static const node_id_t null_node_id;\n      if( null_node_id == peer_node_id )\n      {\n         wlog( \"The node_id in the hello_message from peer ${peer} is null, disconnecting\",\n               (\"peer\", remote_endpoint) );\n         disconnect_from_peer( originating_peer, \"Your node_id in the hello_message is null\" );\n         return;\n      }\n      // Check whether the peer is myself\n      if( _node_id == peer_node_id )\n      {\n         ilog( \"Received a hello_message from peer ${peer} with id ${id} that is myself or claimed to be myself, \"\n               \"rejection\",\n               (\"peer\", remote_endpoint)\n               (\"id\", peer_node_id) );\n         // If it is an outbound connection, make sure we won't reconnect to the peer soon\n         if( peer_connection_direction::outbound == originating_peer->direction )\n         {\n            // Note: deleting is not the best approach since it can be readded soon and we will reconnect soon.\n            //       Marking it \"permanently rejected\" is also not good enough since the peer can be \"fixed\".\n            //       It seems the best approach is to reduce its weight significantly.\n            greatly_delay_next_conn_to( this, *remote_endpoint );\n         }\n         // Now reject\n         // Note: this can happen in rare cases if the peer is not actually myself but another node.\n         //       Anyway, we see it as ourselves, reject it and disconnect it.\n         connection_rejected_message connection_rejected( _user_agent_string, core_protocol_version,\n                                                          *remote_endpoint,\n                                                          rejection_reason_code::connected_to_self,\n                                                          \"I'm connecting to myself\" );\n         originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n         originating_peer->send_message( message(connection_rejected) );\n         disconnect_from_peer( originating_peer, connection_rejected.reason_string );\n         return;\n      }\n      // Get a pointer to an exising connection to the peer (if one exists) for later use\n      peer_connection_ptr already_connected_peer = get_peer_by_node_id( peer_node_id );\n\n      // store off the data provided in the hello message\n      originating_peer->user_agent = hello_message_received.user_agent;\n      originating_peer->node_public_key = hello_message_received.node_public_key;\n      originating_peer->core_protocol_version = hello_message_received.core_protocol_version;\n      originating_peer->inbound_address = hello_message_received.inbound_address;\n      originating_peer->inbound_port = hello_message_received.inbound_port;\n      originating_peer->outbound_port = hello_message_received.outbound_port;\n      // Note: more data is stored after initialized remote_inbound_endpoint\n\n      // For an outbound connection, we know the remote_inbound_endpoint already, so keep it unchanged.\n      // For an inbound connection, we initialize it here.\n      if( !originating_peer->remote_inbound_endpoint )\n      {\n         // Note: the data is not yet verified, so we need to use it with caution.\n         //\n         // We will advertise \"remote_inbound_endpoint\" when other peers request addresses.\n         //\n         // On the one hand, we want to advertise as accurate data as possible to other peers (we will try to verify),\n         // on the other hand, we still want to advertise it to other peers if we didn't have a chance to verify it.\n         //\n         // When the peer is not listening (i.e. it tells us its inbound port is 0), the inbound address it tells us\n         // may be invalid (e.g. 0.0.0.0), and we are not going to verify it anyway.\n         // For observation purposes, we still advertise it to other peers, and we need to tell them an address,\n         // so we use the address we see.\n         //\n         // In addition, by now, our list or exclude list for peer advertisement only contains IP endpoints but not\n         // nodes' public keys (we can't use node_id because it changes every time the node restarts). Using a valid\n         // address is better for the purpose.\n         if( 0 == originating_peer->inbound_port )\n            originating_peer->remote_inbound_endpoint = fc::ip::endpoint( remote_endpoint->get_address() );\n         else if( originating_peer->inbound_address.is_public_address()\n                  || originating_peer->inbound_address == remote_endpoint->get_address() )\n            originating_peer->remote_inbound_endpoint = fc::ip::endpoint( originating_peer->inbound_address,\n                                                                          originating_peer->inbound_port );\n         else\n            originating_peer->remote_inbound_endpoint = remote_endpoint;\n      }\n\n      // Note: store node_id after initialized remote_inbound_endpoint to avoid a race condition\n\n      // will probably be overwritten in parse_hello_user_data_for_peer()\n      originating_peer->node_id = hello_message_received.node_public_key;\n\n      parse_hello_user_data_for_peer(originating_peer, hello_message_received.user_data);\n\n      // if they didn't provide a last known fork, try to guess it\n      if (originating_peer->last_known_fork_block_number == 0 &&\n          originating_peer->graphene_git_revision_unix_timestamp)\n      {\n        uint32_t unix_timestamp = originating_peer->graphene_git_revision_unix_timestamp->sec_since_epoch();\n        originating_peer->last_known_fork_block_number = _delegate->estimate_last_known_fork_from_git_revision_timestamp(unix_timestamp);\n      }\n\n      // now decide what to do with it\n      if (originating_peer->last_known_fork_block_number != 0)\n      {\n          uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(originating_peer->last_known_fork_block_number);\n          if (next_fork_block_number != 0)\n          {\n            // we know about a fork they don't.  See if we've already passed that block.  If we have, don't let them\n            // connect because we won't be able to give them anything useful\n            uint32_t head_block_num = _delegate->get_block_number(_delegate->get_head_block_id());\n            if (next_fork_block_number < head_block_num)\n            {\n#ifdef ENABLE_DEBUG_ULOGS\n              ulog(\"Rejecting connection from peer because their version is too old.  Their version date: ${date}\", (\"date\", originating_peer->graphene_git_revision_unix_timestamp));\n#endif\n              wlog(\"Received hello message from peer running a version of that can only understand blocks up to #${their_hard_fork}, but I'm at head block number #${my_block_number}\",\n                   (\"their_hard_fork\", next_fork_block_number)(\"my_block_number\", head_block_num));\n              std::ostringstream rejection_message;\n              rejection_message << \"Your client is outdated -- you can only understand blocks up to #\" << next_fork_block_number << \", but I'm already on block #\" << head_block_num;\n              connection_rejected_message connection_rejected(_user_agent_string, core_protocol_version,\n                                                              *remote_endpoint,\n                                                              rejection_reason_code::unspecified,\n                                                              rejection_message.str() );\n\n              originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n              originating_peer->send_message(message(connection_rejected));\n              // for this type of message, we're immediately disconnecting this peer, instead of trying to\n              // allowing her to ask us for peers (any of our peers will be on the same chain as us, so there's no\n              // benefit of sharing them)\n              disconnect_from_peer(originating_peer, \"Your client is too old, please upgrade\");\n              return;\n            }\n          }\n      }\n\n        if( peer_connection_ptr() != already_connected_peer )\n        {\n          // If it is an outbound connection, update the existing connection's inbound_endpoint.\n          // Note: there may be a race condition that multiple tasks try to write the same data\n          if( peer_connection_direction::outbound == originating_peer->direction\n              && originating_peer->node_public_key == already_connected_peer->node_public_key )\n          {\n              auto already_connected_endpoint = already_connected_peer->get_remote_endpoint(); // This returns a copy\n              ilog( \"Verified that endpoint ${ep} is reachable and belongs to peer ${peer} with id ${id}\",\n                    (\"ep\", remote_endpoint)\n                    (\"peer\", already_connected_endpoint)\n                    (\"id\", already_connected_peer->node_id) );\n              // Do not replace a verified public address with a private or local address.\n              // Note: there is a scenario that some nodes in the same local network may have connected to each other,\n              //         and of course some are outbound connections and some are inbound, so we are unable to update\n              //         all the data, not to mention that their external addresses might be inaccessible to each\n              //         other.\n              //       Unless they are all configured with the \"p2p-inbound-endpoint\" option with an external address,\n              //         even if they all start out connecting to each other's external addresses, at some point they\n              //         may try to connect to each other's local addresses and possibly stay connected.\n              //       In this case, if the nodes aren't configured with the \"advertise-peer-algorithm\" option and\n              //         related options properly, when advertising connected peers to other peers, they may expose\n              //         that they are in the same local network and connected to each other.\n              //       On the other hand, when we skip updates in some cases, we may end up trying to reconnect soon\n              //         and endlessly (which is addressed with additional_inbound_endpoints).\n              already_connected_peer->additional_inbound_endpoints.insert( *remote_endpoint );\n              if( peer_connection_direction::inbound == already_connected_peer->direction )\n              {\n                 already_connected_peer->potential_inbound_endpoints[*remote_endpoint]\n                       = firewalled_state::not_firewalled;\n              }\n              if( already_connected_peer->is_firewalled != firewalled_state::not_firewalled // implies it's inbound\n                  || remote_endpoint->get_address().is_public_address()\n                  || !already_connected_peer->get_endpoint_for_connecting()->get_address().is_public_address() )\n              {\n                 ilog( \"Saving verification result ${ep} for peer ${peer} with id ${id}\",\n                       (\"ep\", remote_endpoint)\n                       (\"peer\", already_connected_endpoint)\n                       (\"id\", already_connected_peer->node_id) );\n                 already_connected_peer->remote_inbound_endpoint = remote_endpoint;\n                 already_connected_peer->is_firewalled = firewalled_state::not_firewalled;\n              }\n              // If the already connected peer is in the active connections list, save the endpoint to the peer db\n              if( peer_connection::connection_negotiation_status::negotiation_complete\n                     == already_connected_peer->negotiation_status )\n                 save_successful_address( this, *remote_endpoint );\n          }\n          // Now reject\n          connection_rejected_message connection_rejected( _user_agent_string, core_protocol_version,\n                                                           *remote_endpoint,\n                                                           rejection_reason_code::already_connected,\n                                                           \"I'm already connected to you\" );\n          originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n          originating_peer->send_message( message(connection_rejected) );\n          ilog(\"Received a hello_message from peer ${peer} that I'm already connected to (with id ${id}), rejection\",\n               (\"peer\", remote_endpoint)\n               (\"id\", originating_peer->node_id));\n          // If already connected, we disconnect\n          disconnect_from_peer( originating_peer, connection_rejected.reason_string );\n        }\n#ifdef ENABLE_P2P_DEBUGGING_API\n        else if(!_allowed_peers.empty() &&\n                _allowed_peers.find(originating_peer->node_id) == _allowed_peers.end())\n        {\n          connection_rejected_message connection_rejected(_user_agent_string, core_protocol_version,\n                                                          *remote_endpoint,\n                                                          rejection_reason_code::blocked,\n                                                          \"you are not in my allowed_peers list\");\n          originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n          originating_peer->send_message( message(connection_rejected ) );\n          dlog( \"Received a hello_message from peer ${peer} who isn't in my allowed_peers list, rejection\",\n                (\"peer\", remote_endpoint ) );\n        }\n#endif // ENABLE_P2P_DEBUGGING_API\n        else\n        {\n          // whether we're planning on accepting them as a peer or not, they seem to be a valid node,\n          // so add them to our database if they're not firewalled\n          if( peer_connection_direction::outbound == originating_peer->direction )\n          {\n             // For outbound connection, we already know the peer is not firewalled,\n             // and it should be already in the peer database. Do nothing here.\n          }\n          else if( 0 == originating_peer->inbound_port )\n          {\n             ilog( \"peer ${peer} did not give an inbound port so I'm treating them as if they are firewalled.\",\n                   (\"peer\", remote_endpoint) );\n             originating_peer->is_firewalled = firewalled_state::firewalled;\n          }\n          else\n          {\n             // Note: no matter how we guess, we end up adding these to our peer database and trying to connect later.\n\n             // First, we add the inbound endpoint that the peer told us it is listening on.\n             fc::flat_set<fc::ip::endpoint> endpoints_to_save;\n             endpoints_to_save.insert( fc::ip::endpoint( originating_peer->inbound_address,\n                                                         originating_peer->inbound_port ) );\n\n             // Second, we add the address and port we see.\n             // It might be the same as above, but that's OK.\n             endpoints_to_save.insert( *remote_endpoint );\n\n             // Third, we add the address we see, with the inbound port the peer told us.\n             // It might be the same as above, but that's OK.\n             endpoints_to_save.insert( fc::ip::endpoint( remote_endpoint->get_address(),\n                                                         originating_peer->inbound_port ) );\n\n             ilog( \"Saving potential endpoints to the peer database for peer ${peer}: ${endpoints}\",\n                   (\"peer\", remote_endpoint) (\"endpoints\", endpoints_to_save) );\n\n             for( const auto& ep : endpoints_to_save )\n             {\n                // add to the peer database\n                auto updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_ep( ep );\n                updated_peer_record.last_seen_time = fc::time_point::now();\n                _potential_peer_db.update_entry( updated_peer_record );\n                // mark as a potential inbound address\n                originating_peer->potential_inbound_endpoints[ep] = firewalled_state::unknown;\n             }\n\n             // Note: we don't update originating_peer->is_firewalled, because we might guess wrong\n\n          }\n\n          if (!is_accepting_new_connections())\n          {\n            connection_rejected_message connection_rejected(_user_agent_string, core_protocol_version,\n                                                            *remote_endpoint,\n                                                            rejection_reason_code::not_accepting_connections,\n                                                            \"not accepting any more incoming connections\");\n            originating_peer->their_state = peer_connection::their_connection_state::connection_rejected;\n            originating_peer->send_message(message(connection_rejected));\n            ilog(\"Received a hello_message from peer ${peer}, but I'm not accepting any more connections, rejection\",\n                 (\"peer\", remote_endpoint));\n          }\n          else\n          {\n            originating_peer->their_state = peer_connection::their_connection_state::connection_accepted;\n            originating_peer->send_message(message(connection_accepted_message()));\n            ilog(\"Received a hello_message from peer ${peer}, sending reply to accept connection\",\n                 (\"peer\", remote_endpoint));\n          }\n        }\n   }\n\n   void node_impl::on_connection_accepted_message( peer_connection* originating_peer,\n                                                   const connection_accepted_message& ) const\n   {\n      VERIFY_CORRECT_THREAD();\n      // Gatekeeping code\n      // We only send one address request message shortly after connected\n      if( originating_peer->our_state != peer_connection::our_connection_state::just_connected )\n      {\n         // Log and ignore\n         wlog( \"Received an unexpected connection_accepted message from ${peer}\",\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         return;\n      }\n\n      ilog( \"Received a connection_accepted in response to my \\\"hello\\\" from ${peer}\",\n            (\"peer\", originating_peer->get_remote_endpoint()) );\n      originating_peer->negotiation_status = peer_connection::connection_negotiation_status::peer_connection_accepted;\n      originating_peer->our_state = peer_connection::our_connection_state::connection_accepted;\n      originating_peer->expecting_address_message = true;\n      originating_peer->send_message(address_request_message());\n   }\n\n    void node_impl::on_connection_rejected_message(peer_connection* originating_peer, const connection_rejected_message& connection_rejected_message_received)\n    {\n      VERIFY_CORRECT_THREAD();\n      if (originating_peer->our_state == peer_connection::our_connection_state::just_connected)\n      {\n        ilog(\"Received a rejection from ${peer} in response to my \\\"hello\\\", reason: \\\"${reason}\\\"\",\n             (\"peer\", originating_peer->get_remote_endpoint())\n             (\"reason\", connection_rejected_message_received.reason_string));\n\n        originating_peer->negotiation_status = peer_connection::connection_negotiation_status\n                                                              ::peer_connection_rejected;\n        originating_peer->our_state = peer_connection::our_connection_state::connection_rejected;\n\n        if( connection_rejected_message_received.reason_code == rejection_reason_code::connected_to_self\n            || connection_rejected_message_received.reason_code == rejection_reason_code::different_chain )\n        {\n          // Using remote_endpoint here for an outbound connection is OK.\n          // For an inbound connection, we should have not saved anything to the peer database yet, nor we will\n          //   save anything (it would be weird if they rejected us but we didn't reject them),\n          //   so using remote_endpoint here at least won't do anything bad.\n          //   Note: we should not erase or update data by the peer's claimed inbound_address,\n          //         because the data is still unreliable.\n          // Note: deleting is not the best approach since it can be readded soon and we will reconnect soon.\n          //       Marking it \"permanently rejected\" is also not good enough since the peer can be \"fixed\".\n          //       It seems the best approach is to reduce its weight significantly.\n          greatly_delay_next_conn_to( this, *originating_peer->get_remote_endpoint() );\n          // Note: we do not send closing_connection_message, but close directly. This is probably OK\n          move_peer_to_closing_list(originating_peer->shared_from_this());\n          originating_peer->close_connection();\n        }\n        // Note: ideally, if it is an outbound connection, and the rejection reason is \"already_connected\",\n        //         we should update the existing connection's inbound_endpoint and mark it as verified.\n        //       However, at the moment maybe we haven't processed its hello message,\n        //         so don't know its node_id and unable to locate the existing connection.\n        //       So it is better to do the update in on_hello_message().\n        //       It is also possible that its hello message comes too late and the connection is already closed,\n        //         in which case we don't have a chance to update anyway.\n        else\n        {\n          // update our database to record that we were rejected so we won't try to connect again for a while\n          // this only happens on connections we originate, so we should already know that peer is not firewalled\n          fc::optional<potential_peer_record> updated_peer_record\n                = _potential_peer_db.lookup_entry_for_endpoint(originating_peer->get_socket().remote_endpoint());\n          if (updated_peer_record)\n          {\n            updated_peer_record->last_connection_disposition = last_connection_rejected;\n            updated_peer_record->last_connection_attempt_time = fc::time_point::now();\n            // Note: we do not increase number_of_failed_connection_attempts here, this is probably OK\n            _potential_peer_db.update_entry(*updated_peer_record);\n          }\n          originating_peer->expecting_address_message = true;\n          originating_peer->send_message(address_request_message());\n        }\n      }\n      else\n      {\n        // Note: in older versions, FC_THROW() was called here,\n        //       which would cause on_connection_closed() to be called,\n        //       which would then close the connection when the peer_connection object was destroyed.\n        //       Explicitly closing the connection here is more intuitive.\n        wlog( \"Unexpected connection_rejected_message from peer ${peer}, disconnecting\",\n              (\"peer\", originating_peer->get_remote_endpoint()) );\n        disconnect_from_peer( originating_peer, \"Received an unexpected connection_rejected_message\" );\n      }\n    }\n\n    void node_impl::on_address_request_message(peer_connection* originating_peer, const address_request_message&)\n    {\n      VERIFY_CORRECT_THREAD();\n      // Gatekeeping code\n      if( originating_peer->their_state != peer_connection::their_connection_state::connection_accepted\n          && originating_peer->their_state != peer_connection::their_connection_state::connection_rejected )\n      {\n         wlog( \"Unexpected address_request_message from peer ${peer}, disconnecting\",\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         disconnect_from_peer( originating_peer, \"Received an unexpected address_request_message\" );\n         return;\n      }\n\n      dlog( \"Received an address request message from peer ${peer}\",\n            (\"peer\", originating_peer->get_remote_endpoint()) );\n\n      address_message reply;\n      if (_address_builder != nullptr )\n         _address_builder->build( this, reply );\n      originating_peer->send_message(reply);\n\n      // If we rejected their connection, disconnect now\n      if( originating_peer->their_state == peer_connection::their_connection_state::connection_rejected )\n      {\n         disconnect_from_peer( originating_peer,\n                               \"I rejected your connection request (hello message) so I'm disconnecting\" );\n      }\n    }\n\n   void node_impl::set_advertise_algorithm( const std::string& algo,\n         const std::vector<std::string>& advertise_or_exclude_list )\n   {\n      VERIFY_CORRECT_THREAD();\n      if (algo == \"exclude_list\")\n      {\n         _address_builder = std::make_shared<exclude_address_builder>(advertise_or_exclude_list);\n      }\n      else if (algo == \"list\")\n      {\n         _address_builder = std::make_shared<list_address_builder>(advertise_or_exclude_list);\n      }\n      else if (algo == \"nothing\")\n      {\n         _address_builder = nullptr;\n      }\n      else\n         _address_builder = std::make_shared<all_address_builder>();\n   }\n\n   void node_impl::on_address_message( peer_connection* originating_peer,\n                                       const address_message& address_message_received )\n   {\n      VERIFY_CORRECT_THREAD();\n      // Do some gatekeeping here.\n      // Malious peers can easily bypass our checks in on_hello_message(), and we will then request addresses anyway,\n      //   so checking connection_state here is useless.\n      // The size can be large, so we only handle the first N addresses.\n      // The peer might send us lots of address messages even if we didn't request,\n      //   so we'd better know whether we have sent an address request message recently.\n      if( !originating_peer->expecting_address_message )\n      {\n         // Log and ignore\n         wlog( \"Received an unexpected address message containing ${size} addresses for peer ${peer}\",\n               (\"size\", address_message_received.addresses.size())\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         return;\n      }\n      originating_peer->expecting_address_message = false;\n\n      dlog( \"Received an address message containing ${size} addresses for peer ${peer}\",\n            (\"size\", address_message_received.addresses.size())\n            (\"peer\", originating_peer->get_remote_endpoint()) );\n      if( _node_configuration.connect_to_new_peers )\n      {\n         size_t count = 0;\n         for (const address_info& address : address_message_received.addresses)\n         {\n            dlog( \"    ${endpoint} last seen ${time}, firewalled status ${fw}\",\n                  (\"endpoint\", address.remote_endpoint)(\"time\", address.last_seen_time)\n                  (\"fw\", address.firewalled) );\n            ++count;\n            if( count >= _max_addrs_to_handle_at_once )\n               break;\n         }\n         std::vector<graphene::net::address_info> updated_addresses;\n         updated_addresses.reserve( count );\n         auto now = fc::time_point_sec(fc::time_point::now());\n         count = 0;\n         for( const address_info& address : address_message_received.addresses )\n         {\n            if( 0 == address.remote_endpoint.port() )\n               continue;\n            updated_addresses.emplace_back( address.remote_endpoint,\n                                            now,\n                                            address.latency,\n                                            address.node_id,\n                                            address.direction,\n                                            address.firewalled );\n            ++count;\n            if( count >= _max_addrs_to_handle_at_once )\n               break;\n         }\n         if( merge_address_info_with_potential_peer_database(updated_addresses) )\n            trigger_p2p_network_connect_loop();\n      }\n\n      if (_handshaking_connections.find(originating_peer->shared_from_this()) != _handshaking_connections.end())\n      {\n        // if we were handshaking, we need to continue with the next step in handshaking (which is either\n        // ending handshaking and starting synchronization or disconnecting)\n        if( originating_peer->our_state == peer_connection::our_connection_state::connection_rejected)\n          disconnect_from_peer(originating_peer, \"You rejected my connection request (hello message) so I'm disconnecting\");\n        else if (originating_peer->their_state == peer_connection::their_connection_state::connection_rejected)\n          disconnect_from_peer(originating_peer, \"I rejected your connection request (hello message) so I'm disconnecting\");\n        else\n        {\n          // Note: updating last_connection_disposition to last_connection_succeeded for inbound connections\n          //       doesn't seem correct\n          if( peer_connection_direction::outbound == originating_peer->direction )\n             save_successful_address( this, *originating_peer->get_remote_endpoint() );\n\n          // transition it to our active list\n          originating_peer->negotiation_status = peer_connection::connection_negotiation_status::negotiation_complete;\n          move_peer_to_active_list(originating_peer->shared_from_this());\n          new_peer_just_added(originating_peer->shared_from_this());\n        }\n      }\n      // else if this was an active connection, then this was just a reply to our periodic address requests.\n      // we've processed it, there's nothing else to do\n      // Note: we could reinitialize inbound endpoint verification here, but it doesn't seem necessary\n   }\n\n    void node_impl::on_fetch_blockchain_item_ids_message(peer_connection* originating_peer,\n                                                         const fetch_blockchain_item_ids_message& fetch_blockchain_item_ids_message_received)\n    {\n      VERIFY_CORRECT_THREAD();\n      // Gatekeeping code\n      if( originating_peer->their_state != peer_connection::their_connection_state::connection_accepted )\n      {\n         wlog( \"Unexpected fetch_blockchain_item_ids_message from peer ${peer}, disconnecting\",\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         disconnect_from_peer( originating_peer, \"Received an unexpected fetch_blockchain_item_ids_message\" );\n         return;\n      }\n\n      item_id peers_last_item_seen = item_id(fetch_blockchain_item_ids_message_received.item_type, item_hash_t());\n      if (fetch_blockchain_item_ids_message_received.blockchain_synopsis.empty())\n      {\n        dlog(\"sync: received a request for item ids starting at the beginning of the chain \"\n             \"from peer ${peer_endpoint} (full request: ${synopsis})\",\n             (\"peer_endpoint\", originating_peer->get_remote_endpoint())\n             (\"synopsis\", fetch_blockchain_item_ids_message_received.blockchain_synopsis));\n      }\n      else\n      {\n        item_hash_t peers_last_item_hash_seen = fetch_blockchain_item_ids_message_received.blockchain_synopsis.back();\n        dlog(\"sync: received a request for item ids after ${last_item_seen} from peer ${peer_endpoint} (full request: ${synopsis})\",\n             (\"last_item_seen\", peers_last_item_hash_seen)\n             (\"peer_endpoint\", originating_peer->get_remote_endpoint())\n             (\"synopsis\", fetch_blockchain_item_ids_message_received.blockchain_synopsis));\n        peers_last_item_seen.item_hash = peers_last_item_hash_seen;\n      }\n\n      blockchain_item_ids_inventory_message reply_message;\n      reply_message.item_type = fetch_blockchain_item_ids_message_received.item_type;\n      reply_message.total_remaining_item_count = 0;\n      try\n      {\n        reply_message.item_hashes_available\n              = _delegate->get_block_ids(fetch_blockchain_item_ids_message_received.blockchain_synopsis,\n                                         reply_message.total_remaining_item_count);\n      }\n      catch (const peer_is_on_an_unreachable_fork&)\n      {\n        dlog(\"Peer is on a fork and there's no set of blocks we can provide to switch them to our fork\");\n        // we reply with an empty list as if we had an empty blockchain;\n        // we don't want to disconnect because they may be able to provide\n        // us with blocks on their chain\n      }\n\n      bool disconnect_from_inhibited_peer = false;\n      // if our client doesn't have any items after the item the peer requested, it will send back\n      // a list containing the last item the peer requested\n      //idump((reply_message)(fetch_blockchain_item_ids_message_received.blockchain_synopsis)); // for debug\n      if( reply_message.item_hashes_available.empty() )\n        originating_peer->peer_needs_sync_items_from_us = false; /* I have no items in my blockchain */\n      else if( !fetch_blockchain_item_ids_message_received.blockchain_synopsis.empty() &&\n               reply_message.item_hashes_available.size() == 1 &&\n               std::find(fetch_blockchain_item_ids_message_received.blockchain_synopsis.begin(),\n                         fetch_blockchain_item_ids_message_received.blockchain_synopsis.end(),\n                         reply_message.item_hashes_available.back() ) != fetch_blockchain_item_ids_message_received.blockchain_synopsis.end() )\n      {\n        /* the last item in the peer's list matches the last item in our list */\n        originating_peer->peer_needs_sync_items_from_us = false;\n        if (originating_peer->inhibit_fetching_sync_blocks)\n          disconnect_from_inhibited_peer = true; // delay disconnecting until after we send our reply to this fetch_blockchain_item_ids_message\n      }\n      else\n        originating_peer->peer_needs_sync_items_from_us = true;\n\n      if (!originating_peer->peer_needs_sync_items_from_us)\n      {\n        dlog(\"sync: peer is already in sync with us\");\n        // if we thought we had all the items this peer had, but now it turns out that we don't\n        // have the last item it requested to send from,\n        // we need to kick off another round of synchronization\n        if (!originating_peer->we_need_sync_items_from_peer &&\n            !fetch_blockchain_item_ids_message_received.blockchain_synopsis.empty() &&\n            !_delegate->has_item(peers_last_item_seen))\n        {\n          dlog(\"sync: restarting sync with peer ${peer}\", (\"peer\", originating_peer->get_remote_endpoint()));\n          start_synchronizing_with_peer(originating_peer->shared_from_this());\n        }\n      }\n      else\n      {\n        dlog(\"sync: peer is out of sync, sending peer ${count} items ids: first: ${first_item_id}, last: ${last_item_id}\",\n             (\"count\", reply_message.item_hashes_available.size())\n             (\"first_item_id\", reply_message.item_hashes_available.front())\n             (\"last_item_id\", reply_message.item_hashes_available.back()));\n        if (!originating_peer->we_need_sync_items_from_peer &&\n            !fetch_blockchain_item_ids_message_received.blockchain_synopsis.empty() &&\n            !_delegate->has_item(peers_last_item_seen))\n        {\n          dlog(\"sync: restarting sync with peer ${peer}\", (\"peer\", originating_peer->get_remote_endpoint()));\n          start_synchronizing_with_peer(originating_peer->shared_from_this());\n        }\n      }\n      originating_peer->send_message(reply_message);\n\n      if (disconnect_from_inhibited_peer)\n        {\n        // the peer has all of our blocks, and we don't want any of theirs, so disconnect them\n        disconnect_from_peer(originating_peer, \"you are on a fork that I'm unable to switch to\");\n        return;\n        }\n\n      // Why only for inbound connections?\n      if (originating_peer->direction == peer_connection_direction::inbound &&\n          _handshaking_connections.find(originating_peer->shared_from_this()) != _handshaking_connections.end())\n      {\n        // handshaking is done, move the connection to fully active status and start synchronizing\n        dlog(\"peer ${endpoint} which was handshaking with us has started synchronizing with us, \"\n             \"start syncing with it\",\n             (\"endpoint\", originating_peer->get_remote_endpoint()));\n\n        // Note: there was some code here to update the peer database, similar to the code in on_address_message(),\n        //       but this is an inbound connection,\n        //       updating last_connection_disposition to last_connection_succeeded doesn't seem correct,\n        //       so the code was removed.\n\n        // transition it to our active list\n        originating_peer->negotiation_status = peer_connection::connection_negotiation_status::negotiation_complete;\n        move_peer_to_active_list(originating_peer->shared_from_this());\n        new_peer_just_added(originating_peer->shared_from_this());\n      }\n    }\n\n    uint32_t node_impl::calculate_unsynced_block_count_from_all_peers()\n    {\n      VERIFY_CORRECT_THREAD();\n      uint32_t max_number_of_unfetched_items = 0;\n      fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n      for( const peer_connection_ptr& peer : _active_connections )\n      {\n        uint32_t this_peer_unfetched_items_count = (uint32_t)peer->ids_of_items_to_get.size()\n                                                 + peer->number_of_unfetched_item_ids;\n        max_number_of_unfetched_items = std::max(max_number_of_unfetched_items,\n                                                 this_peer_unfetched_items_count);\n      }\n      return max_number_of_unfetched_items;\n    }\n\n    // get a blockchain synopsis that makes sense to send to the given peer.\n    // If the peer isn't yet syncing with us, this is just a synopsis of our active blockchain\n    // If the peer is syncing with us, it is a synopsis of our active blockchain plus the\n    //    blocks the peer has already told us it has\n    std::vector<item_hash_t> node_impl::create_blockchain_synopsis_for_peer( const peer_connection* peer )\n    {\n      VERIFY_CORRECT_THREAD();\n      item_hash_t reference_point = peer->last_block_delegate_has_seen;\n\n      // when we call _delegate->get_blockchain_synopsis(), we may yield and there's a\n      // chance this peer's state will change before we get control back.  Save off\n      // the stuff necessary for generating the synopsis.\n      // This is pretty expensive, we should find a better way to do this\n      std::vector<item_hash_t> original_ids_of_items_to_get(peer->ids_of_items_to_get.begin(),\n                                                            peer->ids_of_items_to_get.end());\n      uint32_t number_of_blocks_after_reference_point = original_ids_of_items_to_get.size();\n\n      std::vector<item_hash_t> synopsis = _delegate->get_blockchain_synopsis(reference_point,\n                                                number_of_blocks_after_reference_point);\n\n#if 0\n      // just for debugging, enable this and set a breakpoint to step through\n      if (synopsis.empty())\n        synopsis = _delegate->get_blockchain_synopsis(reference_point, number_of_blocks_after_reference_point);\n\n      // TODO: it's possible that the returned synopsis is empty if the blockchain is empty (that's fine)\n      // or if the reference point is now past our undo history (that's not).\n      // in the second case, we should mark this peer as one we're unable to sync with and\n      // disconnect them.\n      if (reference_point != item_hash_t() && synopsis.empty())\n        FC_THROW_EXCEPTION(block_older_than_undo_history, \"You are on a fork I'm unable to switch to\");\n#endif\n\n      if( number_of_blocks_after_reference_point )\n      {\n        // then the synopsis is incomplete, add the missing elements from ids_of_items_to_get\n        uint32_t first_block_num_in_ids_to_get = _delegate->get_block_number(original_ids_of_items_to_get.front());\n        uint32_t true_high_block_num = first_block_num_in_ids_to_get + original_ids_of_items_to_get.size() - 1;\n\n        // in order to generate a seamless synopsis, we need to be using the same low_block_num as the\n        // backend code; the first block in the synopsis will be the low block number it used\n        uint32_t low_block_num = synopsis.empty() ? 1 : _delegate->get_block_number(synopsis.front());\n\n        do\n        {\n          if( low_block_num >= first_block_num_in_ids_to_get )\n            synopsis.push_back(original_ids_of_items_to_get[low_block_num - first_block_num_in_ids_to_get]);\n          low_block_num += (true_high_block_num - low_block_num + 2 ) / 2;\n        }\n        while ( low_block_num <= true_high_block_num );\n        assert(synopsis.back() == original_ids_of_items_to_get.back());\n      }\n      return synopsis;\n    }\n\n    void node_impl::fetch_next_batch_of_item_ids_from_peer( peer_connection* peer, bool reset_fork_tracking_data_for_peer /* = false */ )\n    {\n      VERIFY_CORRECT_THREAD();\n      if( reset_fork_tracking_data_for_peer )\n      {\n        peer->last_block_delegate_has_seen = item_hash_t();\n        peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hash_t());\n      }\n\n      fc::oexception synopsis_exception;\n      try\n      {\n        std::vector<item_hash_t> blockchain_synopsis = create_blockchain_synopsis_for_peer( peer );\n\n        item_hash_t last_item_seen = blockchain_synopsis.empty() ? item_hash_t() : blockchain_synopsis.back();\n        dlog( \"sync: sending a request for the next items after ${last_item_seen} to peer ${peer}, \"\n              \"(full request is ${blockchain_synopsis})\",\n             ( \"last_item_seen\", last_item_seen )\n             ( \"peer\", peer->get_remote_endpoint() )\n             ( \"blockchain_synopsis\", blockchain_synopsis ) );\n        peer->item_ids_requested_from_peer = boost::make_tuple( blockchain_synopsis, fc::time_point::now() );\n        peer->send_message( fetch_blockchain_item_ids_message(_sync_item_type, blockchain_synopsis ) );\n      }\n      catch (const block_older_than_undo_history& e)\n      {\n        synopsis_exception = e;\n      }\n      if (synopsis_exception)\n        disconnect_from_peer(peer, \"You are on a fork I'm unable to switch to\");\n    }\n\n    void node_impl::on_blockchain_item_ids_inventory_message(peer_connection* originating_peer,\n                                                             const blockchain_item_ids_inventory_message& blockchain_item_ids_inventory_message_received )\n    {\n      VERIFY_CORRECT_THREAD();\n      // ignore unless we asked for the data\n      if( originating_peer->item_ids_requested_from_peer )\n      {\n        // verify that the peer's the block ids the peer sent is a valid response to our request;\n        // It should either be an empty list of blocks, or a list of blocks that builds off of one of\n        // the blocks in the synopsis we sent\n        if (!blockchain_item_ids_inventory_message_received.item_hashes_available.empty())\n        {\n          // what's more, it should be a sequential list of blocks, verify that first\n          uint32_t first_block_number_in_reponse = _delegate->get_block_number(\n                         blockchain_item_ids_inventory_message_received.item_hashes_available.front());\n          // explicitly convert the size into 32 bit, should be OK\n          auto total_items = uint32_t( blockchain_item_ids_inventory_message_received.item_hashes_available.size() );\n          for (uint32_t i = 1; i < total_items; ++i)\n          {\n            uint32_t actual_num = _delegate->get_block_number(\n                                        blockchain_item_ids_inventory_message_received.item_hashes_available[i]);\n            uint32_t expected_num = first_block_number_in_reponse + i;\n            if (actual_num != expected_num)\n            {\n              wlog(\"Invalid response from peer ${peer_endpoint}.  The list of blocks they provided is not sequential, \"\n                 \"the ${position}th block in their reply was block number ${actual_num}, \"\n                 \"but it should have been number ${expected_num}\",\n                 (\"peer_endpoint\", originating_peer->get_remote_endpoint())\n                 (\"position\", i)\n                 (\"actual_num\", actual_num)\n                 (\"expected_num\", expected_num));\n              fc::exception error_for_peer(FC_LOG_MESSAGE(error,\n                                                        \"You gave an invalid response to my request for sync blocks.  The list of blocks you provided is not sequential, \"\n                                                        \"the ${position}th block in their reply was block number ${actual_num}, \"\n                                                        \"but it should have been number ${expected_num}\",\n                                                        (\"position\", i)\n                                                        (\"actual_num\", actual_num)\n                                                        (\"expected_num\", expected_num)));\n              disconnect_from_peer(originating_peer,\n                                 \"You gave an invalid response to my request for sync blocks\",\n                                 true, error_for_peer);\n              return;\n            }\n          }\n\n          const std::vector<item_hash_t>& synopsis_sent_in_request = originating_peer->item_ids_requested_from_peer->get<0>();\n          const item_hash_t& first_item_hash = blockchain_item_ids_inventory_message_received.item_hashes_available.front();\n\n          if (synopsis_sent_in_request.empty())\n          {\n            // if we sent an empty synopsis, we were asking for all blocks, so the first block should be block 1\n            if (_delegate->get_block_number(first_item_hash) != 1)\n            {\n              wlog(\"Invalid response from peer ${peer_endpoint}.  We requested a list of sync blocks starting from the beginning of the chain, \"\n                   \"but they provided a list of blocks starting with ${first_block}\",\n                   (\"peer_endpoint\", originating_peer->get_remote_endpoint())\n                   (\"first_block\", first_item_hash));\n              fc::exception error_for_peer(FC_LOG_MESSAGE(error, \"You gave an invalid response for my request for sync blocks.  I asked for blocks starting from the beginning of the chain, \"\n                                                          \"but you returned a list of blocks starting with ${first_block}\",  \n                                                          (\"first_block\", first_item_hash)));\n              disconnect_from_peer(originating_peer,\n                                   \"You gave an invalid response to my request for sync blocks\",\n                                   true, error_for_peer);\n              return;\n            }\n          }\n          else // synopsis was not empty, we expect a response building off one of the blocks we sent\n          {\n            if (boost::range::find(synopsis_sent_in_request, first_item_hash) == synopsis_sent_in_request.end())\n            {\n              wlog(\"Invalid response from peer ${peer_endpoint}.  We requested a list of sync blocks based on the synopsis ${synopsis}, but they \"\n                   \"provided a list of blocks starting with ${first_block}\",\n                   (\"peer_endpoint\", originating_peer->get_remote_endpoint())\n                   (\"synopsis\", synopsis_sent_in_request)\n                   (\"first_block\", first_item_hash));\n              fc::exception error_for_peer(FC_LOG_MESSAGE(error, \"You gave an invalid response for my request for sync blocks.  I asked for blocks following something in \"\n                                                          \"${synopsis}, but you returned a list of blocks starting with ${first_block} which wasn't one of your choices\",  \n                                                          (\"synopsis\", synopsis_sent_in_request)\n                                                          (\"first_block\", first_item_hash)));\n              disconnect_from_peer(originating_peer,\n                                   \"You gave an invalid response to my request for sync blocks\",\n                                   true, error_for_peer);\n              return;\n            }\n          }\n        }\n        originating_peer->item_ids_requested_from_peer.reset();\n\n        // if exceptions are throw after clearing the item_ids_requested_from_peer (above),\n        // it could leave our sync in a stalled state.  Wrap a try/catch around the rest\n        // of the function so we can log if this ever happens.\n        try\n        {\n          dlog( \"sync: received a list of ${count} available items from ${peer_endpoint}\",\n               ( \"count\", blockchain_item_ids_inventory_message_received.item_hashes_available.size() )\n               ( \"peer_endpoint\", originating_peer->get_remote_endpoint() ) );\n          //for( const item_hash_t& item_hash : blockchain_item_ids_inventory_message_received.item_hashes_available )\n          //{\n          //  dlog( \"sync:     ${hash}\", (\"hash\", item_hash ) );\n          //}\n\n          // if the peer doesn't have any items after the one we asked for\n          if( blockchain_item_ids_inventory_message_received.total_remaining_item_count == 0 &&\n              ( blockchain_item_ids_inventory_message_received.item_hashes_available.empty() || // there are no items in the peer's blockchain.  this should only happen if our blockchain was empty when we requested, might want to verify that.\n               ( blockchain_item_ids_inventory_message_received.item_hashes_available.size() == 1 &&\n                _delegate->has_item( item_id(blockchain_item_ids_inventory_message_received.item_type,\n                                            blockchain_item_ids_inventory_message_received.item_hashes_available.front() ) ) ) ) && // we've already seen the last item in the peer's blockchain\n              originating_peer->ids_of_items_to_get.empty() &&\n              originating_peer->number_of_unfetched_item_ids == 0 ) // <-- is the last check necessary?\n          {\n            dlog( \"sync: peer said we're up-to-date, entering normal operation with this peer\" );\n            originating_peer->we_need_sync_items_from_peer = false;\n\n            uint32_t new_number_of_unfetched_items = calculate_unsynced_block_count_from_all_peers();\n            _total_num_of_unfetched_items = new_number_of_unfetched_items;\n            if( new_number_of_unfetched_items == 0 )\n              _delegate->sync_status( blockchain_item_ids_inventory_message_received.item_type, 0 );\n\n            return;\n          }\n\n          std::deque<item_hash_t> item_hashes_received( blockchain_item_ids_inventory_message_received.item_hashes_available.begin(),\n                                                       blockchain_item_ids_inventory_message_received.item_hashes_available.end() );\n          originating_peer->number_of_unfetched_item_ids = blockchain_item_ids_inventory_message_received.total_remaining_item_count;\n          // flush any items this peer sent us that we've already received and processed from another peer\n          if (!item_hashes_received.empty() &&\n              originating_peer->ids_of_items_to_get.empty())\n          {\n            bool is_first_item_for_other_peer = false;\n            {\n               fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n               for (const peer_connection_ptr& peer : _active_connections)\n               {\n                  if (peer != originating_peer->shared_from_this() &&\n                        !peer->ids_of_items_to_get.empty() &&\n                        peer->ids_of_items_to_get.front() == blockchain_item_ids_inventory_message_received.item_hashes_available.front())\n                  {\n                     dlog(\"The item ${newitem} is the first item for peer ${peer}\",\n                           (\"newitem\", blockchain_item_ids_inventory_message_received.item_hashes_available.front())\n                           (\"peer\", peer->get_remote_endpoint()));\n                     is_first_item_for_other_peer = true;\n                     break;\n                  }\n               }\n            }\n            dlog(\"is_first_item_for_other_peer: ${is_first}.  item_hashes_received.size() = ${size}\",\n                 (\"is_first\", is_first_item_for_other_peer)(\"size\", item_hashes_received.size()));\n            if (!is_first_item_for_other_peer)\n            {\n              while (!item_hashes_received.empty() &&\n                     _delegate->has_item(item_id(blockchain_item_ids_inventory_message_received.item_type,\n                                                 item_hashes_received.front())))\n              {\n                assert(item_hashes_received.front() != item_hash_t());\n                originating_peer->last_block_delegate_has_seen = item_hashes_received.front();\n                originating_peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hashes_received.front());\n                dlog(\"popping item because delegate has already seen it.  peer ${peer}'s last block the delegate has seen is now ${block_id} (actual block #${actual_block_num})\",\n                     (\"peer\", originating_peer->get_remote_endpoint())\n                     (\"block_id\", originating_peer->last_block_delegate_has_seen)\n                     (\"actual_block_num\", _delegate->get_block_number(item_hashes_received.front())));\n\n                item_hashes_received.pop_front();\n              }\n              dlog(\"after removing all items we have already seen, item_hashes_received.size() = ${size}\", (\"size\", item_hashes_received.size()));\n            }\n          }\n          else if (!item_hashes_received.empty())\n          {\n            // we received a list of items and we already have a list of items to fetch from this peer.\n            // In the normal case, this list will immediately follow the existing list, meaning the\n            // last hash of our existing list will match the first hash of the new list.\n\n            // In the much less likely case, we've received a partial list of items from the peer, then\n            // the peer switched forks before sending us the remaining list.  In this case, the first\n            // hash in the new list may not be the last hash in the existing list (it may be earlier, or\n            // it may not exist at all.\n\n            // In either case, pop items off the back of our existing list until we find our first\n            // item, then append our list.\n            while (!originating_peer->ids_of_items_to_get.empty())\n            {\n              if (item_hashes_received.front() != originating_peer->ids_of_items_to_get.back())\n                originating_peer->ids_of_items_to_get.pop_back();\n              else\n                break;\n            }\n            if (originating_peer->ids_of_items_to_get.empty())\n            {\n              // this happens when the peer has switched forks between the last inventory message and\n              // this one, and there weren't any unfetched items in common\n              // We don't know where in the blockchain the new front() actually falls, all we can\n              // expect is that it is a block that we knew about because it should be one of the\n              // blocks we sent in the initial synopsis.\n              assert(_delegate->has_item(item_id(_sync_item_type, item_hashes_received.front())));\n              originating_peer->last_block_delegate_has_seen = item_hashes_received.front();\n              originating_peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hashes_received.front());\n              item_hashes_received.pop_front();\n            }\n            else\n            {\n              // the common simple case: the new list extends the old.  pop off the duplicate element\n              originating_peer->ids_of_items_to_get.pop_back();\n            }\n          }\n\n          if (!item_hashes_received.empty() && !originating_peer->ids_of_items_to_get.empty())\n            assert(item_hashes_received.front() != originating_peer->ids_of_items_to_get.back());\n\n          // at any given time, there's a maximum number of blocks that can possibly be out there\n          // [(now - genesis time) / block interval].  If they offer us more blocks than that,\n          // they must be an attacker or have a buggy client.\n          fc::time_point_sec minimum_time_of_last_offered_block =\n              originating_peer->last_block_time_delegate_has_seen + // timestamp of the block immediately before the first unfetched block\n              originating_peer->number_of_unfetched_item_ids * GRAPHENE_MIN_BLOCK_INTERVAL;\n          fc::time_point_sec now = fc::time_point::now();\n          if (minimum_time_of_last_offered_block > (now + GRAPHENE_NET_FUTURE_SYNC_BLOCKS_GRACE_PERIOD_SEC))\n          {\n            wlog(\"Disconnecting from peer ${peer} who offered us an implausible number of blocks, their last block would be in the future (${timestamp})\",\n                 (\"peer\", originating_peer->get_remote_endpoint())\n                 (\"timestamp\", minimum_time_of_last_offered_block));\n            fc::exception error_for_peer(FC_LOG_MESSAGE(error, \"You offered me a list of more sync blocks than could possibly exist.  Total blocks offered: ${blocks}, Minimum time of the last block you offered: ${minimum_time_of_last_offered_block}, Now: ${now}\",\n                                                        (\"blocks\", originating_peer->number_of_unfetched_item_ids)\n                                                        (\"minimum_time_of_last_offered_block\", minimum_time_of_last_offered_block)\n                                                        (\"now\", now)));\n            disconnect_from_peer(originating_peer,\n                                 \"You offered me a list of more sync blocks than could possibly exist\",\n                                 true, error_for_peer);\n            return;\n          }\n\n          // append the remaining items to the peer's list\n          boost::push_back(originating_peer->ids_of_items_to_get, item_hashes_received);\n\n          uint32_t new_number_of_unfetched_items = calculate_unsynced_block_count_from_all_peers();\n          if (new_number_of_unfetched_items != _total_num_of_unfetched_items)\n            _delegate->sync_status(blockchain_item_ids_inventory_message_received.item_type,\n                                   new_number_of_unfetched_items);\n          _total_num_of_unfetched_items = new_number_of_unfetched_items;\n\n          if (blockchain_item_ids_inventory_message_received.total_remaining_item_count != 0)\n          {\n            // the peer hasn't sent us all the items it knows about.\n            if (originating_peer->ids_of_items_to_get.size() > GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH)\n            {\n              // we have a good number of item ids from this peer, start fetching blocks from it;\n              // we'll switch back later to finish the job.\n              trigger_fetch_sync_items_loop();\n            }\n            else\n            {\n              // keep fetching the peer's list of sync items until we get enough to switch into block-\n              // fetchimg mode\n              fetch_next_batch_of_item_ids_from_peer(originating_peer);\n            }\n          }\n          else\n          {\n            // the peer has told us about all of the items it knows\n            if (!originating_peer->ids_of_items_to_get.empty())\n            {\n              // we now know about all of the items the peer knows about, and there are some items on the list\n              // that we should try to fetch.  Kick off the fetch loop.\n              trigger_fetch_sync_items_loop();\n            }\n            else\n            {\n              // If we get here, the peer has sent us a non-empty list of items, but we have already\n              // received all of the items from other peers.  Send a new request to the peer to\n              // see if we're really in sync\n              fetch_next_batch_of_item_ids_from_peer(originating_peer);\n            }\n          }\n        }\n        catch (const fc::canceled_exception&)\n        {\n          throw;\n        }\n        catch (const fc::exception& e)\n        {\n          elog(\"Caught unexpected exception: ${e}\", (\"e\", e));\n          assert(false && \"exceptions not expected here\");\n        }\n        catch (const std::exception& e)\n        {\n          elog(\"Caught unexpected exception: ${e}\", (\"e\", e.what()));\n          assert(false && \"exceptions not expected here\");\n        }\n        catch (...)\n        {\n          elog(\"Caught unexpected exception, could break sync operation\");\n        }\n      }\n      else\n      {\n        wlog( \"sync: received a list of sync items available from peer ${peer}, but I didn't ask for any!\",\n              (\"peer\", originating_peer->get_remote_endpoint()) );\n      }\n    }\n\n    graphene::net::message node_impl::get_message_for_item(const item_id& item)\n    {\n      try\n      {\n        return _message_cache.get_message(item.item_hash);\n      }\n      catch (fc::key_not_found_exception&)\n      {}\n      try\n      {\n        return _delegate->get_item(item);\n      }\n      catch (fc::key_not_found_exception&)\n      {}\n      return item_not_available_message(item);\n    }\n\n    void node_impl::on_fetch_items_message(peer_connection* originating_peer,\n                                           const fetch_items_message& fetch_items_message_received)\n    {\n      VERIFY_CORRECT_THREAD();\n      // Gatekeeping code\n      if( originating_peer->their_state != peer_connection::their_connection_state::connection_accepted )\n      {\n         wlog( \"Unexpected fetch_items_message from peer ${peer}, disconnecting\",\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         disconnect_from_peer( originating_peer, \"Received an unexpected fetch_items_message\" );\n         return;\n      }\n\n      dlog(\"received items request for ids ${ids} of type ${type} from peer ${endpoint}\",\n           (\"ids\", fetch_items_message_received.items_to_fetch)\n           (\"type\", fetch_items_message_received.item_type)\n           (\"endpoint\", originating_peer->get_remote_endpoint()));\n\n      fc::optional<message> last_block_message_sent;\n\n      std::list<message> reply_messages;\n      for (const item_hash_t& item_hash : fetch_items_message_received.items_to_fetch)\n      {\n        try\n        {\n          message requested_message = _message_cache.get_message(item_hash);\n          dlog(\"received item request for item ${id} from peer ${endpoint}, returning the item from my message cache\",\n               (\"endpoint\", originating_peer->get_remote_endpoint())\n               (\"id\", requested_message.id()));\n          reply_messages.push_back(requested_message);\n          if (fetch_items_message_received.item_type == block_message_type)\n            last_block_message_sent = requested_message;\n          continue;\n        }\n        catch (fc::key_not_found_exception&)\n        {\n           // it wasn't in our local cache, that's ok ask the client\n        }\n\n        item_id item_to_fetch(fetch_items_message_received.item_type, item_hash);\n        try\n        {\n          message requested_message = _delegate->get_item(item_to_fetch);\n          dlog(\"received item request from peer ${endpoint}, returning the item from delegate with id ${id} size ${size}\",\n               (\"id\", requested_message.id())\n               (\"size\", requested_message.size)\n               (\"endpoint\", originating_peer->get_remote_endpoint()));\n          reply_messages.push_back(requested_message);\n          if (fetch_items_message_received.item_type == block_message_type)\n            last_block_message_sent = requested_message;\n          continue;\n        }\n        catch (fc::key_not_found_exception&)\n        {\n          reply_messages.push_back(item_not_available_message(item_to_fetch));\n          dlog(\"received item request from peer ${endpoint} but we don't have it\",\n               (\"endpoint\", originating_peer->get_remote_endpoint()));\n        }\n      }\n\n      // if we sent them a block, update our record of the last block they've seen accordingly\n      if (last_block_message_sent)\n      {\n        graphene::net::block_message block = last_block_message_sent->as<graphene::net::block_message>();\n        originating_peer->last_block_delegate_has_seen = block.block_id;\n        originating_peer->last_block_time_delegate_has_seen = _delegate->get_block_time(block.block_id);\n      }\n\n      for (const message& reply : reply_messages)\n      {\n        if (reply.msg_type.value() == block_message_type)\n          originating_peer->send_item(item_id(block_message_type, reply.as<graphene::net::block_message>().block_id));\n        else\n          originating_peer->send_message(reply);\n      }\n    }\n\n    void node_impl::on_item_not_available_message( peer_connection* originating_peer, const item_not_available_message& item_not_available_message_received )\n    {\n      VERIFY_CORRECT_THREAD();\n      const item_id& requested_item = item_not_available_message_received.requested_item;\n      auto regular_item_iter = originating_peer->items_requested_from_peer.find(requested_item);\n      if (regular_item_iter != originating_peer->items_requested_from_peer.end())\n      {\n        originating_peer->items_requested_from_peer.erase( regular_item_iter );\n        originating_peer->inventory_peer_advertised_to_us.erase( requested_item );\n        if (is_item_in_any_peers_inventory(requested_item))\n        {\n          _items_to_fetch.insert(prioritized_item_id(requested_item, _items_to_fetch_seq_counter));\n          ++_items_to_fetch_seq_counter;\n        }\n        wlog( \"Peer ${peer} doesn't have the requested item ${item}.\",\n              (\"peer\", originating_peer->get_remote_endpoint())\n              (\"item\", requested_item) );\n        trigger_fetch_items_loop();\n        return;\n      }\n\n      auto sync_item_iter = originating_peer->sync_items_requested_from_peer.find(requested_item.item_hash);\n      if (sync_item_iter != originating_peer->sync_items_requested_from_peer.end())\n      {\n        _active_sync_requests.erase(*sync_item_iter);\n        originating_peer->sync_items_requested_from_peer.erase(sync_item_iter);\n\n        if (originating_peer->peer_needs_sync_items_from_us)\n          originating_peer->inhibit_fetching_sync_blocks = true;\n        else\n          disconnect_from_peer(originating_peer, \"You are missing a sync item you claim to have, your database is probably corrupted. Try --rebuild-index.\",true,\n                               fc::exception(FC_LOG_MESSAGE(error,\"You are missing a sync item you claim to have, your database is probably corrupted. Try --rebuild-index.\",\n                               (\"item_id\", requested_item))));\n        wlog( \"Peer ${peer} doesn't have the requested sync item ${item}.  This really shouldn't happen\",\n              (\"peer\", originating_peer->get_remote_endpoint())\n              (\"item\", requested_item) );\n        trigger_fetch_sync_items_loop();\n        return;\n      }\n\n      dlog(\"Peer doesn't have an item we're looking for, which is fine because we weren't looking for it\");\n    }\n\n    void node_impl::on_item_ids_inventory_message(peer_connection* originating_peer, const item_ids_inventory_message& item_ids_inventory_message_received)\n    {\n      VERIFY_CORRECT_THREAD();\n      // Gatekeeping code\n      if( originating_peer->their_state != peer_connection::their_connection_state::connection_accepted )\n      {\n         wlog( \"Unexpected item_ids_inventory_message from peer ${peer}, disconnecting\",\n               (\"peer\", originating_peer->get_remote_endpoint()) );\n         disconnect_from_peer( originating_peer, \"Received an unexpected item_ids_inventory_message\" );\n         return;\n      }\n\n      // expire old inventory\n      // so we'll be making our decisions about whether to fetch blocks below based only on recent inventory\n      originating_peer->clear_old_inventory();\n\n      dlog( \"received inventory of ${count} items from peer ${endpoint}\",\n            (\"count\", item_ids_inventory_message_received.item_hashes_available.size())\n            (\"endpoint\", originating_peer->get_remote_endpoint() ) );\n      for( const item_hash_t& item_hash : item_ids_inventory_message_received.item_hashes_available )\n      {\n        item_id advertised_item_id(item_ids_inventory_message_received.item_type, item_hash);\n        bool we_advertised_this_item_to_a_peer = false;\n        bool we_requested_this_item_from_a_peer = false;\n        {\n           fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n            for (const peer_connection_ptr& peer : _active_connections)\n            {\n               if (peer->inventory_advertised_to_peer.find(advertised_item_id) != peer->inventory_advertised_to_peer.end())\n               {\n                  we_advertised_this_item_to_a_peer = true;\n                  break;\n               }\n               if (peer->items_requested_from_peer.find(advertised_item_id) != peer->items_requested_from_peer.end())\n                  we_requested_this_item_from_a_peer = true;\n            }\n        }\n\n        // if we have already advertised it to a peer, we must have it, no need to do anything else\n        if (!we_advertised_this_item_to_a_peer)\n        {\n          // if the peer has flooded us with transactions, don't add these to the inventory to prevent our\n          // inventory list from growing without bound.  We try to allow fetching blocks even when\n          // we've stopped fetching transactions.\n          if ((item_ids_inventory_message_received.item_type == graphene::net::trx_message_type &&\n               originating_peer->is_inventory_advertised_to_us_list_full_for_transactions()) ||\n              originating_peer->is_inventory_advertised_to_us_list_full())\n            break;\n          originating_peer->inventory_peer_advertised_to_us.insert(peer_connection::timestamped_item_id(advertised_item_id, fc::time_point::now()));\n          if (!we_requested_this_item_from_a_peer)\n          {\n            if (_recently_failed_items.find(item_id(item_ids_inventory_message_received.item_type, item_hash)) != _recently_failed_items.end())\n            {\n              dlog(\"not adding ${item_hash} to our list of items to fetch because we've recently fetched a copy and it failed to push\",\n                   (\"item_hash\", item_hash));\n            }\n            else\n            {\n              auto items_to_fetch_iter = _items_to_fetch.get<item_id_index>().find(advertised_item_id);\n              if (items_to_fetch_iter == _items_to_fetch.get<item_id_index>().end())\n              {\n                // it's new to us\n                _items_to_fetch.insert(prioritized_item_id(advertised_item_id, _items_to_fetch_seq_counter));\n                ++_items_to_fetch_seq_counter;\n                dlog(\"adding item ${item_hash} from inventory message to our list of items to fetch\",\n                     (\"item_hash\", item_hash));\n                trigger_fetch_items_loop();\n              }\n              else\n              {\n                // another peer has told us about this item already, but this peer just told us it has the item\n                // too, we can expect it to be around in this peer's cache for longer, so update its timestamp\n                _items_to_fetch.get<item_id_index>().modify(items_to_fetch_iter,\n                                                            [](prioritized_item_id& item) { item.timestamp = fc::time_point::now(); });\n              }\n            }\n          }\n        }\n      }\n\n    }\n\n    void node_impl::on_closing_connection_message( peer_connection* originating_peer,\n          const closing_connection_message& closing_connection_message_received )\n    {\n      VERIFY_CORRECT_THREAD();\n      originating_peer->they_have_requested_close = true;\n\n      if( closing_connection_message_received.closing_due_to_error )\n      {\n        wlog( \"Peer ${peer} is disconnecting us because of an error: ${msg}, exception: ${error}\",\n             ( \"peer\", originating_peer->get_remote_endpoint() )\n             ( \"msg\", closing_connection_message_received.reason_for_closing )\n             ( \"error\", closing_connection_message_received.error ) );\n        std::ostringstream message;\n        message << \"Peer \" << fc::variant( originating_peer->get_remote_endpoint(),\n                                           GRAPHENE_NET_MAX_NESTED_OBJECTS ).as_string() <<\n                  \" disconnected us: \" << closing_connection_message_received.reason_for_closing;\n        fc::exception detailed_error(FC_LOG_MESSAGE(warn,\n              \"Peer ${peer} is disconnecting us because of an error: ${msg}, exception: ${error}\",\n              ( \"peer\", originating_peer->get_remote_endpoint() )\n              ( \"msg\", closing_connection_message_received.reason_for_closing )\n              ( \"error\", closing_connection_message_received.error ) ));\n        _delegate->error_encountered( message.str(),\n                                      detailed_error );\n      }\n      else\n      {\n        wlog( \"Peer ${peer} is disconnecting us because: ${msg}\",\n             ( \"peer\", originating_peer->get_remote_endpoint() )\n             ( \"msg\", closing_connection_message_received.reason_for_closing ) );\n      }\n      if( originating_peer->we_have_requested_close )\n        originating_peer->close_connection();\n    }\n\n    void node_impl::on_connection_closed(peer_connection* originating_peer)\n    {\n      VERIFY_CORRECT_THREAD();\n      peer_connection_ptr originating_peer_ptr = originating_peer->shared_from_this();\n      _rate_limiter.remove_tcp_socket( &originating_peer->get_socket() );\n\n      // if we closed the connection (due to timeout or handshake failure), we should have recorded an\n      // error message to store in the peer database when we closed the connection\n      fc::optional<fc::ip::endpoint> inbound_endpoint = originating_peer->get_endpoint_for_connecting();\n      if( originating_peer->connection_closed_error\n          && inbound_endpoint.valid() && inbound_endpoint->port() != 0 )\n      {\n        fc::optional<potential_peer_record> updated_peer_record\n              = _potential_peer_db.lookup_entry_for_endpoint(*inbound_endpoint);\n        if (updated_peer_record)\n        {\n          updated_peer_record->last_error = *originating_peer->connection_closed_error;\n          _potential_peer_db.update_entry(*updated_peer_record);\n        }\n      }\n\n      _closing_connections.erase(originating_peer_ptr);\n      _handshaking_connections.erase(originating_peer_ptr);\n      _terminating_connections.erase(originating_peer_ptr);\n      if (_active_connections.find(originating_peer_ptr) != _active_connections.end())\n      {\n        _active_connections.erase(originating_peer_ptr);\n\n        update_address_seen_time( this, originating_peer );\n      }\n\n      ilog(\"Remote peer ${endpoint} closed their connection to us\",\n           (\"endpoint\", originating_peer->get_remote_endpoint()));\n      display_current_connections();\n      trigger_p2p_network_connect_loop();\n\n      // notify the node delegate so it can update the display\n      if( _active_connections.size() != _last_reported_number_of_conns )\n      {\n        _last_reported_number_of_conns = (uint32_t)_active_connections.size();\n        _delegate->connection_count_changed( _last_reported_number_of_conns );\n      }\n\n      // if we had requested any sync or regular items from this peer that we haven't\n      // received yet, reschedule them to be fetched from another peer\n      if (!originating_peer->sync_items_requested_from_peer.empty())\n      {\n        for (auto sync_item : originating_peer->sync_items_requested_from_peer)\n          _active_sync_requests.erase(sync_item);\n        trigger_fetch_sync_items_loop();\n      }\n\n      if (!originating_peer->items_requested_from_peer.empty())\n      {\n        for (auto item_and_time : originating_peer->items_requested_from_peer)\n        {\n          if (is_item_in_any_peers_inventory(item_and_time.first))\n          {\n            _items_to_fetch.insert(prioritized_item_id(item_and_time.first, _items_to_fetch_seq_counter));\n            ++_items_to_fetch_seq_counter;\n          }\n        }\n        trigger_fetch_items_loop();\n      }\n\n      schedule_peer_for_deletion(originating_peer_ptr);\n    }\n\n    void node_impl::send_sync_block_to_node_delegate(const graphene::net::block_message& block_message_to_send)\n    {\n      dlog(\"in send_sync_block_to_node_delegate()\");\n      bool client_accepted_block = false;\n      bool discontinue_fetching_blocks_from_peer = false;\n\n      fc::oexception handle_message_exception;\n\n      try\n      {\n        std::vector<message_hash_type> contained_transaction_msg_ids;\n        _delegate->handle_block(block_message_to_send, true, contained_transaction_msg_ids);\n        dlog(\"Successfully pushed sync block ${num} (id:${id})\",\n             (\"num\", block_message_to_send.block.block_num())\n             (\"id\", block_message_to_send.block_id));\n        _most_recent_blocks_accepted.push_back(block_message_to_send.block_id);\n\n        client_accepted_block = true;\n      }\n      catch (const block_older_than_undo_history& e)\n      {\n        wlog(\"Failed to push sync block ${num} (id:${id}): block is on a fork older than our undo history would \"\n             \"allow us to switch to: ${e}\",\n             (\"num\", block_message_to_send.block.block_num())\n             (\"id\", block_message_to_send.block_id)\n             (\"e\", (fc::exception)e));\n        handle_message_exception = e;\n        discontinue_fetching_blocks_from_peer = true;\n      }\n      catch (const fc::canceled_exception&)\n      {\n        throw;\n      }\n      catch (const fc::exception& e)\n      {\n        auto block_num = block_message_to_send.block.block_num();\n        wlog(\"Failed to push sync block ${num} (id:${id}): client rejected sync block sent by peer: ${e}\",\n             (\"num\", block_num)\n             (\"id\", block_message_to_send.block_id)\n             (\"e\", e));\n        if( e.code() == block_timestamp_in_future_exception::code_enum::code_value )\n        {\n           handle_message_exception = block_timestamp_in_future_exception( FC_LOG_MESSAGE( warn, \"\",\n                (\"block_header\", static_cast<graphene::protocol::block_header>(block_message_to_send.block))\n                (\"block_num\", block_num)\n                (\"block_id\", block_message_to_send.block_id) ) );\n        }\n        else\n           handle_message_exception = e;\n      }\n\n      // build up lists for any potentially-blocking operations we need to do, then do them\n      // at the end of this function\n      std::set<peer_connection_ptr> peers_with_newly_empty_item_lists;\n      std::set<peer_connection_ptr> peers_we_need_to_sync_to;\n      std::map<peer_connection_ptr, std::pair<std::string, fc::oexception> > peers_to_disconnect; // map peer -> pair<reason_string, exception>\n\n      if( client_accepted_block )\n      {\n         --_total_num_of_unfetched_items;\n         dlog(\"sync: client accpted the block, we now have only ${count} items left to fetch before we're in sync\",\n               (\"count\", _total_num_of_unfetched_items));\n         bool is_fork_block = is_hard_fork_block(block_message_to_send.block.block_num());\n         {\n            fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n\n            for (const peer_connection_ptr& peer : _active_connections)\n            {\n               bool disconnecting_this_peer = false;\n               if (is_fork_block)\n               {\n                  // we just pushed a hard fork block.  Find out if this peer is running a client\n                  // that will be unable to process future blocks\n                  if (peer->last_known_fork_block_number != 0)\n                  {\n                     uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(peer->last_known_fork_block_number);\n                     if (next_fork_block_number != 0 &&\n                           next_fork_block_number <= block_message_to_send.block.block_num())\n                     {\n                        std::ostringstream disconnect_reason_stream;\n                        disconnect_reason_stream << \"You need to upgrade your client due to hard fork at block \" << block_message_to_send.block.block_num();\n                        peers_to_disconnect[peer] = std::make_pair(disconnect_reason_stream.str(),\n                              fc::oexception(fc::exception(FC_LOG_MESSAGE(error, \"You need to upgrade your client due to hard fork at block ${block_number}\",\n                              (\"block_number\", block_message_to_send.block.block_num())))));\n#ifdef ENABLE_DEBUG_ULOGS\n                        ulog(\"Disconnecting from peer during sync because their version is too old.  Their version date: ${date}\", (\"date\", peer->graphene_git_revision_unix_timestamp));\n#endif\n                        disconnecting_this_peer = true;\n                     }\n                  }\n               }\n               if (!disconnecting_this_peer &&\n                     peer->ids_of_items_to_get.empty() && peer->ids_of_items_being_processed.empty())\n               {\n                  dlog( \"Cannot pop first element off peer ${peer}'s list, its list is empty\", (\"peer\", peer->get_remote_endpoint() ) );\n                  // we don't know for sure that this peer has the item we just received.\n                  // If peer is still syncing to us, we know they will ask us for\n                  // sync item ids at least one more time and we'll notify them about\n                  // the item then, so there's no need to do anything.  If we still need items\n                  // from them, we'll be asking them for more items at some point, and\n                  // that will clue them in that they are out of sync.  If we're fully in sync\n                  // we need to kick off another round of synchronization with them so they can\n                  // find out about the new item.\n                  if (!peer->peer_needs_sync_items_from_us && !peer->we_need_sync_items_from_peer)\n                  {\n                     dlog(\"We will be restarting synchronization with peer ${peer}\", (\"peer\", peer->get_remote_endpoint()));\n                     peers_we_need_to_sync_to.insert(peer);\n                  }\n               }\n               else if (!disconnecting_this_peer)\n               {\n                  auto items_being_processed_iter = peer->ids_of_items_being_processed.find(block_message_to_send.block_id);\n                  if (items_being_processed_iter != peer->ids_of_items_being_processed.end())\n                  {\n                     peer->last_block_delegate_has_seen = block_message_to_send.block_id;\n                     peer->last_block_time_delegate_has_seen = block_message_to_send.block.timestamp;\n\n                     peer->ids_of_items_being_processed.erase(items_being_processed_iter);\n                     dlog(\"Removed item from ${endpoint}'s list of items being processed, still processing ${len} blocks\",\n                           (\"endpoint\", peer->get_remote_endpoint())(\"len\", peer->ids_of_items_being_processed.size()));\n\n                     // if we just received the last item in our list from this peer, we will want to\n                     // send another request to find out if we are in sync, but we can't do this yet\n                     // (we don't want to allow a fiber swap in the middle of popping items off the list)\n                     if (peer->ids_of_items_to_get.empty() &&\n                           peer->number_of_unfetched_item_ids == 0 &&\n                           peer->ids_of_items_being_processed.empty())\n                     peers_with_newly_empty_item_lists.insert(peer);\n\n                     // in this case, we know the peer was offering us this exact item, no need to\n                     // try to inform them of its existence\n                  }\n               }\n            } // for\n         } // lock_guard\n      }\n      else\n      {\n        // invalid message received\n        fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n        for (const peer_connection_ptr& peer : _active_connections)\n        {\n          if (peer->ids_of_items_being_processed.find(block_message_to_send.block_id)\n                 != peer->ids_of_items_being_processed.end())\n          {\n            if (discontinue_fetching_blocks_from_peer)\n            {\n              wlog(\"inhibiting fetching sync blocks from peer ${endpoint} because it is on a fork that's too old\",\n                   (\"endpoint\", peer->get_remote_endpoint()));\n              peer->inhibit_fetching_sync_blocks = true;\n            }\n            else\n              peers_to_disconnect[peer] = std::make_pair(\n                    std::string(\"You offered us a block that we reject as invalid\"),\n                    fc::oexception(handle_message_exception));\n          }\n        }\n      }\n\n      for (auto& peer_to_disconnect : peers_to_disconnect)\n      {\n        const peer_connection_ptr& peer = peer_to_disconnect.first;\n        std::string reason_string;\n        fc::oexception reason_exception;\n        std::tie(reason_string, reason_exception) = peer_to_disconnect.second;\n        wlog(\"disconnecting client ${endpoint} because it offered us the rejected block\",\n             (\"endpoint\", peer->get_remote_endpoint()));\n        disconnect_from_peer(peer.get(), reason_string, true, reason_exception);\n      }\n      for (const peer_connection_ptr& peer : peers_with_newly_empty_item_lists)\n        fetch_next_batch_of_item_ids_from_peer(peer.get());\n\n      for (const peer_connection_ptr& peer : peers_we_need_to_sync_to)\n        start_synchronizing_with_peer(peer);\n\n      dlog(\"Leaving send_sync_block_to_node_delegate\");\n\n      if (// _suspend_fetching_sync_blocks && <-- you can use this if\n                                               // \"max_blocks_to_handle_at_once\" == \"max_sync_blocks_to_prefetch\"\n          !_node_is_shutting_down &&\n          (!_process_backlog_of_sync_blocks_done.valid() || _process_backlog_of_sync_blocks_done.ready()))\n        _process_backlog_of_sync_blocks_done = fc::async([=](){ process_backlog_of_sync_blocks(); },\n                                                         \"process_backlog_of_sync_blocks\");\n    }\n\n    void node_impl::process_backlog_of_sync_blocks()\n    {\n      VERIFY_CORRECT_THREAD();\n      // garbage-collect the list of async tasks here for lack of a better place\n      for (auto calls_iter = _handle_message_calls_in_progress.begin();\n            calls_iter != _handle_message_calls_in_progress.end();)\n      {\n        if (calls_iter->ready())\n          calls_iter = _handle_message_calls_in_progress.erase(calls_iter);\n        else\n          ++calls_iter;\n      }\n\n      dlog(\"in process_backlog_of_sync_blocks\");\n      if (_handle_message_calls_in_progress.size() >= _max_blocks_to_handle_at_once)\n      {\n        dlog(\"leaving process_backlog_of_sync_blocks because we're already processing too many blocks\");\n        return; // we will be rescheduled when the next block finishes its processing\n      }\n      dlog(\"currently ${count} blocks in the process of being handled\", (\"count\", _handle_message_calls_in_progress.size()));\n\n\n      if (_suspend_fetching_sync_blocks)\n      {\n        dlog(\"resuming processing sync block backlog because we only ${count} blocks in progress\",\n             (\"count\", _handle_message_calls_in_progress.size()));\n        _suspend_fetching_sync_blocks = false;\n      }\n\n\n      // when syncing with multiple peers, it's possible that we'll have hundreds of blocks ready to push\n      // to the client at once.  This can be slow, and we need to limit the number we push at any given\n      // time to allow network traffic to continue so we don't end up disconnecting from peers\n      //fc::time_point start_time = fc::time_point::now();\n      //fc::time_point when_we_should_yield = start_time + fc::seconds(1);\n\n      bool block_processed_this_iteration;\n      size_t blocks_processed = 0;\n\n      std::set<peer_connection_ptr> peers_with_newly_empty_item_lists;\n      std::set<peer_connection_ptr> peers_we_need_to_sync_to;\n      std::map<peer_connection_ptr, fc::oexception> peers_with_rejected_block;\n\n      do\n      {\n        std::copy(std::make_move_iterator(_new_received_sync_items.begin()),\n                  std::make_move_iterator(_new_received_sync_items.end()),\n                  std::front_inserter(_received_sync_items));\n        _new_received_sync_items.clear();\n        dlog(\"currently ${count} sync items to consider\", (\"count\", _received_sync_items.size()));\n\n        block_processed_this_iteration = false;\n        for (auto received_block_iter = _received_sync_items.begin();\n             received_block_iter != _received_sync_items.end();\n             ++received_block_iter)\n        {\n\n          // find out if this block is the next block on the active chain or one of the forks\n          bool potential_first_block = false;\n          {\n            fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n            for (const peer_connection_ptr& peer : _active_connections)\n            {\n               if (!peer->ids_of_items_to_get.empty() &&\n                     peer->ids_of_items_to_get.front() == received_block_iter->block_id)\n               {\n                  potential_first_block = true;\n                  peer->ids_of_items_to_get.pop_front();\n                  peer->ids_of_items_being_processed.insert(received_block_iter->block_id);\n               }\n            }\n          }\n\n          // if it is, process it, remove it from all sync peers lists\n          if (potential_first_block)\n          {\n            // we can get into an interesting situation near the end of synchronization.  We can be in\n            // sync with one peer who is sending us the last block on the chain via a regular inventory\n            // message, while at the same time still be synchronizing with a peer who is sending us the\n            // block through the sync mechanism.  Further, we must request both blocks because\n            // we don't know they're the same (for the peer in normal operation, it has only told us the\n            // message id, for the peer in the sync case we only known the block_id).\n            if (std::find(_most_recent_blocks_accepted.begin(), _most_recent_blocks_accepted.end(),\n                          received_block_iter->block_id) == _most_recent_blocks_accepted.end())\n            {\n              graphene::net::block_message block_message_to_process = *received_block_iter;\n              _received_sync_items.erase(received_block_iter);\n              _handle_message_calls_in_progress.emplace_back(fc::async([this, block_message_to_process](){\n                send_sync_block_to_node_delegate(block_message_to_process);\n              }, \"send_sync_block_to_node_delegate\"));\n              ++blocks_processed;\n              block_processed_this_iteration = true;\n            }\n            else\n            {\n              dlog(\"Already received and accepted this block (presumably through normal inventory mechanism), treating it as accepted\");\n              std::vector< peer_connection_ptr > peers_needing_next_batch;\n              fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n              for (const peer_connection_ptr& peer : _active_connections)\n              {\n                auto items_being_processed_iter = peer->ids_of_items_being_processed.find(received_block_iter->block_id);\n                if (items_being_processed_iter != peer->ids_of_items_being_processed.end())\n                {\n                  peer->ids_of_items_being_processed.erase(items_being_processed_iter);\n                  dlog(\"Removed item from ${endpoint}'s list of items being processed, still processing ${len} blocks\",\n                       (\"endpoint\", peer->get_remote_endpoint())(\"len\", peer->ids_of_items_being_processed.size()));\n\n                  // if we just processed the last item in our list from this peer, we will want to\n                  // send another request to find out if we are now in sync (this is normally handled in\n                  // send_sync_block_to_node_delegate)\n                  if (peer->ids_of_items_to_get.empty() &&\n                      peer->number_of_unfetched_item_ids == 0 &&\n                      peer->ids_of_items_being_processed.empty())\n                  {\n                    dlog(\"We received last item in our list for peer ${endpoint}, setup to do a sync check\", (\"endpoint\", peer->get_remote_endpoint()));\n                    peers_needing_next_batch.push_back( peer );\n                  }\n                }\n              }\n              for( const peer_connection_ptr& peer : peers_needing_next_batch )\n                fetch_next_batch_of_item_ids_from_peer(peer.get());\n            }\n\n            break; // start iterating _received_sync_items from the beginning\n          } // end if potential_first_block\n        } // end for each block in _received_sync_items\n\n        if (_handle_message_calls_in_progress.size() >= _max_blocks_to_handle_at_once)\n        {\n          dlog(\"stopping processing sync block backlog because we have ${count} blocks in progress\",\n               (\"count\", _handle_message_calls_in_progress.size()));\n          //ulog(\"stopping processing sync block backlog because we have ${count} blocks in progress, total on hand: ${received}\",\n          //     (\"count\", _handle_message_calls_in_progress.size())(\"received\", _received_sync_items.size()));\n          if (_received_sync_items.size() >= _max_sync_blocks_to_prefetch)\n            _suspend_fetching_sync_blocks = true;\n          break;\n        }\n      } while (block_processed_this_iteration);\n\n      dlog(\"leaving process_backlog_of_sync_blocks, ${count} processed\", (\"count\", blocks_processed));\n\n      if (!_suspend_fetching_sync_blocks)\n        trigger_fetch_sync_items_loop();\n    }\n\n    void node_impl::trigger_process_backlog_of_sync_blocks()\n    {\n      if (!_node_is_shutting_down &&\n          (!_process_backlog_of_sync_blocks_done.valid() || _process_backlog_of_sync_blocks_done.ready()))\n        _process_backlog_of_sync_blocks_done = fc::async( [this](){ process_backlog_of_sync_blocks(); },\n                                                          \"process_backlog_of_sync_blocks\" );\n    }\n\n    void node_impl::process_block_during_syncing( peer_connection* originating_peer,\n                                               const graphene::net::block_message& block_message_to_process,\n                                               const message_hash_type& )\n    {\n      VERIFY_CORRECT_THREAD();\n      dlog( \"received a sync block from peer ${endpoint}\", (\"endpoint\", originating_peer->get_remote_endpoint() ) );\n\n      // add it to the front of _received_sync_items, then process _received_sync_items to try to\n      // pass as many messages as possible to the client.\n      _new_received_sync_items.push_front( block_message_to_process );\n      trigger_process_backlog_of_sync_blocks();\n    }\n\n    void node_impl::process_block_when_in_sync( peer_connection* originating_peer,\n                                               const graphene::net::block_message& block_message_to_process,\n                                               const message_hash_type& message_hash )\n    {\n      fc::time_point message_receive_time = fc::time_point::now();\n\n      dlog( \"received a block from peer ${endpoint}, passing it to client\",\n            (\"endpoint\", originating_peer->get_remote_endpoint() ) );\n      std::set<peer_connection_ptr> peers_to_disconnect;\n      std::string disconnect_reason;\n      fc::oexception disconnect_exception;\n      fc::oexception restart_sync_exception;\n      try\n      {\n        // we can get into an intersting situation near the end of synchronization.  We can be in\n        // sync with one peer who is sending us the last block on the chain via a regular inventory\n        // message, while at the same time still be synchronizing with a peer who is sending us the\n        // block through the sync mechanism.  Further, we must request both blocks because\n        // we don't know they're the same (for the peer in normal operation, it has only told us the\n        // message id, for the peer in the sync case we only known the block_id).\n        fc::time_point message_validated_time;\n        if (std::find(_most_recent_blocks_accepted.begin(), _most_recent_blocks_accepted.end(),\n                      block_message_to_process.block_id) == _most_recent_blocks_accepted.end())\n        {\n          std::vector<message_hash_type> contained_transaction_msg_ids;\n          _delegate->handle_block(block_message_to_process, false, contained_transaction_msg_ids);\n          message_validated_time = fc::time_point::now();\n          dlog(\"Successfully pushed block ${num} (id:${id})\",\n                (\"num\", block_message_to_process.block.block_num())\n                (\"id\", block_message_to_process.block_id));\n          _most_recent_blocks_accepted.push_back(block_message_to_process.block_id);\n\n          bool new_transaction_discovered = false;\n          for (const item_hash_t& transaction_message_hash : contained_transaction_msg_ids)\n          {\n            /*size_t items_erased =*/\n            _items_to_fetch.get<item_id_index>().erase(item_id(trx_message_type, transaction_message_hash));\n            // there are two ways we could behave here: we could either act as if we received\n            // the transaction outside the block and offer it to our peers, or we could just\n            // forget about it (we would still advertise this block to our peers so they should\n            // get the transaction through that mechanism).\n            // We take the second approach, bring in the next if block to try the first approach\n            //if (items_erased)\n            //{\n            //  new_transaction_discovered = true;\n            //  _new_inventory.insert(item_id(trx_message_type, transaction_message_hash));\n            //}\n          }\n          if (new_transaction_discovered)\n            trigger_advertise_inventory_loop();\n        }\n        else\n          dlog( \"Already received and accepted this block (presumably through sync mechanism), treating it as accepted\" );\n\n        dlog( \"client validated the block, advertising it to other peers\" );\n\n        item_id block_message_item_id(core_message_type_enum::block_message_type, message_hash);\n        uint32_t block_number = block_message_to_process.block.block_num();\n        fc::time_point_sec block_time = block_message_to_process.block.timestamp;\n        {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& peer : _active_connections)\n         {\n            auto iter = peer->inventory_peer_advertised_to_us.find(block_message_item_id);\n            if (iter != peer->inventory_peer_advertised_to_us.end())\n            {\n               // this peer offered us the item.  It will eventually expire from the peer's\n               // inventory_peer_advertised_to_us list after some time has passed (currently 2 minutes).\n               // For now, it will remain there, which will prevent us from offering the peer this\n               // block back when we rebroadcast the block below\n               peer->last_block_delegate_has_seen = block_message_to_process.block_id;\n               peer->last_block_time_delegate_has_seen = block_time;\n            }\n            peer->clear_old_inventory();\n         }\n        }\n        message_propagation_data propagation_data { message_receive_time, message_validated_time,\n                                                    originating_peer->node_id };\n        broadcast( block_message_to_process, propagation_data );\n        _message_cache.block_accepted();\n\n        if (is_hard_fork_block(block_number))\n        {\n          // we just pushed a hard fork block.  Find out if any of our peers are running clients\n          // that will be unable to process future blocks\n          fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n          for (const peer_connection_ptr& peer : _active_connections)\n          {\n            if (peer->last_known_fork_block_number != 0)\n            {\n              uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(peer->last_known_fork_block_number);\n              if (next_fork_block_number != 0 &&\n                  next_fork_block_number <= block_number)\n              {\n                peers_to_disconnect.insert(peer);\n#ifdef ENABLE_DEBUG_ULOGS\n                ulog(\"Disconnecting from peer because their version is too old.  Their version date: ${date}\", (\"date\", peer->graphene_git_revision_unix_timestamp));\n#endif\n              }\n            }\n          }\n          if (!peers_to_disconnect.empty())\n          {\n            std::ostringstream disconnect_reason_stream;\n            disconnect_reason_stream << \"You need to upgrade your client due to hard fork at block \" << block_number;\n            disconnect_reason = disconnect_reason_stream.str();\n            disconnect_exception = fc::exception(FC_LOG_MESSAGE(error, \"You need to upgrade your client due to hard fork at block ${block_number}\",\n                                                                (\"block_number\", block_number)));\n          }\n        }\n      }\n      catch (const fc::canceled_exception&)\n      {\n        throw;\n      }\n      catch (const unlinkable_block_exception& e)\n      {\n        restart_sync_exception = e;\n      }\n      catch (const fc::exception& e)\n      {\n        // client rejected the block.  Disconnect the client and any other clients that offered us this block\n        auto block_num = block_message_to_process.block.block_num();\n        wlog(\"Failed to push block ${num} (id:${id}), client rejected block sent by peer: ${e}\",\n              (\"num\", block_num)\n              (\"id\", block_message_to_process.block_id)\n              (\"e\",e));\n\n        if( e.code() == block_timestamp_in_future_exception::code_enum::code_value )\n        {\n           disconnect_exception = block_timestamp_in_future_exception( FC_LOG_MESSAGE( warn, \"\",\n                (\"block_header\", static_cast<graphene::protocol::block_header>(block_message_to_process.block))\n                (\"block_num\", block_num)\n                (\"block_id\", block_message_to_process.block_id) ) );\n        }\n        else\n           disconnect_exception = e;\n        disconnect_reason = \"You offered me a block that I have deemed to be invalid\";\n\n        peers_to_disconnect.insert( originating_peer->shared_from_this() );\n        fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n        for (const peer_connection_ptr& peer : _active_connections)\n          if (!peer->ids_of_items_to_get.empty() && peer->ids_of_items_to_get.front() == block_message_to_process.block_id)\n            peers_to_disconnect.insert(peer);\n      }\n\n      if (restart_sync_exception)\n      {\n        wlog(\"Peer ${peer} sent me a block that didn't link to our blockchain.  Restarting sync mode with them to get the missing block. \"\n             \"Error pushing block was: ${e}\",\n             (\"peer\", originating_peer->get_remote_endpoint())\n             (\"e\", *restart_sync_exception));\n        start_synchronizing_with_peer(originating_peer->shared_from_this());\n      }\n\n      for (const peer_connection_ptr& peer : peers_to_disconnect)\n      {\n        wlog(\"disconnecting client ${endpoint} because it offered us the rejected block\",\n             (\"endpoint\", peer->get_remote_endpoint()));\n        disconnect_from_peer(peer.get(), disconnect_reason, true, *disconnect_exception);\n      }\n    }\n    void node_impl::process_block_message(peer_connection* originating_peer,\n                                          const message& message_to_process,\n                                          const message_hash_type& message_hash)\n    {\n      VERIFY_CORRECT_THREAD();\n      // find out whether we requested this item while we were synchronizing or during normal operation\n      // (it's possible that we request an item during normal operation and then get kicked into sync\n      // mode before we receive and process the item.  In that case, we should process the item as a normal\n      // item to avoid confusing the sync code)\n      graphene::net::block_message block_message_to_process(message_to_process.as<graphene::net::block_message>());\n      auto item_iter = originating_peer->items_requested_from_peer.find(\n                             item_id(graphene::net::block_message_type, message_hash));\n      if (item_iter != originating_peer->items_requested_from_peer.end())\n      {\n        originating_peer->items_requested_from_peer.erase(item_iter);\n        process_block_when_in_sync(originating_peer, block_message_to_process, message_hash);\n        if (originating_peer->idle())\n          trigger_fetch_items_loop();\n        return;\n      }\n      else\n      {\n        // not during normal operation.  see if we requested it during sync\n        auto sync_item_iter = originating_peer->sync_items_requested_from_peer.find( block_message_to_process.block_id);\n        if (sync_item_iter != originating_peer->sync_items_requested_from_peer.end())\n        {\n          originating_peer->sync_items_requested_from_peer.erase(sync_item_iter);\n          // if exceptions are throw here after removing the sync item from the list (above),\n          // it could leave our sync in a stalled state.  Wrap a try/catch around the rest\n          // of the function so we can log if this ever happens.\n          try\n          {\n            originating_peer->last_sync_item_received_time = fc::time_point::now();\n            _active_sync_requests.erase(block_message_to_process.block_id);\n            process_block_during_syncing(originating_peer, block_message_to_process, message_hash);\n            if (originating_peer->idle())\n            {\n              // we have finished fetching a batch of items, so we either need to grab another batch of items\n              // or we need to get another list of item ids.\n              if (originating_peer->number_of_unfetched_item_ids > 0 &&\n                  originating_peer->ids_of_items_to_get.size() < GRAPHENE_NET_MIN_BLOCK_IDS_TO_PREFETCH)\n                fetch_next_batch_of_item_ids_from_peer(originating_peer);\n              else\n                trigger_fetch_sync_items_loop();\n            }\n            return;\n          }\n          catch (const fc::canceled_exception& e)\n          {\n            throw;\n          }\n          catch (const fc::exception& e)\n          {\n            elog(\"Caught unexpected exception: ${e}\", (\"e\", e));\n            assert(false && \"exceptions not expected here\");\n          }\n          catch (const std::exception& e)\n          {\n            elog(\"Caught unexpected exception: ${e}\", (\"e\", e.what()));\n            assert(false && \"exceptions not expected here\");\n          }\n          catch (...)\n          {\n            elog(\"Caught unexpected exception, could break sync operation\");\n          }\n        }\n      }\n\n      // if we get here, we didn't request the message, we must have a misbehaving peer\n      wlog(\"received a block ${block_id} I didn't ask for from peer ${endpoint}, disconnecting from peer\",\n           (\"endpoint\", originating_peer->get_remote_endpoint())\n           (\"block_id\", block_message_to_process.block_id));\n      fc::exception detailed_error(FC_LOG_MESSAGE(error, \"You sent me a block that I didn't ask for, block_id: ${block_id}\",\n                                                  (\"block_id\", block_message_to_process.block_id)\n                                                  (\"graphene_git_revision_sha\", originating_peer->graphene_git_revision_sha)\n                                                  (\"graphene_git_revision_unix_timestamp\", originating_peer->graphene_git_revision_unix_timestamp)\n                                                  (\"fc_git_revision_sha\", originating_peer->fc_git_revision_sha)\n                                                  (\"fc_git_revision_unix_timestamp\", originating_peer->fc_git_revision_unix_timestamp)));\n      disconnect_from_peer(originating_peer, \"You sent me a block that I didn't ask for\", true, detailed_error);\n    }\n\n    void node_impl::on_current_time_request_message(peer_connection* originating_peer,\n                                                    const current_time_request_message& current_time_request_message_received)\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::time_point request_received_time(fc::time_point::now());\n      current_time_reply_message reply(current_time_request_message_received.request_sent_time,\n                                       request_received_time);\n      originating_peer->send_message(reply, offsetof(current_time_reply_message, reply_transmitted_time));\n    }\n\n    void node_impl::on_current_time_reply_message(peer_connection* originating_peer,\n                                                  const current_time_reply_message& current_time_reply_message_received)\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::time_point reply_received_time = fc::time_point::now();\n      constexpr uint8_t two = 2;\n      originating_peer->clock_offset = fc::microseconds( ( (current_time_reply_message_received.request_received_time\n                                                            - current_time_reply_message_received.request_sent_time)\n                                                         + (current_time_reply_message_received.reply_transmitted_time\n                                                            - reply_received_time) ).count() / two );\n      originating_peer->round_trip_delay = ( reply_received_time\n                                             - current_time_reply_message_received.request_sent_time )\n                                         - ( current_time_reply_message_received.reply_transmitted_time\n                                             - current_time_reply_message_received.request_received_time );\n    }\n\n    // this handles any message we get that doesn't require any special processing.\n    // currently, this is any message other than block messages and p2p-specific\n    // messages.  (transaction messages would be handled here, for example)\n    // this just passes the message to the client, and does the bookkeeping\n    // related to requesting and rebroadcasting the message.\n    void node_impl::process_ordinary_message( peer_connection* originating_peer,\n                                              const message& message_to_process,\n                                              const message_hash_type& message_hash )\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::time_point message_receive_time = fc::time_point::now();\n\n      // only process it if we asked for it\n      auto iter = originating_peer->items_requested_from_peer.find(\n                        item_id(message_to_process.msg_type.value(), message_hash) );\n      if( iter == originating_peer->items_requested_from_peer.end() )\n      {\n        wlog( \"received a message I didn't ask for from peer ${endpoint}, disconnecting from peer\",\n             ( \"endpoint\", originating_peer->get_remote_endpoint() ) );\n        fc::exception detailed_error( FC_LOG_MESSAGE(error,\n                            \"You sent me a message that I didn't ask for, message_hash: ${message_hash}\",\n                            ( \"message_hash\", message_hash ) ) );\n        disconnect_from_peer( originating_peer, \"You sent me a message that I didn't request\", true, detailed_error );\n        return;\n      }\n      else\n      {\n        originating_peer->items_requested_from_peer.erase( iter );\n        if (originating_peer->idle())\n          trigger_fetch_items_loop();\n\n        // Next: have the delegate process the message\n        fc::time_point message_validated_time;\n        try\n        {\n          if (message_to_process.msg_type.value() == trx_message_type)\n          {\n            trx_message transaction_message_to_process = message_to_process.as<trx_message>();\n            dlog( \"passing message containing transaction ${trx} to client\",\n                  (\"trx\", transaction_message_to_process.trx.id()) );\n            _delegate->handle_transaction(transaction_message_to_process);\n          }\n          else\n            _delegate->handle_message( message_to_process );\n          message_validated_time = fc::time_point::now();\n        }\n        catch ( const fc::canceled_exception& )\n        {\n          throw;\n        }\n        catch ( const fc::exception& e )\n        {\n          switch( e.code() )\n          {\n          // log common exceptions in debug level\n          case graphene::chain::duplicate_transaction::code_enum::code_value :\n          case graphene::chain::limit_order_create_kill_unfilled::code_enum::code_value :\n          case graphene::chain::limit_order_create_market_not_whitelisted::code_enum::code_value :\n          case graphene::chain::limit_order_create_market_blacklisted::code_enum::code_value :\n          case graphene::chain::limit_order_create_selling_asset_unauthorized::code_enum::code_value :\n          case graphene::chain::limit_order_create_receiving_asset_unauthorized::code_enum::code_value :\n          case graphene::chain::limit_order_create_insufficient_balance::code_enum::code_value :\n          case graphene::chain::limit_order_update_nonexist_order::code_enum::code_value :\n          case graphene::chain::limit_order_update_owner_mismatch::code_enum::code_value :\n          case graphene::chain::limit_order_cancel_nonexist_order::code_enum::code_value :\n          case graphene::chain::limit_order_cancel_owner_mismatch::code_enum::code_value :\n          case graphene::chain::liquidity_pool_exchange_unfillable_price::code_enum::code_value :\n             dlog( \"client rejected message sent by peer ${peer}, ${e}\",\n                   (\"peer\", originating_peer->get_remote_endpoint() )(\"e\", e) );\n             break;\n          // log rarer exceptions in warn level\n          default:\n             wlog( \"client rejected message sent by peer ${peer}, ${e}\",\n                   (\"peer\", originating_peer->get_remote_endpoint() )(\"e\", e) );\n             break;\n          }\n          // record it so we don't try to fetch this item again\n          _recently_failed_items.insert( peer_connection::timestamped_item_id(\n                item_id( message_to_process.msg_type.value(), message_hash ), fc::time_point::now() ) );\n          return;\n        }\n\n        // finally, if the delegate validated the message, broadcast it to our other peers\n        message_propagation_data propagation_data { message_receive_time, message_validated_time,\n                                                    originating_peer->node_id };\n        broadcast( message_to_process, propagation_data );\n      }\n    }\n\n    void node_impl::start_synchronizing_with_peer( const peer_connection_ptr& peer )\n    {\n      VERIFY_CORRECT_THREAD();\n      peer->ids_of_items_to_get.clear();\n      peer->number_of_unfetched_item_ids = 0;\n      peer->we_need_sync_items_from_peer = true;\n      peer->last_block_delegate_has_seen = item_hash_t();\n      peer->last_block_time_delegate_has_seen = _delegate->get_block_time(item_hash_t());\n      peer->inhibit_fetching_sync_blocks = false;\n      fetch_next_batch_of_item_ids_from_peer( peer.get() );\n    }\n\n    void node_impl::start_synchronizing()\n    {\n      fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n      for( const peer_connection_ptr& peer : _active_connections )\n        start_synchronizing_with_peer( peer );\n    }\n\n    void node_impl::new_peer_just_added( const peer_connection_ptr& peer )\n    {\n      VERIFY_CORRECT_THREAD();\n      peer->send_message(current_time_request_message(),\n                         offsetof(current_time_request_message, request_sent_time));\n      start_synchronizing_with_peer( peer );\n      if( _active_connections.size() != _last_reported_number_of_conns )\n      {\n        _last_reported_number_of_conns = (uint32_t)_active_connections.size();\n        _delegate->connection_count_changed( _last_reported_number_of_conns );\n      }\n      // If it is an inbound connection, try to verify its inbound endpoint\n      if( peer_connection_direction::inbound == peer->direction )\n      {\n         for( const auto& potential_inbound_endpoint : peer->potential_inbound_endpoints )\n            _add_once_node_list.emplace_back( potential_inbound_endpoint.first );\n      }\n    }\n\n    void node_impl::close()\n    {\n      VERIFY_CORRECT_THREAD();\n\n      try\n      {\n        _potential_peer_db.close();\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while closing P2P peer database, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while closing P2P peer database, ignoring\" );\n      }\n\n      // First, stop accepting incoming network connections\n      try\n      {\n        _tcp_server.close();\n        dlog(\"P2P TCP server closed\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while closing P2P TCP server, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while closing P2P TCP server, ignoring\" );\n      }\n\n      try\n      {\n        _accept_loop_complete.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"P2P accept loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating P2P accept loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating P2P accept loop, ignoring\" );\n      }\n\n      // terminate all of our long-running loops (these run continuously instead of rescheduling themselves)\n      try\n      {\n        _p2p_network_connect_loop_done.cancel(\"node_impl::close()\");\n        // cancel() is currently broken, so we need to wake up the task to allow it to finish\n        trigger_p2p_network_connect_loop();\n        _p2p_network_connect_loop_done.wait();\n        dlog(\"P2P connect loop terminated\");\n      }\n      catch ( const fc::canceled_exception& )\n      {\n        dlog(\"P2P connect loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating P2P connect loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating P2P connect loop, ignoring\" );\n      }\n\n      try\n      {\n        _process_backlog_of_sync_blocks_done.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"Process backlog of sync items task terminated\");\n      }\n      catch ( const fc::canceled_exception& )\n      {\n        dlog(\"Process backlog of sync items task terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Process backlog of sync items task, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Process backlog of sync items task, ignoring\" );\n      }\n\n      size_t handle_message_call_count = 0;\n      while( true )\n      {\n        auto it = _handle_message_calls_in_progress.begin();\n        if( it == _handle_message_calls_in_progress.end() )\n           break;\n        if( it->ready() || it->error() || it->canceled() )\n        {\n           _handle_message_calls_in_progress.erase( it );\n           continue;\n        }\n        ++handle_message_call_count;\n        try\n        {\n          it->cancel_and_wait(\"node_impl::close()\");\n          dlog(\"handle_message call #${count} task terminated\", (\"count\", handle_message_call_count));\n        }\n        catch ( const fc::canceled_exception& )\n        {\n          dlog(\"handle_message call #${count} task terminated\", (\"count\", handle_message_call_count));\n        }\n        catch ( const fc::exception& e )\n        {\n          wlog(\"Exception thrown while terminating handle_message call #${count} task, ignoring: ${e}\", (\"e\", e)(\"count\", handle_message_call_count));\n        }\n        catch (...)\n        {\n          wlog(\"Exception thrown while terminating handle_message call #${count} task, ignoring\",(\"count\", handle_message_call_count));\n        }\n      }\n\n      try\n      {\n        _fetch_sync_items_loop_done.cancel(\"node_impl::close()\");\n        // cancel() is currently broken, so we need to wake up the task to allow it to finish\n        trigger_fetch_sync_items_loop();\n        _fetch_sync_items_loop_done.wait();\n        dlog(\"Fetch sync items loop terminated\");\n      }\n      catch ( const fc::canceled_exception& )\n      {\n        dlog(\"Fetch sync items loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Fetch sync items loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Fetch sync items loop, ignoring\" );\n      }\n\n      try\n      {\n        _fetch_item_loop_done.cancel(\"node_impl::close()\");\n        // cancel() is currently broken, so we need to wake up the task to allow it to finish\n        trigger_fetch_items_loop();\n        _fetch_item_loop_done.wait();\n        dlog(\"Fetch items loop terminated\");\n      }\n      catch ( const fc::canceled_exception& )\n      {\n        dlog(\"Fetch items loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Fetch items loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Fetch items loop, ignoring\" );\n      }\n\n      try\n      {\n        _advertise_inventory_loop_done.cancel(\"node_impl::close()\");\n        // cancel() is currently broken, so we need to wake up the task to allow it to finish\n        trigger_advertise_inventory_loop();\n        _advertise_inventory_loop_done.wait();\n        dlog(\"Advertise inventory loop terminated\");\n      }\n      catch ( const fc::canceled_exception& )\n      {\n        dlog(\"Advertise inventory loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Advertise inventory loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Advertise inventory loop, ignoring\" );\n      }\n\n\n      // Next, terminate our existing connections.  First, close all of the connections nicely.\n      // This will close the sockets and may result in calls to our \"on_connection_closing\"\n      // method to inform us that the connection really closed (or may not if we manage to cancel\n      // the read loop before it gets an EOF).\n      // operate off copies of the lists in case they change during iteration\n      std::list<peer_connection_ptr> all_peers;\n      auto p_back = [&all_peers](const peer_connection_ptr& conn) { all_peers.push_back(conn); };\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         std::for_each(_active_connections.begin(), _active_connections.end(), p_back);\n      }\n      {\n         fc::scoped_lock<fc::mutex> lock(_handshaking_connections.get_mutex());\n         std::for_each(_handshaking_connections.begin(), _handshaking_connections.end(), p_back);\n      }\n      {\n         fc::scoped_lock<fc::mutex> lock(_closing_connections.get_mutex());\n         std::for_each(_closing_connections.begin(), _closing_connections.end(), p_back);\n      }\n\n      for (const peer_connection_ptr& peer : all_peers)\n      {\n        try\n        {\n          peer->destroy_connection();\n        }\n        catch ( const fc::exception& e )\n        {\n          wlog( \"Exception thrown while closing peer connection, ignoring: ${e}\", (\"e\", e) );\n        }\n        catch (...)\n        {\n          wlog( \"Exception thrown while closing peer connection, ignoring\" );\n        }\n      }\n\n      // and delete all of the peer_connection objects\n      _active_connections.clear();\n      _handshaking_connections.clear();\n      _closing_connections.clear();\n      all_peers.clear();\n\n      {\n#ifdef USE_PEERS_TO_DELETE_MUTEX\n        fc::scoped_lock<fc::mutex> lock(_peers_to_delete_mutex);\n#endif\n        try\n        {\n          _delayed_peer_deletion_task_done.cancel_and_wait(\"node_impl::close()\");\n          dlog(\"Delayed peer deletion task terminated\");\n        }\n        catch ( const fc::exception& e )\n        {\n          wlog( \"Exception thrown while terminating Delayed peer deletion task, ignoring: ${e}\", (\"e\", e) );\n        }\n        catch (...)\n        {\n          wlog( \"Exception thrown while terminating Delayed peer deletion task, ignoring\" );\n        }\n        _peers_to_delete.clear();\n      }\n\n      // Now that there are no more peers that can call methods on us, there should be no\n      // chance for one of our loops to be rescheduled, so we can safely terminate all of\n      // our loops now\n      try\n      {\n        _kill_inactive_conns_loop_done.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"Kill inactive connections loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Terminate inactive connections loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Terminate inactive connections loop, ignoring\" );\n      }\n\n      try\n      {\n        _fetch_updated_peer_lists_loop_done.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"Fetch updated peer lists loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Fetch updated peer lists loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Fetch updated peer lists loop, ignoring\" );\n      }\n\n      try\n      {\n        _update_seed_nodes_loop_done.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"Update seed nodes loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Update seed nodes loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Update seed nodes loop, ignoring\" );\n      }\n\n      try\n      {\n        _bandwidth_monitor_loop_done.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"Bandwidth monitor loop terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Bandwidth monitor loop, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Bandwidth monitor loop, ignoring\" );\n      }\n\n      try\n      {\n        _dump_node_status_task_done.cancel_and_wait(\"node_impl::close()\");\n        dlog(\"Dump node status task terminated\");\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"Exception thrown while terminating Dump node status task, ignoring: ${e}\", (\"e\", e) );\n      }\n      catch (...)\n      {\n        wlog( \"Exception thrown while terminating Dump node status task, ignoring\" );\n      }\n    } // node_impl::close()\n\n    void node_impl::accept_connection_task( peer_connection_ptr new_peer )\n    {\n      VERIFY_CORRECT_THREAD();\n      new_peer->accept_connection(); // this blocks until the secure connection is fully negotiated\n      send_hello_message(new_peer);\n    }\n\n    void node_impl::accept_loop()\n    {\n      VERIFY_CORRECT_THREAD();\n      while ( !_accept_loop_complete.canceled() )\n      {\n        peer_connection_ptr new_peer(peer_connection::make_shared(this));\n\n        try\n        {\n          _tcp_server.accept( new_peer->get_socket() );\n          ilog( \"accepted inbound connection from ${remote_endpoint}\",\n                (\"remote_endpoint\", new_peer->get_socket().remote_endpoint() ) );\n          if (_node_is_shutting_down)\n            return;\n          new_peer->connection_initiation_time = fc::time_point::now();\n          _handshaking_connections.insert( new_peer );\n          _rate_limiter.add_tcp_socket( &new_peer->get_socket() );\n          std::weak_ptr<peer_connection> new_weak_peer(new_peer);\n          new_peer->accept_or_connect_task_done = fc::async( [this, new_weak_peer]() {\n            peer_connection_ptr new_peer(new_weak_peer.lock());\n            assert(new_peer);\n            if (!new_peer)\n              return;\n            accept_connection_task(new_peer);\n          }, \"accept_connection_task\" );\n\n          // limit the rate at which we accept connections to mitigate DOS attacks\n          fc::usleep( fc::milliseconds(10) );\n        } FC_CAPTURE_AND_LOG( (0) ) // GCOVR_EXCL_LINE\n      }\n    } // accept_loop()\n\n    void node_impl::send_hello_message(const peer_connection_ptr& peer)\n    {\n      VERIFY_CORRECT_THREAD();\n      peer->negotiation_status = peer_connection::connection_negotiation_status::hello_sent;\n\n      fc::sha256::encoder shared_secret_encoder;\n      fc::sha512 shared_secret = peer->get_shared_secret();\n      shared_secret_encoder.write(shared_secret.data(), sizeof(shared_secret));\n      fc::ecc::compact_signature signature\n            = _node_configuration.private_key.sign_compact(shared_secret_encoder.result());\n\n      // In the hello messsage, we send three things:\n      // * inbound IP address\n      // * inbound port\n      // * outbound port\n      //\n      // If we don't accept incoming connections, we send nothing.\n      //\n      // The peer we're connecting to may assume we're firewalled if the\n      // IP address and outbound port we send don't match the values it sees on its remote endpoint,\n      // but it is not always true, E.G. if the peer itself is behind a reverse proxy.\n      //\n      // Note: we no longer perform remote firewall check (ask the peer to check whether we are firewalled),\n      //       thus we don't know our external IP address,\n      //       nor we know whether we're behind NAT or a reverse proxy that will allow incoming connections.\n      //       However, if the \"p2p-inbound-endpoint\" node startup option is configured, we send that instead.\n\n      fc::ip::address inbound_address; // default 0.0.0.0\n      uint16_t inbound_port = 0;\n      uint16_t outbound_port = 0;\n      if( _node_configuration.accept_incoming_connections )\n      {\n         fc::ip::endpoint local_endpoint = peer->get_socket().local_endpoint();\n         inbound_address = local_endpoint.get_address();\n         inbound_port = _actual_listening_endpoint.port();\n         outbound_port = local_endpoint.port();\n         if( _node_configuration.inbound_endpoint.valid() )\n         {\n            if( _node_configuration.inbound_endpoint->get_address() != fc::ip::address() )\n               inbound_address = _node_configuration.inbound_endpoint->get_address();\n            inbound_port = _node_configuration.inbound_endpoint->port();\n         }\n      }\n\n      hello_message hello(_user_agent_string,\n                          core_protocol_version,\n                          inbound_address,\n                          inbound_port,\n                          outbound_port,\n                          _node_public_key,\n                          signature,\n                          _chain_id,\n                          generate_hello_user_data());\n\n      peer->send_message(message(hello));\n    }\n\n    void node_impl::connect_to_task(peer_connection_ptr new_peer,\n                                    const fc::ip::endpoint& remote_endpoint)\n    {\n      VERIFY_CORRECT_THREAD();\n\n      // create or find the database entry for the new peer\n      auto updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_ep(remote_endpoint);\n      updated_peer_record.last_connection_disposition = last_connection_failed;\n      updated_peer_record.last_connection_attempt_time = fc::time_point::now();;\n      _potential_peer_db.update_entry(updated_peer_record);\n\n      fc::oexception connect_failed_exception;\n\n      try\n      {\n        ilog(\"Connecting to peer ${peer}\", (\"peer\", remote_endpoint));\n        // blocks until the connection is established and secure connection is negotiated\n        auto bind_to_endpoint = _node_configuration.accept_incoming_connections ? _actual_listening_endpoint\n                                                                                : fc::optional<fc::ip::endpoint>();\n        new_peer->connect_to( remote_endpoint, bind_to_endpoint );\n\n        // we connected to the peer.  guess they're not firewalled....\n        new_peer->is_firewalled = firewalled_state::not_firewalled;\n\n        // connection succeeded, we've started handshaking.  record that in our database\n        updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_ep(remote_endpoint);\n        updated_peer_record.last_connection_disposition = last_connection_handshaking_failed;\n        updated_peer_record.number_of_successful_connection_attempts++;\n        updated_peer_record.last_seen_time = fc::time_point::now();\n        _potential_peer_db.update_entry(updated_peer_record);\n      }\n      catch (const fc::exception& except)\n      {\n        connect_failed_exception = except;\n      }\n\n      if( connect_failed_exception )\n      {\n        // connection failed.  record that in our database\n        updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_ep(remote_endpoint);\n        updated_peer_record.last_connection_disposition = last_connection_failed;\n        updated_peer_record.number_of_failed_connection_attempts++;\n        if (new_peer->connection_closed_error)\n          updated_peer_record.last_error = *new_peer->connection_closed_error;\n        else\n          updated_peer_record.last_error = *connect_failed_exception;\n        _potential_peer_db.update_entry(updated_peer_record);\n\n        // If this is for inbound endpoint verification,\n        // here we could try to find the original connection and update its firewalled state,\n        // but it doesn't seem necessary.\n\n        // if the connection failed, we want to disconnect now.\n        _handshaking_connections.erase(new_peer);\n        _terminating_connections.erase(new_peer);\n        _active_connections.erase(new_peer);\n        _closing_connections.erase(new_peer);\n\n        display_current_connections();\n        trigger_p2p_network_connect_loop();\n        schedule_peer_for_deletion(new_peer);\n\n        throw *connect_failed_exception;\n      }\n      else\n      {\n        // connection was successful and we want to stay connected\n        fc::ip::endpoint local_endpoint = new_peer->get_local_endpoint();\n        new_peer->inbound_address = local_endpoint.get_address();\n        new_peer->inbound_port = _node_configuration.accept_incoming_connections ? _actual_listening_endpoint.port()\n                                                                                 : 0;\n        new_peer->outbound_port = local_endpoint.port();\n\n        new_peer->our_state = peer_connection::our_connection_state::just_connected;\n        new_peer->their_state = peer_connection::their_connection_state::just_connected;\n        send_hello_message(new_peer);\n        ilog(\"Sent \\\"hello\\\" to peer ${peer}\", (\"peer\", new_peer->get_remote_endpoint()));\n      }\n    }\n\n    // methods implementing node's public interface\n    void node_impl::set_node_delegate(std::shared_ptr<node_delegate> del, fc::thread* thread_for_delegate_calls)\n    {\n      VERIFY_CORRECT_THREAD();\n      _delegate.reset();\n      if (del)\n        _delegate = std::make_unique<statistics_gathering_node_delegate_wrapper>(del, thread_for_delegate_calls);\n      if( _delegate )\n        _chain_id = del->get_chain_id();\n    }\n\n    void node_impl::load_configuration( const fc::path& configuration_directory )\n    {\n      VERIFY_CORRECT_THREAD();\n      _node_configuration_directory = configuration_directory;\n      fc::path configuration_file_name( _node_configuration_directory / NODE_CONFIGURATION_FILENAME );\n      bool node_configuration_loaded = false;\n      if( fc::exists(configuration_file_name ) )\n      {\n        try\n        {\n          _node_configuration = fc::json::from_file( configuration_file_name ).as<detail::node_configuration>(GRAPHENE_NET_MAX_NESTED_OBJECTS);\n          ilog( \"Loaded configuration from file ${filename}\", (\"filename\", configuration_file_name ) );\n\n          if( _node_configuration.private_key == fc::ecc::private_key() )\n            _node_configuration.private_key = fc::ecc::private_key::generate();\n\n          node_configuration_loaded = true;\n        }\n        catch ( fc::parse_error_exception& parse_error )\n        {\n          elog( \"malformed node configuration file ${filename}: ${error}\",\n               ( \"filename\", configuration_file_name )(\"error\", parse_error.to_detail_string() ) );\n        }\n        catch ( fc::exception& except )\n        {\n          elog( \"unexpected exception while reading configuration file ${filename}: ${error}\",\n               ( \"filename\", configuration_file_name )(\"error\", except.to_detail_string() ) );\n        }\n      }\n\n      if( !node_configuration_loaded )\n      {\n        _node_configuration = detail::node_configuration();\n\n#ifdef GRAPHENE_TEST_NETWORK\n        uint32_t port = GRAPHENE_NET_TEST_P2P_PORT;\n#else\n        uint32_t port = GRAPHENE_NET_DEFAULT_P2P_PORT;\n#endif\n        _node_configuration.listen_endpoint.set_port( port );\n\n        ilog( \"generating new private key for this node\" );\n        _node_configuration.private_key = fc::ecc::private_key::generate();\n        save_node_configuration();\n      }\n\n      _node_public_key = _node_configuration.private_key.get_public_key().serialize();\n\n      fc::path potential_peer_database_file_name(_node_configuration_directory / POTENTIAL_PEER_DATABASE_FILENAME);\n      try\n      {\n        _potential_peer_db.open(potential_peer_database_file_name);\n\n        // push back the time on all peers loaded from the database so we will be able to retry them immediately\n        // Note: this step is almost useless because we didn't multiply _peer_connection_retry_timeout\n        //       by number_of_failed_connection_attempts. However, it is probably desired as we don't want\n        //       to try to connect to a large number of dead nodes at startup.\n        //       As of writing, _peer_connection_retry_timeout is 30 seconds, pushing the time back that much\n        //       won't have much impact in production.\n        //       TODO Perhaps just remove it.\n        for (peer_database::iterator itr = _potential_peer_db.begin(); itr != _potential_peer_db.end(); ++itr)\n        {\n          potential_peer_record updated_peer_record = *itr;\n          updated_peer_record.last_connection_attempt_time = std::min<fc::time_point_sec>(updated_peer_record.last_connection_attempt_time,\n                                                                                          fc::time_point::now() - fc::seconds(_peer_connection_retry_timeout));\n          _potential_peer_db.update_entry(updated_peer_record);\n        }\n\n        trigger_p2p_network_connect_loop();\n      }\n      catch (fc::exception& except)\n      {\n        elog(\"unable to open peer database ${filename}: ${error}\",\n             (\"filename\", potential_peer_database_file_name)(\"error\", except.to_detail_string()));\n        throw;\n      }\n    }\n\n    void node_impl::listen_to_p2p_network()\n    {\n      VERIFY_CORRECT_THREAD();\n      if (!_node_configuration.accept_incoming_connections)\n      {\n        wlog(\"accept_incoming_connections is false, p2p network will not accept any incoming connections\");\n        return;\n      }\n\n      assert(_node_public_key != fc::ecc::public_key_data());\n\n      fc::ip::endpoint listen_endpoint = _node_configuration.listen_endpoint;\n      if( listen_endpoint.port() != 0 )\n      {\n        // if the user specified a port, we only want to bind to it if it's not already\n        // being used by another application.  During normal operation, we set the\n        // SO_REUSEADDR/SO_REUSEPORT flags so that we can bind outbound sockets to the\n        // same local endpoint as we're listening on here.  On some platforms, setting\n        // those flags will prevent us from detecting that other applications are\n        // listening on that port.  We'd like to detect that, so we'll set up a temporary\n        // tcp server without that flag to see if we can listen on that port.\n        // Note: There is a race condition where another application may start listening\n        //       on the same port just after the temporary tcp server is destroyed and\n        //       before we try to listen with the real tcp server.\n        //       This happens frequently when running multiple test cases at the same\n        //       time, but less likely in production.\n        bool first = true;\n        for( ;; )\n        {\n          bool listen_failed = false;\n\n          try\n          {\n            fc::tcp_server temporary_server;\n            if( listen_endpoint.get_address() != fc::ip::address() )\n              temporary_server.listen( listen_endpoint );\n            else\n              temporary_server.listen( listen_endpoint.port() );\n            break;\n          }\n          catch ( const fc::exception&)\n          {\n            listen_failed = true;\n          }\n\n          if (listen_failed)\n          {\n            if( _node_configuration.wait_if_endpoint_is_busy )\n            {\n              std::ostringstream error_message_stream;\n              if( first )\n              {\n                error_message_stream << \"Unable to listen for connections on port \" << listen_endpoint.port()\n                                     << \", retrying in a few seconds\\n\";\n                error_message_stream << \"You can wait for it to become available, or restart this program using\\n\";\n                error_message_stream << \"the --p2p-endpoint option to specify another port\\n\";\n                first = false;\n              }\n              else\n              {\n                error_message_stream << \"\\nStill waiting for port \" << listen_endpoint.port() << \" to become available\\n\";\n              }\n              std::string error_message = error_message_stream.str();\n              wlog(error_message);\n              std::cout << \"\\033[31m\" << error_message;  \n              _delegate->error_encountered( error_message, fc::oexception() );\n              fc::usleep( fc::seconds(5 ) );\n            }\n            else // don't wait, just find a random port\n            {\n              wlog( \"unable to bind on the requested endpoint ${endpoint}, which probably means that endpoint is already in use\",\n                   ( \"endpoint\", listen_endpoint ) );\n              listen_endpoint.set_port( 0 );\n            }\n          } // if (listen_failed)\n        } // for(;;)\n      } // if (listen_endpoint.port() != 0)\n      else // port is 0\n      {\n        // if they requested a random port, we'll just assume it's available\n        // (it may not be due to ip address, but we'll detect that in the next step)\n      }\n\n      _tcp_server.set_reuse_address();\n      try\n      {\n        if( listen_endpoint.get_address() != fc::ip::address() )\n          _tcp_server.listen( listen_endpoint );\n        else\n          _tcp_server.listen( listen_endpoint.port() );\n        _actual_listening_endpoint = _tcp_server.get_local_endpoint();\n        ilog( \"listening for connections on endpoint ${endpoint} (our first choice)\",\n              ( \"endpoint\", _actual_listening_endpoint ) );\n      }\n      catch ( fc::exception& e )\n      {\n        FC_RETHROW_EXCEPTION( e, error, \"unable to listen on ${endpoint}\", (\"endpoint\",listen_endpoint ) );\n      }\n    }\n\n    void node_impl::connect_to_p2p_network(node_impl_ptr self)\n    {\n      VERIFY_CORRECT_THREAD();\n      assert(_node_public_key != fc::ecc::public_key_data());\n\n      assert(!_accept_loop_complete.valid() &&\n             !_p2p_network_connect_loop_done.valid() &&\n             !_update_seed_nodes_loop_done.valid() &&\n             !_fetch_sync_items_loop_done.valid() &&\n             !_fetch_item_loop_done.valid() &&\n             !_advertise_inventory_loop_done.valid() &&\n             !_kill_inactive_conns_loop_done.valid() &&\n             !_fetch_updated_peer_lists_loop_done.valid() &&\n             !_bandwidth_monitor_loop_done.valid() &&\n             !_dump_node_status_task_done.valid());\n      if (_node_configuration.accept_incoming_connections)\n        _accept_loop_complete = fc::async( [this](){ accept_loop(); }, \"accept_loop\");\n\n      _p2p_network_connect_loop_done = fc::async( [this]() { p2p_network_connect_loop(); },\n                                                  \"p2p_network_connect_loop\" );\n      _fetch_sync_items_loop_done = fc::async( [this]() { fetch_sync_items_loop(); }, \"fetch_sync_items_loop\" );\n      _fetch_item_loop_done = fc::async( [this]() { fetch_items_loop(); }, \"fetch_items_loop\" );\n      _advertise_inventory_loop_done = fc::async( [this]() { advertise_inventory_loop(); },\n                                                  \"advertise_inventory_loop\" );\n      _kill_inactive_conns_loop_done = fc::async( [this,self]() { kill_inactive_conns_loop(self); },\n                                                  \"kill_inactive_conns_loop\" );\n      _fetch_updated_peer_lists_loop_done = fc::async([this](){ fetch_updated_peer_lists_loop(); },\n                                                                \"fetch_updated_peer_lists_loop\");\n      _bandwidth_monitor_loop_done = fc::async([this](){ bandwidth_monitor_loop(); }, \"bandwidth_monitor_loop\");\n      _dump_node_status_task_done = fc::async([this](){ dump_node_status_task(); }, \"dump_node_status_task\");\n      schedule_next_update_seed_nodes_task();\n    }\n\n    void node_impl::add_node(const fc::ip::endpoint& ep)\n    {\n      VERIFY_CORRECT_THREAD();\n      // if we're connecting to them, we believe they're not firewalled\n      auto updated_peer_record = _potential_peer_db.lookup_or_create_entry_for_ep(ep);\n\n      // if we've recently connected to this peer, reset the last_connection_attempt_time to allow\n      // us to immediately retry this peer\n      // Note: to make it work, we need to multiply _peer_connection_retry_timeout\n      //         by number_of_failed_connection_attempts.\n      //       However, this step is almost useless because we will immediately try to connect anyway\n      //         due to _add_once_node_list.\n      //       On the other hand, if we connected to the peer already but it was not in the peer database somehow,\n      //         this step makes sure that it will be added to the peer database.\n      auto delay_until_retry = fc::seconds( (updated_peer_record.number_of_failed_connection_attempts + 1)\n                                            * _peer_connection_retry_timeout );\n      updated_peer_record.last_connection_attempt_time\n            = std::min<fc::time_point_sec>( updated_peer_record.last_connection_attempt_time,\n                                            fc::time_point::now() - delay_until_retry );\n      _add_once_node_list.push_back(updated_peer_record);\n      _potential_peer_db.update_entry(updated_peer_record);\n      trigger_p2p_network_connect_loop();\n    }\n\n   void node_impl::add_seed_node(const std::string& endpoint_string)\n   {\n      VERIFY_CORRECT_THREAD();\n      _seed_nodes.insert( endpoint_string );\n      resolve_seed_node_and_add( endpoint_string );\n   }\n\n   /**\n    * @brief Helper to convert a string to a collection of endpoints\n    *\n    * This converts a string (i.e. \"bitshares.eu:665535\") to a collection of endpoints.\n    * NOTE: Throws an exception if not in correct format or was unable to resolve URL.\n    *\n    * @param in the incoming string\n    * @returns a vector of endpoints\n    */\n   static std::vector<fc::ip::endpoint> resolve_string_to_ip_endpoints(const std::string& in)\n   {\n      try\n      {\n         std::string::size_type colon_pos = in.find(':');\n         if (colon_pos == std::string::npos)\n            FC_THROW(\"Missing required port number in endpoint string \\\"${endpoint_string}\\\"\",\n                  (\"endpoint_string\", in));\n         std::string port_string = in.substr(colon_pos + 1);\n         try\n         {\n            uint16_t port = boost::lexical_cast<uint16_t>(port_string);\n\n            std::string hostname = in.substr(0, colon_pos);\n            std::vector<fc::ip::endpoint> endpoints = fc::resolve(hostname, port);\n            if (endpoints.empty())\n               FC_THROW_EXCEPTION( fc::unknown_host_exception,\n                     \"The host name can not be resolved: ${hostname}\",\n                     (\"hostname\", hostname) );\n            return endpoints;\n         }\n         catch (const boost::bad_lexical_cast&)\n         {\n            FC_THROW(\"Bad port: ${port}\", (\"port\", port_string));\n         }\n      }\n      FC_CAPTURE_AND_RETHROW((in)) // GCOVR_EXCL_LINE\n   }\n\n   void node_impl::resolve_seed_node_and_add(const std::string& endpoint_string)\n   {\n      VERIFY_CORRECT_THREAD();\n      std::vector<fc::ip::endpoint> endpoints;\n      ilog(\"Resolving seed node ${endpoint}\", (\"endpoint\", endpoint_string));\n      try\n      {\n         endpoints = resolve_string_to_ip_endpoints(endpoint_string);\n      }\n      catch(...)\n      {\n         wlog( \"Unable to resolve endpoint during attempt to add seed node ${ep}\", (\"ep\", endpoint_string) );\n      }\n      for (const fc::ip::endpoint& endpoint : endpoints)\n      {\n         ilog(\"Adding seed node ${endpoint}\", (\"endpoint\", endpoint));\n         add_node(endpoint);\n      }\n   }\n\n    void node_impl::initiate_connect_to(const peer_connection_ptr& new_peer)\n    {\n      new_peer->get_socket().open();\n      new_peer->get_socket().set_reuse_address();\n      new_peer->connection_initiation_time = fc::time_point::now();\n      _handshaking_connections.insert(new_peer);\n      _rate_limiter.add_tcp_socket(&new_peer->get_socket());\n\n      if (_node_is_shutting_down)\n        return;\n\n      std::weak_ptr<peer_connection> new_weak_peer(new_peer);\n      new_peer->accept_or_connect_task_done = fc::async([this, new_weak_peer](){\n        peer_connection_ptr new_peer(new_weak_peer.lock());\n        assert(new_peer);\n        if (!new_peer)\n          return;\n        connect_to_task(new_peer, *new_peer->get_remote_endpoint());\n      }, \"connect_to_task\");\n    }\n\n    void node_impl::connect_to_endpoint(const fc::ip::endpoint& remote_endpoint)\n    {\n      VERIFY_CORRECT_THREAD();\n      if( is_connected_to_endpoint(remote_endpoint) )\n        FC_THROW_EXCEPTION(already_connected_to_requested_peer, \"already connected to requested endpoint ${endpoint}\",\n                           (\"endpoint\", remote_endpoint));\n\n      dlog(\"node_impl::connect_to_endpoint(${endpoint})\", (\"endpoint\", remote_endpoint));\n      peer_connection_ptr new_peer(peer_connection::make_shared(this));\n      new_peer->set_remote_endpoint(remote_endpoint);\n      initiate_connect_to(new_peer);\n    }\n\n   peer_connection_ptr node_impl::get_active_conn_for_endpoint( const fc::ip::endpoint& remote_endpoint ) const\n   {\n      VERIFY_CORRECT_THREAD();\n      fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n      for( const peer_connection_ptr& active_peer : _active_connections )\n      {\n         // Note: for outbound connections, checking by remote_endpoint is OK,\n         //         and we will ignore the inbound address and port it sends to us when handshaking.\n         //       For an inbound active connection, we want to verify its inbound endpoint, if it happens to be\n         //         the same as remote_endpoint but not yet verified, we consider it as not connected.\n         //       * If verification succeeds, we will mark it as \"verified\" and won't try to connect again.\n         //       * We may fail to verify if it is firewalled, in this case number_of_failed_connection_attempts\n         //         will increase, so we will not reconnect soon, but will wait longer and longer.\n         fc::optional<fc::ip::endpoint> endpoint_for_this_peer = active_peer->get_remote_endpoint();\n         if( peer_connection_direction::outbound == active_peer->direction\n             && endpoint_for_this_peer && *endpoint_for_this_peer == remote_endpoint )\n            return active_peer;\n         // Note: if it is an inbound connection and its inbound endpoint is verified already,\n         //       the inbound endpoint should be in additional_inbound_endpoints\n         if( active_peer->additional_inbound_endpoints.find( remote_endpoint )\n             != active_peer->additional_inbound_endpoints.end() )\n            return active_peer;\n      }\n      return peer_connection_ptr();\n   }\n\n   peer_connection_ptr node_impl::get_connection_for_endpoint( const fc::ip::endpoint& remote_endpoint ) const\n   {\n      VERIFY_CORRECT_THREAD();\n      peer_connection_ptr active_ptr = get_active_conn_for_endpoint( remote_endpoint );\n      if ( active_ptr != peer_connection_ptr() )\n         return active_ptr;\n      fc::scoped_lock<fc::mutex> lock(_handshaking_connections.get_mutex());\n      for( const peer_connection_ptr& handshaking_peer : _handshaking_connections )\n      {\n         // For an inbound handshaking connection, there is a race condition since we might not know its node_id yet,\n         // so be stricter here.\n         // Even so, there may be situations that we end up having multiple active connections with them.\n         fc::optional<fc::ip::endpoint> endpoint_for_this_peer = handshaking_peer->get_remote_endpoint();\n         if( endpoint_for_this_peer && *endpoint_for_this_peer == remote_endpoint )\n            return handshaking_peer;\n         // Note: if it is an inbound connection and its inbound endpoint is verified already,\n         //       the inbound endpoint should be in additional_inbound_endpoints\n         if( handshaking_peer->additional_inbound_endpoints.find( remote_endpoint )\n             != handshaking_peer->additional_inbound_endpoints.end() )\n            return handshaking_peer;\n      }\n      return peer_connection_ptr();\n   }\n\n    bool node_impl::is_connected_to_endpoint( const fc::ip::endpoint& remote_endpoint ) const\n    {\n      VERIFY_CORRECT_THREAD();\n      return get_connection_for_endpoint( remote_endpoint ) != peer_connection_ptr();\n    }\n\n    void node_impl::move_peer_to_active_list(const peer_connection_ptr& peer)\n    {\n      VERIFY_CORRECT_THREAD();\n      _active_connections.insert(peer);\n      _handshaking_connections.erase(peer);\n      _closing_connections.erase(peer);\n      _terminating_connections.erase(peer);\n    }\n\n    void node_impl::move_peer_to_closing_list(const peer_connection_ptr& peer)\n    {\n      VERIFY_CORRECT_THREAD();\n      _active_connections.erase(peer);\n      _handshaking_connections.erase(peer);\n      _closing_connections.insert(peer);\n      _terminating_connections.erase(peer);\n    }\n\n    void node_impl::move_peer_to_terminating_list(const peer_connection_ptr& peer)\n    {\n      VERIFY_CORRECT_THREAD();\n      _active_connections.erase(peer);\n      _handshaking_connections.erase(peer);\n      _closing_connections.erase(peer);\n      _terminating_connections.insert(peer);\n    }\n\n    void node_impl::dump_node_status()\n    {\n      VERIFY_CORRECT_THREAD();\n      ilog( \"----------------- PEER STATUS UPDATE --------------------\" );\n      ilog( \" number of peers: ${active} active, ${handshaking} handshaking, ${closing} closing. \"\n            \" attempting to maintain ${desired} - ${maximum} peers\",\n           ( \"active\", _active_connections.size() )(\"handshaking\", _handshaking_connections.size() )\n           ( \"closing\", _closing_connections.size() )\n           ( \"desired\", _desired_number_of_connections )(\"maximum\", _maximum_number_of_connections ) );\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for( const peer_connection_ptr& peer : _active_connections )\n         {\n            ilog( \"       active peer ${endpoint} [${direction}] (${inbound_ep} ${is_firewalled}) \"\n                  \"peer_is_in_sync_with_us:${in_sync_with_us} we_are_in_sync_with_peer:${in_sync_with_them}\",\n                  ( \"endpoint\", peer->get_remote_endpoint() )\n                  ( \"direction\", peer->direction )\n                  ( \"inbound_ep\", peer->get_endpoint_for_connecting() )\n                  ( \"is_firewalled\", peer->is_firewalled)\n                  ( \"in_sync_with_us\", !peer->peer_needs_sync_items_from_us )\n                  ( \"in_sync_with_them\", !peer->we_need_sync_items_from_peer ) );\n            if( peer->we_need_sync_items_from_peer )\n               ilog( \"              above peer has ${count} sync items we might need\",\n                     (\"count\", peer->ids_of_items_to_get.size() ) );\n            if (peer->inhibit_fetching_sync_blocks)\n               ilog( \"              we are not fetching sync blocks from the above peer \"\n                     \"(inhibit_fetching_sync_blocks == true)\" );\n\n         }\n      }\n      {\n         fc::scoped_lock<fc::mutex> lock(_handshaking_connections.get_mutex());\n         for( const peer_connection_ptr& peer : _handshaking_connections )\n         {\n            ilog( \"  handshaking peer ${endpoint} [${direction}] in state ours(${our_state}) theirs(${their_state})\",\n                  ( \"endpoint\", peer->get_remote_endpoint() )\n                  ( \"direction\", peer->direction )\n                  ( \"our_state\", peer->our_state )( \"their_state\", peer->their_state ) );\n         }\n      }\n      ilog( \"--------- MEMORY USAGE ------------\" );\n      ilog( \"node._active_sync_requests size: ${size}\", (\"size\", _active_sync_requests.size() ) );\n      ilog( \"node._received_sync_items size: ${size}\", (\"size\", _received_sync_items.size() ) );\n      ilog( \"node._new_received_sync_items size: ${size}\", (\"size\", _new_received_sync_items.size() ) );\n      ilog( \"node._items_to_fetch size: ${size}\", (\"size\", _items_to_fetch.size() ) );\n      ilog( \"node._new_inventory size: ${size}\", (\"size\", _new_inventory.size() ) );\n      ilog( \"node._message_cache size: ${size}\", (\"size\", _message_cache.size() ) );\n      fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n      for( const peer_connection_ptr& peer : _active_connections )\n      {\n        ilog( \"  peer ${endpoint}\", (\"endpoint\", peer->get_remote_endpoint() ) );\n        ilog( \"    peer.ids_of_items_to_get size: ${size}\", (\"size\", peer->ids_of_items_to_get.size() ) );\n        ilog( \"    peer.inventory_peer_advertised_to_us size: ${size}\", (\"size\", peer->inventory_peer_advertised_to_us.size() ) );\n        ilog( \"    peer.inventory_advertised_to_peer size: ${size}\", (\"size\", peer->inventory_advertised_to_peer.size() ) );\n        ilog( \"    peer.items_requested_from_peer size: ${size}\", (\"size\", peer->items_requested_from_peer.size() ) );\n        ilog( \"    peer.sync_items_requested_from_peer size: ${size}\", (\"size\", peer->sync_items_requested_from_peer.size() ) );\n      }\n      ilog( \"--------- END MEMORY USAGE ------------\" );\n    }\n\n    void node_impl::disconnect_from_peer( peer_connection* peer_to_disconnect,\n                                          const std::string& reason_for_disconnect,\n                                          bool caused_by_error /* = false */,\n                                          const fc::oexception& error /* = fc::oexception() */ )\n    {\n      VERIFY_CORRECT_THREAD();\n      move_peer_to_closing_list(peer_to_disconnect->shared_from_this());\n\n      if (peer_to_disconnect->they_have_requested_close)\n      {\n        // the peer has already told us that it's ready to close the connection, so just close the connection\n        peer_to_disconnect->close_connection();\n      }\n      else if( peer_to_disconnect->we_have_requested_close )\n      {\n         dlog( \"Disconnecting again from ${peer} for ${reason}, ignore\",\n              (\"peer\",peer_to_disconnect->get_remote_endpoint()) (\"reason\",reason_for_disconnect));\n         return;\n      }\n      else\n      {\n        // we're the first to try to want to close the connection\n        fc::optional<fc::ip::endpoint> inbound_endpoint = peer_to_disconnect->get_endpoint_for_connecting();\n        if( inbound_endpoint.valid() && inbound_endpoint->port() != 0 )\n        {\n          fc::optional<potential_peer_record> updated_peer_record\n                = _potential_peer_db.lookup_entry_for_endpoint(*inbound_endpoint);\n          if (updated_peer_record)\n          {\n            updated_peer_record->last_seen_time = fc::time_point::now();\n            if (error)\n              updated_peer_record->last_error = error;\n            else\n              updated_peer_record->last_error = fc::exception(FC_LOG_MESSAGE(info, reason_for_disconnect.c_str()));\n            _potential_peer_db.update_entry(*updated_peer_record);\n          }\n        }\n        peer_to_disconnect->we_have_requested_close = true;\n        peer_to_disconnect->connection_closed_time = fc::time_point::now();\n\n        closing_connection_message closing_message( reason_for_disconnect, caused_by_error, error );\n        peer_to_disconnect->send_message( closing_message );\n      }\n\n      // notify the user.  This will be useful in testing, but we might want to remove it later.\n      // It makes good sense to notify the user if other nodes think she is behaving badly, but\n      // if we're just detecting and dissconnecting other badly-behaving nodes, they don't really care.\n      if (caused_by_error)\n      {\n        std::ostringstream error_message;\n        error_message << \"I am disconnecting peer \" << fc::variant( peer_to_disconnect->get_remote_endpoint(), GRAPHENE_NET_MAX_NESTED_OBJECTS ).as_string() <<\n                         \" for reason: \" << reason_for_disconnect;\n        _delegate->error_encountered(error_message.str(), fc::oexception());\n        dlog(error_message.str());\n      }\n      else\n        dlog(\"Disconnecting from ${peer} for ${reason}\", (\"peer\",peer_to_disconnect->get_remote_endpoint()) (\"reason\",reason_for_disconnect));\n    }\n\n    void node_impl::set_listen_endpoint( const fc::ip::endpoint& ep, bool wait_if_not_available )\n    {\n      VERIFY_CORRECT_THREAD();\n      _node_configuration.listen_endpoint = ep;\n      _node_configuration.wait_if_endpoint_is_busy = wait_if_not_available;\n      save_node_configuration();\n    }\n\n    void node_impl::set_inbound_endpoint( const fc::ip::endpoint& ep )\n    {\n      VERIFY_CORRECT_THREAD();\n      _node_configuration.inbound_endpoint = ep;\n      save_node_configuration();\n    }\n\n    void node_impl::set_accept_incoming_connections(bool accept)\n    {\n      VERIFY_CORRECT_THREAD();\n      _node_configuration.accept_incoming_connections = accept;\n      save_node_configuration();\n    }\n\n   void node_impl::set_connect_to_new_peers( bool connect )\n   {\n      VERIFY_CORRECT_THREAD();\n      _node_configuration.connect_to_new_peers = connect;\n      save_node_configuration();\n   }\n\n    fc::ip::endpoint node_impl::get_actual_listening_endpoint() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _actual_listening_endpoint;\n    }\n\n    std::vector<peer_status> node_impl::get_connected_peers() const\n    {\n      VERIFY_CORRECT_THREAD();\n      std::vector<peer_status> statuses;\n      fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n      for (const peer_connection_ptr& peer : _active_connections)\n      {\n        peer_status this_peer_status;\n        this_peer_status.version = 0;\n        fc::optional<fc::ip::endpoint> endpoint = peer->get_remote_endpoint();\n        if (endpoint)\n          this_peer_status.host = *endpoint;\n        fc::mutable_variant_object peer_details;\n        peer_details[\"addr\"] = endpoint ? (std::string)*endpoint : std::string();\n        peer_details[\"addrlocal\"] = (std::string)peer->get_local_endpoint();\n        peer_details[\"services\"] = \"00000001\";\n        peer_details[\"lastsend\"] = peer->get_last_message_sent_time().sec_since_epoch();\n        peer_details[\"lastrecv\"] = peer->get_last_message_received_time().sec_since_epoch();\n        peer_details[\"bytessent\"] = peer->get_total_bytes_sent();\n        peer_details[\"bytesrecv\"] = peer->get_total_bytes_received();\n        peer_details[\"conntime\"] = peer->get_connection_time();\n        peer_details[\"pingtime\"] = \"\";\n        peer_details[\"pingwait\"] = \"\";\n        peer_details[\"version\"] = \"\";\n        peer_details[\"subver\"] = peer->user_agent;\n        peer_details[\"inbound\"] = peer->direction == peer_connection_direction::inbound;\n        peer_details[\"firewall_status\"] = fc::variant( peer->is_firewalled, 1 );\n        peer_details[\"startingheight\"] = \"\";\n        peer_details[\"banscore\"] = \"\";\n        peer_details[\"syncnode\"] = \"\";\n\n        if (peer->fc_git_revision_sha)\n        {\n          std::string revision_string = *peer->fc_git_revision_sha;\n          if (*peer->fc_git_revision_sha == fc::git_revision_sha)\n            revision_string += \" (same as ours)\";\n          else\n            revision_string += \" (different from ours)\";\n          peer_details[\"fc_git_revision_sha\"] = revision_string;\n\n        }\n        if (peer->fc_git_revision_unix_timestamp)\n        {\n          peer_details[\"fc_git_revision_unix_timestamp\"] = *peer->fc_git_revision_unix_timestamp;\n          std::string age_string = fc::get_approximate_relative_time_string( *peer->fc_git_revision_unix_timestamp);\n          if (*peer->fc_git_revision_unix_timestamp == fc::time_point_sec(fc::git_revision_unix_timestamp))\n            age_string += \" (same as ours)\";\n          else if (*peer->fc_git_revision_unix_timestamp > fc::time_point_sec(fc::git_revision_unix_timestamp))\n            age_string += \" (newer than ours)\";\n          else\n            age_string += \" (older than ours)\";\n          peer_details[\"fc_git_revision_age\"] = age_string;\n        }\n\n        if (peer->platform)\n          peer_details[\"platform\"] = *peer->platform;\n\n        // provide these for debugging\n        // warning: these are just approximations, if the peer is \"downstream\" of us, they may\n        // have received blocks from other peers that we are unaware of\n        peer_details[\"current_head_block\"] = fc::variant( peer->last_block_delegate_has_seen, 1 );\n        peer_details[\"current_head_block_number\"] = _delegate->get_block_number(peer->last_block_delegate_has_seen);\n        peer_details[\"current_head_block_time\"] = peer->last_block_time_delegate_has_seen;\n\n        peer_details[\"peer_needs_sync_items_from_us\"] = peer->peer_needs_sync_items_from_us;\n        peer_details[\"we_need_sync_items_from_peer\"] = peer->we_need_sync_items_from_peer;\n\n        this_peer_status.info = peer_details;\n        statuses.push_back(this_peer_status);\n      }\n      return statuses;\n    }\n\n    uint32_t node_impl::get_connection_count() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return (uint32_t)_active_connections.size();\n    }\n\n    void node_impl::broadcast( const message& item_to_broadcast, const message_propagation_data& propagation_data )\n    {\n      VERIFY_CORRECT_THREAD();\n      message_hash_type hash_of_message_contents;\n      if( item_to_broadcast.msg_type.value() == graphene::net::block_message_type )\n      {\n        graphene::net::block_message block_message_to_broadcast = item_to_broadcast.as<graphene::net::block_message>();\n        hash_of_message_contents = block_message_to_broadcast.block_id; // for debugging\n        _most_recent_blocks_accepted.push_back( block_message_to_broadcast.block_id );\n      }\n      else if( item_to_broadcast.msg_type.value() == graphene::net::trx_message_type )\n      {\n        graphene::net::trx_message transaction_message_to_broadcast = item_to_broadcast.as<graphene::net::trx_message>();\n        hash_of_message_contents = transaction_message_to_broadcast.trx.id(); // for debugging\n        dlog( \"broadcasting trx: ${trx}\", (\"trx\", transaction_message_to_broadcast) );\n      }\n      message_hash_type hash_of_item_to_broadcast = item_to_broadcast.id();\n\n      _message_cache.cache_message( item_to_broadcast, hash_of_item_to_broadcast, propagation_data, hash_of_message_contents );\n      _new_inventory.insert( item_id(item_to_broadcast.msg_type.value(), hash_of_item_to_broadcast ) );\n      trigger_advertise_inventory_loop();\n    }\n\n    void node_impl::broadcast( const message& item_to_broadcast )\n    {\n      VERIFY_CORRECT_THREAD();\n      // this version is called directly from the client\n      message_propagation_data propagation_data{fc::time_point::now(), fc::time_point::now(), _node_id};\n      broadcast( item_to_broadcast, propagation_data );\n    }\n\n    void node_impl::sync_from(const item_id& current_head_block, const std::vector<uint32_t>& hard_fork_block_numbers)\n    {\n      VERIFY_CORRECT_THREAD();\n      _most_recent_blocks_accepted.clear();\n      _sync_item_type = current_head_block.item_type;\n      _most_recent_blocks_accepted.push_back(current_head_block.item_hash);\n      _hard_fork_block_numbers = hard_fork_block_numbers;\n    }\n\n    bool node_impl::is_connected() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return !_active_connections.empty();\n    }\n\n    std::vector<potential_peer_record> node_impl::get_potential_peers() const\n    {\n      VERIFY_CORRECT_THREAD();\n      std::vector<potential_peer_record> result;\n      // use explicit iterators here, for some reason the mac compiler can't used ranged-based for loops here\n      for (peer_database::iterator itr = _potential_peer_db.begin(); itr != _potential_peer_db.end(); ++itr)\n        result.push_back(*itr);\n      return result;\n    }\n\n    void node_impl::set_advanced_node_parameters(const fc::variant_object& params)\n    {\n      VERIFY_CORRECT_THREAD();\n      if (params.contains(\"peer_connection_retry_timeout\"))\n        _peer_connection_retry_timeout = params[\"peer_connection_retry_timeout\"].as<uint32_t>(1);\n      if (params.contains(\"desired_number_of_connections\"))\n        _desired_number_of_connections = params[\"desired_number_of_connections\"].as<uint32_t>(1);\n      if (params.contains(\"maximum_number_of_connections\"))\n        _maximum_number_of_connections = params[\"maximum_number_of_connections\"].as<uint32_t>(1);\n      if (params.contains(\"max_addresses_to_handle_at_once\"))\n        _max_addrs_to_handle_at_once = params[\"max_addresses_to_handle_at_once\"].as<uint32_t>(1);\n      if (params.contains(\"max_blocks_to_handle_at_once\"))\n        _max_blocks_to_handle_at_once = params[\"max_blocks_to_handle_at_once\"].as<uint32_t>(1);\n      if (params.contains(\"max_sync_blocks_to_prefetch\"))\n        _max_sync_blocks_to_prefetch = params[\"max_sync_blocks_to_prefetch\"].as<uint32_t>(1);\n      if (params.contains(\"max_sync_blocks_per_peer\"))\n        _max_sync_blocks_per_peer = params[\"max_sync_blocks_per_peer\"].as<uint32_t>(1);\n\n      _desired_number_of_connections = std::min(_desired_number_of_connections, _maximum_number_of_connections);\n\n      while (_active_connections.size() > _maximum_number_of_connections)\n        disconnect_from_peer(_active_connections.begin()->get(),\n                             \"I have too many connections open\");\n      trigger_p2p_network_connect_loop();\n    }\n\n    fc::variant_object node_impl::get_advanced_node_parameters()\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::mutable_variant_object result;\n      result[\"peer_connection_retry_timeout\"] = _peer_connection_retry_timeout;\n      result[\"desired_number_of_connections\"] = _desired_number_of_connections;\n      result[\"maximum_number_of_connections\"] = _maximum_number_of_connections;\n      result[\"max_addresses_to_handle_at_once\"] = _max_addrs_to_handle_at_once;\n      result[\"max_blocks_to_handle_at_once\"] = _max_blocks_to_handle_at_once;\n      result[\"max_sync_blocks_to_prefetch\"] = _max_sync_blocks_to_prefetch;\n      result[\"max_sync_blocks_per_peer\"] = _max_sync_blocks_per_peer;\n      return result;\n    }\n\n    message_propagation_data node_impl::get_tx_propagation_data(\n          const graphene::net::transaction_id_type& transaction_id ) const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_cache.get_message_propagation_data( transaction_id );\n    }\n\n    message_propagation_data node_impl::get_block_propagation_data(\n          const graphene::net::block_id_type& block_id ) const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_cache.get_message_propagation_data( block_id );\n    }\n\n    node_id_t node_impl::get_node_id() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _node_id;\n    }\n    void node_impl::set_allowed_peers(const std::vector<node_id_t>& allowed_peers)\n    {\n      VERIFY_CORRECT_THREAD();\n#ifdef ENABLE_P2P_DEBUGGING_API\n      _allowed_peers.clear();\n      _allowed_peers.insert(allowed_peers.begin(), allowed_peers.end());\n      std::list<peer_connection_ptr> peers_to_disconnect;\n      if (!_allowed_peers.empty())\n      {\n         fc::scoped_lock<fc::mutex> lock(_active_connections.get_mutex());\n         for (const peer_connection_ptr& peer : _active_connections)\n            if (_allowed_peers.find(peer->node_id) == _allowed_peers.end())\n               peers_to_disconnect.push_back(peer);\n      }\n      for (const peer_connection_ptr& peer : peers_to_disconnect)\n        disconnect_from_peer(peer.get(), \"My allowed_peers list has changed, and you're no longer allowed.  Bye.\");\n#endif // ENABLE_P2P_DEBUGGING_API\n    }\n    void node_impl::clear_peer_database()\n    {\n      VERIFY_CORRECT_THREAD();\n      _potential_peer_db.clear();\n    }\n\n    void node_impl::set_total_bandwidth_limit( uint32_t upload_bytes_per_second, uint32_t download_bytes_per_second )\n    {\n      VERIFY_CORRECT_THREAD();\n      _rate_limiter.set_upload_limit( upload_bytes_per_second );\n      _rate_limiter.set_download_limit( download_bytes_per_second );\n    }\n\n    fc::variant_object node_impl::get_call_statistics() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _delegate->get_call_statistics();\n    }\n\n    fc::variant_object node_impl::network_get_info() const\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::mutable_variant_object info;\n      info[\"listening_on\"] = std::string( _actual_listening_endpoint );\n      info[\"node_public_key\"] = fc::variant( _node_public_key, 1 );\n      info[\"node_id\"] = fc::variant( _node_id, 1 );\n      return info;\n    }\n    fc::variant_object node_impl::network_get_usage_stats() const\n    {\n      VERIFY_CORRECT_THREAD();\n      std::vector<uint32_t> network_usage_by_second;\n      network_usage_by_second.reserve(_avg_net_read_speed_seconds.size());\n      std::transform(_avg_net_read_speed_seconds.begin(), _avg_net_read_speed_seconds.end(),\n                     _avg_net_write_speed_seconds.begin(),\n                     std::back_inserter(network_usage_by_second),\n                     std::plus<uint32_t>());\n\n      std::vector<uint32_t> network_usage_by_minute;\n      network_usage_by_minute.reserve(_avg_net_read_speed_minutes.size());\n      std::transform(_avg_net_read_speed_minutes.begin(), _avg_net_read_speed_minutes.end(),\n                     _avg_net_write_speed_minutes.begin(),\n                     std::back_inserter(network_usage_by_minute),\n                     std::plus<uint32_t>());\n\n      std::vector<uint32_t> network_usage_by_hour;\n      network_usage_by_hour.reserve(_avg_net_read_speed_hours.size());\n      std::transform(_avg_net_read_speed_hours.begin(), _avg_net_read_speed_hours.end(),\n                     _avg_net_write_speed_hours.begin(),\n                     std::back_inserter(network_usage_by_hour),\n                     std::plus<uint32_t>());\n\n      fc::mutable_variant_object result;\n      result[\"usage_by_second\"] = fc::variant( network_usage_by_second, 2 );\n      result[\"usage_by_minute\"] = fc::variant( network_usage_by_minute, 2 );\n      result[\"usage_by_hour\"]   = fc::variant( network_usage_by_hour, 2 );\n      return result;\n    }\n\n    bool node_impl::is_hard_fork_block(uint32_t block_number) const\n    {\n      return std::binary_search(_hard_fork_block_numbers.begin(), _hard_fork_block_numbers.end(), block_number);\n    }\n    uint32_t node_impl::get_next_known_hard_fork_block_number(uint32_t block_number) const\n    {\n      auto iter = std::upper_bound(_hard_fork_block_numbers.begin(), _hard_fork_block_numbers.end(),\n                                   block_number);\n      return iter != _hard_fork_block_numbers.end() ? *iter : 0;\n    }\n\n  }  // end namespace detail\n\n\n\n  /////////////////////////////////////////////////////////////////////////////////////////////////////////////\n  // implement node functions, they call the matching function in to detail::node_impl in the correct thread //\n\n#ifdef P2P_IN_DEDICATED_THREAD\n# define INVOKE_IN_IMPL(method_name, ...) \\\n    return my->_thread->async([&](){ return my->method_name(__VA_ARGS__); }, \"thread invoke for method \" BOOST_PP_STRINGIZE(method_name)).wait()\n#else\n# define INVOKE_IN_IMPL(method_name, ...) \\\n    return my->method_name(__VA_ARGS__)\n#endif // P2P_IN_DEDICATED_THREAD\n\n  node::node(const std::string& user_agent) :\n    my(new detail::node_impl(user_agent), detail::node_impl_deleter())\n  {\n    // nothing else to do\n  }\n\n  node::~node()\n  {\n    // nothing to do\n  }\n\n  void node::set_node_delegate( std::shared_ptr<node_delegate> del ) const\n  {\n    fc::thread* delegate_thread = &fc::thread::current();\n    INVOKE_IN_IMPL(set_node_delegate, del, delegate_thread);\n  }\n\n  void node::load_configuration( const fc::path& configuration_directory ) const\n  {\n    INVOKE_IN_IMPL(load_configuration, configuration_directory);\n  }\n\n  void node::listen_to_p2p_network() const\n  {\n    INVOKE_IN_IMPL(listen_to_p2p_network);\n  }\n\n  void node::connect_to_p2p_network() const\n  {\n    INVOKE_IN_IMPL(connect_to_p2p_network, my);\n  }\n\n  void node::add_node( const fc::ip::endpoint& ep ) const\n  {\n    INVOKE_IN_IMPL(add_node, ep);\n  }\n\n  void node::connect_to_endpoint( const fc::ip::endpoint& remote_endpoint ) const\n  {\n    INVOKE_IN_IMPL(connect_to_endpoint, remote_endpoint);\n  }\n\n  void node::set_listen_endpoint(const fc::ip::endpoint& ep , bool wait_if_not_available) const\n  {\n    INVOKE_IN_IMPL(set_listen_endpoint, ep, wait_if_not_available);\n  }\n\n  void node::set_inbound_endpoint(const fc::ip::endpoint& ep ) const\n  {\n    INVOKE_IN_IMPL( set_inbound_endpoint, ep );\n  }\n\n  void node::set_accept_incoming_connections(bool accept) const\n  {\n    INVOKE_IN_IMPL(set_accept_incoming_connections, accept);\n  }\n\n  void node::set_connect_to_new_peers( bool connect ) const\n  {\n     INVOKE_IN_IMPL( set_connect_to_new_peers, connect );\n  }\n\n  fc::ip::endpoint node::get_actual_listening_endpoint() const\n  {\n    INVOKE_IN_IMPL(get_actual_listening_endpoint);\n  }\n\n  std::vector<peer_status> node::get_connected_peers() const\n  {\n    INVOKE_IN_IMPL(get_connected_peers);\n  }\n\n  uint32_t node::get_connection_count() const\n  {\n    INVOKE_IN_IMPL(get_connection_count);\n  }\n\n  void node::broadcast( const message& msg ) const\n  {\n    INVOKE_IN_IMPL(broadcast, msg);\n  }\n\n  void node::sync_from(const item_id& current_head_block, const std::vector<uint32_t>& hard_fork_block_numbers) const\n  {\n    INVOKE_IN_IMPL(sync_from, current_head_block, hard_fork_block_numbers);\n  }\n\n  bool node::is_connected() const\n  {\n    INVOKE_IN_IMPL(is_connected);\n  }\n\n  std::vector<potential_peer_record> node::get_potential_peers()const\n  {\n    INVOKE_IN_IMPL(get_potential_peers);\n  }\n\n  void node::set_advanced_node_parameters( const fc::variant_object& params ) const\n  {\n    INVOKE_IN_IMPL(set_advanced_node_parameters, params);\n  }\n\n  fc::variant_object node::get_advanced_node_parameters() const\n  {\n    INVOKE_IN_IMPL(get_advanced_node_parameters);\n  }\n\n  message_propagation_data node::get_tx_propagation_data(\n        const graphene::net::transaction_id_type& transaction_id ) const\n  {\n    INVOKE_IN_IMPL(get_tx_propagation_data, transaction_id);\n  }\n\n  message_propagation_data node::get_block_propagation_data( const graphene::net::block_id_type& block_id ) const\n  {\n    INVOKE_IN_IMPL(get_block_propagation_data, block_id);\n  }\n\n  node_id_t node::get_node_id() const\n  {\n    INVOKE_IN_IMPL(get_node_id);\n  }\n\n  void node::set_allowed_peers( const std::vector<node_id_t>& allowed_peers ) const\n  {\n    INVOKE_IN_IMPL(set_allowed_peers, allowed_peers);\n  }\n\n  void node::clear_peer_database() const\n  {\n    INVOKE_IN_IMPL(clear_peer_database);\n  }\n\n  void node::set_total_bandwidth_limit(uint32_t upload_bytes_per_second,\n                                       uint32_t download_bytes_per_second) const\n  {\n    INVOKE_IN_IMPL(set_total_bandwidth_limit, upload_bytes_per_second, download_bytes_per_second);\n  }\n\n  fc::variant_object node::get_call_statistics() const\n  {\n    INVOKE_IN_IMPL(get_call_statistics);\n  }\n\n  fc::variant_object node::network_get_info() const\n  {\n    INVOKE_IN_IMPL(network_get_info);\n  }\n\n  fc::variant_object node::network_get_usage_stats() const\n  {\n    INVOKE_IN_IMPL(network_get_usage_stats);\n  }\n\n  void node::close() const\n  {\n    INVOKE_IN_IMPL(close);\n  }\n\n  namespace detail\n  {\n#define ROLLING_WINDOW_SIZE 1000\n#define INITIALIZE_ACCUMULATOR(r, data, method_name) \\\n      , BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator))(boost::accumulators::tag::rolling_window::window_size = ROLLING_WINDOW_SIZE) \\\n      , BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_before_accumulator))(boost::accumulators::tag::rolling_window::window_size = ROLLING_WINDOW_SIZE) \\\n      , BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_after_accumulator))(boost::accumulators::tag::rolling_window::window_size = ROLLING_WINDOW_SIZE)\n\n\n    statistics_gathering_node_delegate_wrapper::statistics_gathering_node_delegate_wrapper(\n            std::shared_ptr<node_delegate> delegate, fc::thread* thread_for_delegate_calls) :\n      _node_delegate(delegate),\n      _thread(thread_for_delegate_calls)\n      BOOST_PP_SEQ_FOR_EACH(INITIALIZE_ACCUMULATOR, unused, NODE_DELEGATE_METHOD_NAMES)\n    {}\n#undef INITIALIZE_ACCUMULATOR\n\n    fc::variant_object statistics_gathering_node_delegate_wrapper::get_call_statistics()\n    {\n      fc::mutable_variant_object statistics;\n      std::ostringstream note;\n      note << \"All times are in microseconds, mean is the average of the last \" << ROLLING_WINDOW_SIZE << \" call times\";\n      statistics[\"_note\"] = note.str();\n\n#define ADD_STATISTICS_FOR_METHOD(r, data, method_name) \\\n      fc::mutable_variant_object BOOST_PP_CAT(method_name, _stats); \\\n      BOOST_PP_CAT(method_name, _stats)[\"min\"] = boost::accumulators::min(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"mean\"] = boost::accumulators::rolling_mean(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"max\"] = boost::accumulators::max(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"sum\"] = boost::accumulators::sum(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_before_min\"] = boost::accumulators::min(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_before_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_before_mean\"] = boost::accumulators::rolling_mean(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_before_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_before_max\"] = boost::accumulators::max(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_before_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_before_sum\"] = boost::accumulators::sum(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_before_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_after_min\"] = boost::accumulators::min(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_after_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_after_mean\"] = boost::accumulators::rolling_mean(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_after_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_after_max\"] = boost::accumulators::max(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_after_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"delay_after_sum\"] = boost::accumulators::sum(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_after_accumulator))); \\\n      BOOST_PP_CAT(method_name, _stats)[\"count\"] = boost::accumulators::count(BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator))); \\\n      statistics[BOOST_PP_STRINGIZE(method_name)] = BOOST_PP_CAT(method_name, _stats);\n\n      BOOST_PP_SEQ_FOR_EACH(ADD_STATISTICS_FOR_METHOD, unused, NODE_DELEGATE_METHOD_NAMES)\n#undef ADD_STATISTICS_FOR_METHOD\n\n      return statistics;\n    }\n\n// define VERBOSE_NODE_DELEGATE_LOGGING to log whenever the node delegate throws exceptions\n//#define VERBOSE_NODE_DELEGATE_LOGGING\n#ifdef VERBOSE_NODE_DELEGATE_LOGGING\n#  define INVOKE_AND_COLLECT_STATISTICS(method_name, ...) \\\n    try \\\n    { \\\n      std::shared_ptr<call_statistics_collector> statistics_collector = std::make_shared<call_statistics_collector>( \\\n                                                     #method_name, \\\n                                                     &_ ## method_name ## _execution_accumulator, \\\n                                                     &_ ## method_name ## _delay_before_accumulator, \\\n                                                     &_ ## method_name ## _delay_after_accumulator); \\\n      if (_thread->is_current()) \\\n      { \\\n        call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \\\n        return _node_delegate->method_name(__VA_ARGS__); \\\n      } \\\n      else \\\n        return _thread->async([&, statistics_collector](){ \\\n          call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \\\n          return _node_delegate->method_name(__VA_ARGS__); \\\n        }, \"invoke \" BOOST_STRINGIZE(method_name)).wait(); \\\n    } \\\n    catch (const fc::exception& e) \\\n    { \\\n      dlog(\"node_delegate threw fc::exception: ${e}\", (\"e\", e)); \\\n      throw; \\\n    } \\\n    catch (const std::exception& e) \\\n    { \\\n      dlog(\"node_delegate threw std::exception: ${e}\", (\"e\", e.what())); \\\n      throw; \\\n    } \\\n    catch (...) \\\n    { \\\n      dlog(\"node_delegate threw unrecognized exception\"); \\\n      throw; \\\n    }\n#else\n#  define INVOKE_AND_COLLECT_STATISTICS(method_name, ...) \\\n    std::shared_ptr<call_statistics_collector> statistics_collector = std::make_shared<call_statistics_collector>( \\\n                                                   #method_name, \\\n                                                   &_ ## method_name ## _execution_accumulator, \\\n                                                   &_ ## method_name ## _delay_before_accumulator, \\\n                                                   &_ ## method_name ## _delay_after_accumulator); \\\n    if (_thread->is_current()) \\\n    { \\\n      call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \\\n      return _node_delegate->method_name(__VA_ARGS__); \\\n    } \\\n    else \\\n      return _thread->async([&, statistics_collector](){ \\\n        call_statistics_collector::actual_execution_measurement_helper helper(statistics_collector); \\\n        return _node_delegate->method_name(__VA_ARGS__); \\\n      }, \"invoke \" BOOST_STRINGIZE(method_name)).wait()\n#endif\n\n    bool statistics_gathering_node_delegate_wrapper::has_item( const net::item_id& id )\n    {\n      INVOKE_AND_COLLECT_STATISTICS(has_item, id);\n    }\n\n    void statistics_gathering_node_delegate_wrapper::handle_message( const message& message_to_handle )\n    {\n      INVOKE_AND_COLLECT_STATISTICS(handle_message, message_to_handle);\n    }\n\n    bool statistics_gathering_node_delegate_wrapper::handle_block( const graphene::net::block_message& block_message,\n             bool sync_mode, std::vector<message_hash_type>& contained_transaction_msg_ids)\n    {\n      INVOKE_AND_COLLECT_STATISTICS(handle_block, block_message, sync_mode, contained_transaction_msg_ids);\n    }\n\n    void statistics_gathering_node_delegate_wrapper::handle_transaction( const graphene::net::trx_message& transaction_message )\n    {\n      INVOKE_AND_COLLECT_STATISTICS(handle_transaction, transaction_message);\n    }\n\n    std::vector<item_hash_t> statistics_gathering_node_delegate_wrapper::get_block_ids(const std::vector<item_hash_t>& blockchain_synopsis,\n                                                                                       uint32_t& remaining_item_count,\n                                                                                       uint32_t limit /* = 2000 */)\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_block_ids, blockchain_synopsis, remaining_item_count, limit);\n    }\n\n    message statistics_gathering_node_delegate_wrapper::get_item( const item_id& id )\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_item, id);\n    }\n\n    chain_id_type statistics_gathering_node_delegate_wrapper::get_chain_id() const\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_chain_id);\n    }\n\n    std::vector<item_hash_t> statistics_gathering_node_delegate_wrapper::get_blockchain_synopsis(const item_hash_t& reference_point, uint32_t number_of_blocks_after_reference_point)\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_blockchain_synopsis, reference_point, number_of_blocks_after_reference_point);\n    }\n\n    void statistics_gathering_node_delegate_wrapper::sync_status( uint32_t item_type, uint32_t item_count )\n    {\n      INVOKE_AND_COLLECT_STATISTICS(sync_status, item_type, item_count);\n    }\n\n    void statistics_gathering_node_delegate_wrapper::connection_count_changed( uint32_t c )\n    {\n      INVOKE_AND_COLLECT_STATISTICS(connection_count_changed, c);\n    }\n\n    uint32_t statistics_gathering_node_delegate_wrapper::get_block_number(const item_hash_t& block_id)\n    {\n      // this function doesn't need to block,\n      ASSERT_TASK_NOT_PREEMPTED();\n      return _node_delegate->get_block_number(block_id);\n    }\n\n    fc::time_point_sec statistics_gathering_node_delegate_wrapper::get_block_time(const item_hash_t& block_id)\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_block_time, block_id);\n    }\n\n    item_hash_t statistics_gathering_node_delegate_wrapper::get_head_block_id() const\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_head_block_id);\n    }\n\n    uint32_t statistics_gathering_node_delegate_wrapper::estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const\n    {\n      INVOKE_AND_COLLECT_STATISTICS(estimate_last_known_fork_from_git_revision_timestamp, unix_timestamp);\n    }\n\n    void statistics_gathering_node_delegate_wrapper::error_encountered(const std::string& message, const fc::oexception& error)\n    {\n      INVOKE_AND_COLLECT_STATISTICS(error_encountered, message, error);\n    }\n\n    uint8_t statistics_gathering_node_delegate_wrapper::get_current_block_interval_in_seconds() const\n    {\n      INVOKE_AND_COLLECT_STATISTICS(get_current_block_interval_in_seconds);\n    }\n\n#undef INVOKE_AND_COLLECT_STATISTICS\n\n  } // end namespace detail\n\n   void node::add_seed_nodes(const std::vector<std::string>& seeds) const\n   {\n      for(const std::string& endpoint_string : seeds )\n      {\n         try {\n            add_seed_node(endpoint_string);\n         } catch( const fc::exception& e ) {\n            wlog( \"caught exception ${e} while adding seed node ${endpoint}\",\n                  (\"e\", e.to_detail_string())(\"endpoint\", endpoint_string) );\n         }\n      }\n   }\n\n   void node::add_seed_node(const std::string& in) const\n   {\n      INVOKE_IN_IMPL(add_seed_node, in);\n   }\n\n   void node::set_advertise_algorithm( const std::string& algo,\n         const std::vector<std::string>& advertise_or_exclude_list ) const\n   {\n      INVOKE_IN_IMPL( set_advertise_algorithm, algo, advertise_or_exclude_list );\n   }\n\n} } // end namespace graphene::net\n"
  },
  {
    "path": "libraries/net/node_impl.hxx",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#define P2P_IN_DEDICATED_THREAD\n\n//#define ENABLE_DEBUG_ULOGS\n\n#ifdef DEFAULT_LOGGER\n# undef DEFAULT_LOGGER\n#endif\n#define DEFAULT_LOGGER \"p2p\"\n\n//log these messages even at warn level when operating on the test network\n#ifdef GRAPHENE_TEST_NETWORK\n#define testnetlog wlog\n#else\n#define testnetlog(...) do {} while (0)\n#endif\n\n#include <memory>\n#include <boost/accumulators/accumulators.hpp>\n#include <boost/accumulators/statistics.hpp>\n#include <boost/accumulators/statistics/rolling_mean.hpp>\n#include <fc/thread/thread.hpp>\n#include <fc/thread/mutex.hpp>\n#include <fc/thread/scoped_lock.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/network/tcp_socket.hpp>\n#include <fc/network/rate_limiting.hpp>\n#include <graphene/chain/config.hpp>\n#include <graphene/protocol/types.hpp>\n#include <graphene/net/node.hpp>\n#include <graphene/net/core_messages.hpp>\n#include <graphene/net/peer_connection.hpp>\n\nnamespace graphene { namespace net { namespace detail {\n\nnamespace bmi = boost::multi_index;\n\n/*******\n * A class to wrap std::unordered_set for multithreading\n */\ntemplate <class Key, class Hash = std::hash<Key>, class Pred = std::equal_to<Key> >\nclass concurrent_unordered_set : private std::unordered_set<Key, Hash, Pred>\n{\nprivate:\n   mutable fc::mutex mux;\n\npublic:\n   /// Iterations require a lock. This exposes the mutex. Use with care (i.e. lock_guard)\n   fc::mutex& get_mutex()const { return mux; }\n\n   /// Insertion\n   /// @{\n   std::pair< typename std::unordered_set<Key, Hash, Pred>::iterator, bool> emplace( Key key)\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::emplace( key );\n   }\n   std::pair< typename std::unordered_set<Key, Hash, Pred>::iterator, bool> insert (const Key& val)\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::insert( val ); \n   }\n   /// @}\n   /// Size\n   /// @{\n   size_t size() const \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::size(); \n   }\n   bool empty() const noexcept\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::empty();\n   }\n   /// @}\n   /// Removal\n   /// @{\n   void clear() noexcept\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      std::unordered_set<Key, Hash, Pred>::clear();\n   }\n   typename std::unordered_set<Key, Hash, Pred>::iterator erase( \n         typename std::unordered_set<Key, Hash, Pred>::const_iterator itr)\n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::erase( itr); \n   }\n   size_t erase( const Key& key)\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::erase( key ); \n   }\n   /// @}\n   /// Swap\n   /// @{\n   void swap( typename std::unordered_set<Key, Hash, Pred>& other ) noexcept\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      std::unordered_set<Key, Hash, Pred>::swap( other );\n   }\n   /// @}\n   /// Iteration\n   /// @{\n   typename std::unordered_set<Key, Hash, Pred>::iterator begin() noexcept \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::begin(); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::const_iterator begin() const noexcept \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::begin(); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::local_iterator begin(size_t n) \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::begin(n); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::const_local_iterator begin(size_t n) const \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::begin(n); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::iterator end() noexcept \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::end(); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::const_iterator end() const noexcept \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::end(); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::local_iterator end(size_t n) \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::end(n); \n   }\n   typename std::unordered_set<Key, Hash, Pred>::const_local_iterator end(size_t n) const \n   { \n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::end(n); \n   }\n   /// @}\n   /// Search\n   typename std::unordered_set<Key, Hash, Pred>::const_iterator find(Key key)\n   {\n      fc::scoped_lock<fc::mutex> lock(mux);\n      return std::unordered_set<Key, Hash, Pred>::find(key); \n   }\n};\n\nclass blockchain_tied_message_cache\n{\nprivate:\n   static const uint32_t cache_duration_in_blocks = GRAPHENE_NET_MESSAGE_CACHE_DURATION_IN_BLOCKS;\n\n   struct message_hash_index{};\n   struct message_contents_hash_index{};\n   struct block_clock_index{};\n   struct message_info\n   {\n      message_hash_type message_hash;\n      message           message_body;\n      uint32_t          block_clock_when_received;\n\n      /// for network performance stats\n      message_propagation_data propagation_data;\n      /// hash of whatever the message contains\n      /// (if it's a transaction, this is the transaction id, if it's a block, it's the block_id)\n      message_hash_type message_contents_hash;\n\n      message_info( const message_hash_type& message_hash,\n                    const message&           message_body,\n                    uint32_t                 block_clock_when_received,\n                    const message_propagation_data& propagation_data,\n                    message_hash_type        message_contents_hash ) :\n            message_hash( message_hash ),\n            message_body( message_body ),\n            block_clock_when_received( block_clock_when_received ),\n            propagation_data( propagation_data ),\n            message_contents_hash( message_contents_hash )\n      {}\n   };\n\n   using message_cache_container = boost::multi_index_container < message_info,\n               bmi::indexed_by<\n                  bmi::ordered_unique< bmi::tag<message_hash_index>,\n                     bmi::member<message_info, message_hash_type, &message_info::message_hash> >,\n                  bmi::ordered_non_unique< bmi::tag<message_contents_hash_index>,\n                     bmi::member<message_info, message_hash_type, &message_info::message_contents_hash> >,\n                  bmi::ordered_non_unique< bmi::tag<block_clock_index>,\n                     bmi::member<message_info, uint32_t, &message_info::block_clock_when_received> > > >;\n\n   message_cache_container _message_cache;\n\n   uint32_t block_clock = 0;\n\npublic:\n   void block_accepted();\n   void cache_message( const message& message_to_cache,\n                       const message_hash_type& hash_of_message_to_cache,\n                       const message_propagation_data& propagation_data,\n                       const message_hash_type& message_content_hash );\n   message get_message( const message_hash_type& hash_of_message_to_lookup ) const;\n   message_propagation_data get_message_propagation_data(\n         const message_hash_type& hash_of_msg_contents_to_lookup ) const;\n   size_t size() const { return _message_cache.size(); }\n};\n\n/// When requesting items from peers, we want to prioritize any blocks before\n/// transactions, but otherwise request items in the order we heard about them\nstruct prioritized_item_id\n{\n  item_id  item;\n  size_t sequence_number;\n  fc::time_point timestamp; ///< the time we last heard about this item in an inventory message\n\n  prioritized_item_id(const item_id& item, size_t sequence_number) :\n    item(item),\n    sequence_number(sequence_number),\n    timestamp(fc::time_point::now())\n  {}\n  bool operator<(const prioritized_item_id& rhs) const\n  {\n    static_assert(graphene::net::block_message_type > graphene::net::trx_message_type,\n                  \"block_message_type must be greater than trx_message_type for prioritized_item_ids to sort correctly\");\n    if (item.item_type != rhs.item.item_type)\n      return item.item_type > rhs.item.item_type;\n    return rhs.sequence_number > sequence_number;\n  }\n};\n\nclass statistics_gathering_node_delegate_wrapper : public node_delegate\n{\n   private:\n      std::shared_ptr<node_delegate> _node_delegate;\n      fc::thread *_thread;\n\n      using call_stats_accumulator = boost::accumulators::accumulator_set< int64_t,\n                                        boost::accumulators::stats< boost::accumulators::tag::min,\n                                                                    boost::accumulators::tag::rolling_mean,\n                                                                    boost::accumulators::tag::max,\n                                                                    boost::accumulators::tag::sum,\n                                                                    boost::accumulators::tag::count> >;\n#define NODE_DELEGATE_METHOD_NAMES (has_item) \\\n                               (handle_message) \\\n                               (handle_block) \\\n                               (handle_transaction) \\\n                               (get_block_ids) \\\n                               (get_item) \\\n                               (get_chain_id) \\\n                               (get_blockchain_synopsis) \\\n                               (sync_status) \\\n                               (connection_count_changed) \\\n                               (get_block_number) \\\n                               (get_block_time) \\\n                               (get_head_block_id) \\\n                               (estimate_last_known_fork_from_git_revision_timestamp) \\\n                               (error_encountered) \\\n                               (get_current_block_interval_in_seconds)\n\n\n\n#define DECLARE_ACCUMULATOR(r, data, method_name) \\\n      mutable call_stats_accumulator BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _execution_accumulator)); \\\n      mutable call_stats_accumulator BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_before_accumulator)); \\\n      mutable call_stats_accumulator BOOST_PP_CAT(_, BOOST_PP_CAT(method_name, _delay_after_accumulator));\n      BOOST_PP_SEQ_FOR_EACH(DECLARE_ACCUMULATOR, unused, NODE_DELEGATE_METHOD_NAMES)\n#undef DECLARE_ACCUMULATOR\n\n      class call_statistics_collector\n      {\n      private:\n        fc::time_point _call_requested_time;\n        fc::time_point _begin_execution_time;\n        fc::time_point _execution_completed_time;\n        const char* _method_name;\n        call_stats_accumulator* _execution_accumulator;\n        call_stats_accumulator* _delay_before_accumulator;\n        call_stats_accumulator* _delay_after_accumulator;\n      public:\n        class actual_execution_measurement_helper\n        {\n          std::shared_ptr<call_statistics_collector> _collector;\n        public:\n          explicit actual_execution_measurement_helper(std::shared_ptr<call_statistics_collector> collector) :\n            _collector(collector)\n          {\n            _collector->starting_execution();\n          }\n          ~actual_execution_measurement_helper()\n          {\n            _collector->execution_completed();\n          }\n        };\n        call_statistics_collector(const char* method_name,\n                                  call_stats_accumulator* execution_accumulator,\n                                  call_stats_accumulator* delay_before_accumulator,\n                                  call_stats_accumulator* delay_after_accumulator) :\n          _call_requested_time(fc::time_point::now()),\n          _method_name(method_name),\n          _execution_accumulator(execution_accumulator),\n          _delay_before_accumulator(delay_before_accumulator),\n          _delay_after_accumulator(delay_after_accumulator)\n        {}\n        ~call_statistics_collector()\n        {\n          fc::time_point end_time(fc::time_point::now());\n          fc::microseconds actual_execution_time(_execution_completed_time - _begin_execution_time);\n          fc::microseconds delay_before(_begin_execution_time - _call_requested_time);\n          fc::microseconds delay_after(end_time - _execution_completed_time);\n          fc::microseconds total_duration(actual_execution_time + delay_before + delay_after);\n          (*_execution_accumulator)(actual_execution_time.count());\n          (*_delay_before_accumulator)(delay_before.count());\n          (*_delay_after_accumulator)(delay_after.count());\n          if (total_duration > fc::milliseconds(500))\n          {\n            dlog(\"Call to method node_delegate::${method} took ${total_duration}us, longer than our target maximum of 500ms\",\n                 (\"method\", _method_name)\n                 (\"total_duration\", total_duration.count()));\n            dlog(\"Actual execution took ${execution_duration}us, with a ${delegate_delay}us delay before the delegate thread started \"\n                 \"executing the method, and a ${p2p_delay}us delay after it finished before the p2p thread started processing the response\",\n                 (\"execution_duration\", actual_execution_time)\n                 (\"delegate_delay\", delay_before)\n                 (\"p2p_delay\", delay_after));\n          }\n        }\n        void starting_execution()\n        {\n          _begin_execution_time = fc::time_point::now();\n        }\n        void execution_completed()\n        {\n          _execution_completed_time = fc::time_point::now();\n        }\n      };\n   public:\n      statistics_gathering_node_delegate_wrapper(std::shared_ptr<node_delegate> delegate,\n                                                 fc::thread* thread_for_delegate_calls);\n\n      fc::variant_object get_call_statistics();\n\n      bool has_item( const graphene::net::item_id& id ) override;\n      void handle_message( const message& ) override;\n      bool handle_block( const graphene::net::block_message& block_message, bool sync_mode,\n                         std::vector<message_hash_type>& contained_transaction_msg_ids ) override;\n      void handle_transaction( const graphene::net::trx_message& transaction_message ) override;\n      std::vector<item_hash_t> get_block_ids(const std::vector<item_hash_t>& blockchain_synopsis,\n                                             uint32_t& remaining_item_count,\n                                             uint32_t limit = 2000) override;\n      message get_item( const item_id& id ) override;\n      graphene::protocol::chain_id_type get_chain_id() const override;\n      std::vector<item_hash_t> get_blockchain_synopsis(const item_hash_t& reference_point,\n                                                       uint32_t number_of_blocks_after_reference_point) override;\n      void     sync_status( uint32_t item_type, uint32_t item_count ) override;\n      void     connection_count_changed( uint32_t c ) override;\n      uint32_t get_block_number(const item_hash_t& block_id) override;\n      fc::time_point_sec get_block_time(const item_hash_t& block_id) override;\n      item_hash_t get_head_block_id() const override;\n      uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const override;\n      void error_encountered(const std::string& message, const fc::oexception& error) override;\n      uint8_t get_current_block_interval_in_seconds() const override;\n};\n\n/// This specifies configuration info for the local node.  It's stored as JSON\n/// in the configuration directory (application data directory)\nstruct node_configuration\n{\n   fc::ip::endpoint listen_endpoint;\n   fc::optional<fc::ip::endpoint> inbound_endpoint;\n   bool accept_incoming_connections = true;\n   bool connect_to_new_peers = true;\n   bool wait_if_endpoint_is_busy = false;\n   /**\n    * Originally, our p2p code just had a 'node-id' that was a random number identifying this node\n    * on the network.  This is now a private key/public key pair, where the public key is used\n    * in place of the old random node-id.  The private part is unused, but might be used in\n    * the future to support some notion of trusted peers.\n    */\n   fc::ecc::private_key private_key;\n};\n\nclass node_impl : public peer_connection_delegate, public std::enable_shared_from_this<node_impl>\n{\npublic:\n   class address_builder\n   {\n   public:\n      static std::shared_ptr<address_builder> create_default_address_builder();\n      void build( node_impl* impl, address_message& ) const;\n      virtual bool should_advertise(const  fc::ip::endpoint& in ) const = 0;\n      virtual ~address_builder() = default;\n   };\n\n#ifdef P2P_IN_DEDICATED_THREAD\n      std::shared_ptr<fc::thread> _thread = std::make_shared<fc::thread>(\"p2p\");\n      std::shared_ptr<fc::thread> get_thread() const { return _thread; }\n#endif // P2P_IN_DEDICATED_THREAD\n      std::unique_ptr<statistics_gathering_node_delegate_wrapper> _delegate;\n      fc::sha256           _chain_id;\n\n#define NODE_CONFIGURATION_FILENAME      \"node_config.json\"\n#define POTENTIAL_PEER_DATABASE_FILENAME \"peers.json\"\n      fc::path             _node_configuration_directory;\n      node_configuration   _node_configuration;\n\n      /// Stores the endpoint we're listening on.  This will be the same as\n      /// _node_configuration.listen_endpoint, unless that endpoint was already\n      /// in use.\n      /// This will be 0.0.0.0:0 if the node is configured to not listen.\n      // Note: updating the type to optional may break 3rd-party client applications.\n      fc::ip::endpoint     _actual_listening_endpoint;\n\n      /// Used by the task that manages connecting to peers\n      /// @{\n      /// List of peers we want to connect to as soon as possible\n      std::list<potential_peer_record> _add_once_node_list;\n\n      peer_database             _potential_peer_db;\n      fc::promise<void>::ptr    _retrigger_connect_loop_promise;\n      bool                      _potential_peer_db_updated = false;\n      fc::future<void>          _p2p_network_connect_loop_done;\n      /// @}\n\n      /// Used by the task that fetches sync items during synchronization\n      /// @{\n      fc::promise<void>::ptr    _retrigger_fetch_sync_items_loop_promise;\n      bool                      _sync_items_to_fetch_updated = false;\n      fc::future<void>          _fetch_sync_items_loop_done;\n\n      typedef std::unordered_map<graphene::net::block_id_type, fc::time_point> active_sync_requests_map;\n\n      /// List of sync blocks we've asked for from peers but have not yet received\n      active_sync_requests_map              _active_sync_requests;\n      /// List of sync blocks we've just received but haven't yet tried to process\n      std::list<graphene::net::block_message> _new_received_sync_items;\n      /// List of sync blocks we've received, but can't yet process because we are still missing blocks\n      /// that come earlier in the chain\n      std::list<graphene::net::block_message> _received_sync_items;\n      /// @}\n\n      fc::future<void> _process_backlog_of_sync_blocks_done;\n      bool _suspend_fetching_sync_blocks = false;\n\n      /// Used by the task that fetches items during normal operation\n      /// @{\n      fc::promise<void>::ptr _retrigger_fetch_item_loop_promise;\n      bool                   _items_to_fetch_updated = false;\n      fc::future<void>       _fetch_item_loop_done;\n\n      struct item_id_index{};\n      using items_to_fetch_set_type = boost::multi_index_container< prioritized_item_id,\n               boost::multi_index::indexed_by<\n                  boost::multi_index::ordered_unique< boost::multi_index::identity<prioritized_item_id> >,\n                  boost::multi_index::hashed_unique<\n                     boost::multi_index::tag<item_id_index>,\n                     boost::multi_index::member<prioritized_item_id, item_id, &prioritized_item_id::item>,\n                     std::hash<item_id>\n                  >\n               >\n            >;\n      /// Items to fetch sequence counter\n      size_t  _items_to_fetch_seq_counter = 0;\n      /// List of items we know another peer has and we want\n      items_to_fetch_set_type _items_to_fetch;\n      /// List of transactions we've recently pushed and had rejected by the delegate\n      peer_connection::timestamped_items_set_type _recently_failed_items;\n      /// @}\n\n      /// Used by the task that advertises inventory during normal operation\n      /// @{\n      fc::promise<void>::ptr        _retrigger_advertise_inventory_loop_promise;\n      fc::future<void>              _advertise_inventory_loop_done;\n      /// List of items we have received but not yet advertised to our peers\n      concurrent_unordered_set<item_id>   _new_inventory;\n      /// @}\n\n      fc::future<void>     _kill_inactive_conns_loop_done;\n      /// A cached copy of the block interval, to avoid a thread hop to the blockchain to get the current value\n      uint8_t _recent_block_interval_seconds = GRAPHENE_MAX_BLOCK_INTERVAL;\n\n      std::string          _user_agent_string;\n      /**\n       * A key automatically generated when the client is first run, stored in\n       * node_config.json.  It doesn't really have much of a purpose yet, there was just some thought\n       * that we might someday have a use for nodes having a private key (sent in hello messages)\n       */\n      node_id_t            _node_public_key;\n      /**\n       * A random number generated each time the client is launched, used to prevent us\n       * from connecting to the same client multiple times (sent in hello messages).\n       * Since this was introduced after the hello_message was finalized, this is sent in the\n       * user_data field.\n       * While this shares the same underlying type as a public key, it is really just a random\n       * number.\n       */\n      node_id_t            _node_id;\n\n      /** If we have less than `_desired_number_of_connections`, we will try to connect with more nodes */\n      uint32_t             _desired_number_of_connections = GRAPHENE_NET_DEFAULT_DESIRED_CONNECTIONS;\n      /** If we have _maximum_number_of_connections or more, we will refuse any inbound connections */\n      uint32_t             _maximum_number_of_connections = GRAPHENE_NET_DEFAULT_MAX_CONNECTIONS;\n      /** Retry connections to peers that have failed or rejected us this often, in seconds */\n      uint32_t             _peer_connection_retry_timeout = GRAPHENE_NET_DEFAULT_PEER_CONNECTION_RETRY_TIME;\n      /** How many seconds of inactivity are permitted before disconnecting a peer */\n      uint32_t             _peer_inactivity_timeout = GRAPHENE_NET_PEER_HANDSHAKE_INACTIVITY_TIMEOUT;\n\n      fc::tcp_server       _tcp_server;\n      fc::future<void>     _accept_loop_complete;\n\n      /// Stores all connections which have not yet finished key exchange or are still sending\n      /// initial handshaking messages back and forth (not yet ready to initiate syncing)\n      concurrent_unordered_set<graphene::net::peer_connection_ptr>               _handshaking_connections;\n      /** Stores fully established connections we're either syncing with or in normal operation with */\n      concurrent_unordered_set<graphene::net::peer_connection_ptr>               _active_connections;\n      /// Stores connections we've closed (sent closing message, not actually closed),\n      /// but are still waiting for the remote end to close before we delete them\n      concurrent_unordered_set<graphene::net::peer_connection_ptr>               _closing_connections;\n      /// Stores connections we've closed, but are still waiting for the OS to notify us that the socket\n      /// is really closed\n      concurrent_unordered_set<graphene::net::peer_connection_ptr>               _terminating_connections;\n\n      /// The /n/ most recent blocks we've accepted (currently tuned to the max number of connections)\n      boost::circular_buffer<item_hash_t> _most_recent_blocks_accepted { _maximum_number_of_connections };\n\n      uint32_t _sync_item_type = 0;\n      /// The number of items we still need to fetch while syncing\n      uint32_t _total_num_of_unfetched_items = 0;\n      /// List of all block numbers where there are hard forks\n      std::vector<uint32_t> _hard_fork_block_numbers;\n\n      /// Cache message we have received and might be required to provide to other peers via inventory requests\n      blockchain_tied_message_cache _message_cache;\n\n      fc::rate_limiting_group _rate_limiter { 0, 0 };\n\n      /// Number of connections last reported to the client (to avoid sending duplicate messages)\n      uint32_t _last_reported_number_of_conns = 0;\n\n      std::shared_ptr<address_builder> _address_builder = address_builder::create_default_address_builder();\n\n      fc::future<void> _fetch_updated_peer_lists_loop_done;\n\n      /// Average network read speed in the past seconds\n      boost::circular_buffer<uint32_t> _avg_net_read_speed_seconds { 60 };\n      /// Average network write speed in the past seconds\n      boost::circular_buffer<uint32_t> _avg_net_write_speed_seconds { 60 };\n      /// Average network read speed in the past minutes\n      boost::circular_buffer<uint32_t> _avg_net_read_speed_minutes { 60 };\n      /// Average network write speed in the past minutes\n      boost::circular_buffer<uint32_t> _avg_net_write_speed_minutes { 60 };\n      /// Average network read speed in the past hours\n      boost::circular_buffer<uint32_t> _avg_net_read_speed_hours { 72 };\n      /// Average network write speed in the past hours\n      boost::circular_buffer<uint32_t> _avg_net_write_speed_hours { 72 };\n      /// Average network usage second counter\n      size_t _avg_net_usage_second_counter = 0;\n      /// Average network usage minute counter\n      size_t _avg_net_usage_minute_counter = 0;\n\n      fc::time_point_sec _bandwidth_monitor_last_update_time;\n      fc::future<void> _bandwidth_monitor_loop_done;\n\n      fc::future<void> _dump_node_status_task_done;\n\n      /**\n       * We have two alternate paths through the schedule_peer_for_deletion code -- one that\n       * uses a mutex to prevent one fiber from adding items to the queue while another is deleting\n       * items from it, and one that doesn't.  The one that doesn't is simpler and more efficient\n       * code, but we're keeping around the version that uses the mutex because it crashes, and\n       * this crash probably indicates a bug in our underlying threading code that needs\n       * fixing.  To produce the bug, define USE_PEERS_TO_DELETE_MUTEX and then connect up\n       * to the network and set your desired/max connection counts high\n       */\n      /// @{\n//#define USE_PEERS_TO_DELETE_MUTEX 1\n#ifdef USE_PEERS_TO_DELETE_MUTEX\n      fc::mutex _peers_to_delete_mutex;\n#endif\n      std::list<peer_connection_ptr> _peers_to_delete;\n      fc::future<void> _delayed_peer_deletion_task_done;\n      /// @}\n\n#ifdef ENABLE_P2P_DEBUGGING_API\n      std::set<node_id_t> _allowed_peers;\n#endif // ENABLE_P2P_DEBUGGING_API\n\n      /// Set to true when we begin our destructor,\n      /// used to prevent us from starting new tasks while we're shutting down\n      bool _node_is_shutting_down = false;\n\n      /// Maximum number of addresses to handle at one time\n      size_t _max_addrs_to_handle_at_once = MAX_ADDRESSES_TO_HANDLE_AT_ONCE;\n      /// Maximum number of blocks to handle at one time\n      size_t _max_blocks_to_handle_at_once = MAX_BLOCKS_TO_HANDLE_AT_ONCE;\n      /// Maximum number of sync blocks to prefetch\n      size_t _max_sync_blocks_to_prefetch = MAX_SYNC_BLOCKS_TO_PREFETCH;\n      /// Maximum number of blocks per peer during syncing\n      size_t _max_sync_blocks_per_peer = GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING;\n\n      std::list<fc::future<void> > _handle_message_calls_in_progress;\n\n      /// Used by the task that checks whether addresses of seed nodes have been updated\n      /// @{\n      boost::container::flat_set<std::string> _seed_nodes;\n      fc::future<void> _update_seed_nodes_loop_done;\n      void update_seed_nodes_task();\n      void schedule_next_update_seed_nodes_task();\n      /// @}\n\n      explicit node_impl(const std::string& user_agent);\n      ~node_impl() override;\n\n      void save_node_configuration();\n\n      void p2p_network_connect_loop();\n      void trigger_p2p_network_connect_loop();\n\n      bool have_already_received_sync_item( const item_hash_t& item_hash );\n      void request_sync_item_from_peer( const peer_connection_ptr& peer, const item_hash_t& item_to_request );\n      void request_sync_items_from_peer( const peer_connection_ptr& peer, const std::vector<item_hash_t>& items_to_request );\n      void fetch_sync_items_loop();\n      void trigger_fetch_sync_items_loop();\n\n      bool is_item_in_any_peers_inventory(const item_id& item) const;\n      void fetch_items_loop();\n      void trigger_fetch_items_loop();\n\n      void advertise_inventory_loop();\n      void trigger_advertise_inventory_loop();\n\n      void kill_inactive_conns_loop(node_impl_ptr self);\n\n      void fetch_updated_peer_lists_loop();\n      void update_bandwidth_data(uint32_t bytes_read_this_second, uint32_t bytes_written_this_second);\n      void bandwidth_monitor_loop();\n      void dump_node_status_task();\n\n      bool is_accepting_new_connections();\n      bool is_wanting_new_connections();\n      uint32_t get_number_of_connections();\n      peer_connection_ptr get_peer_by_node_id(const node_id_t& id) const;\n\n      bool merge_address_info_with_potential_peer_database( const std::vector<address_info> addresses );\n      void display_current_connections();\n      uint32_t calculate_unsynced_block_count_from_all_peers();\n      std::vector<item_hash_t> create_blockchain_synopsis_for_peer( const peer_connection* peer );\n      void fetch_next_batch_of_item_ids_from_peer( peer_connection* peer, bool reset_fork_tracking_data_for_peer = false );\n\n      fc::variant_object generate_hello_user_data();\n      void parse_hello_user_data_for_peer( peer_connection* originating_peer, const fc::variant_object& user_data );\n\n      void on_message( peer_connection* originating_peer,\n                       const message& received_message ) override;\n\n      void on_hello_message( peer_connection* originating_peer,\n                             const hello_message& hello_message_received );\n\n      void on_connection_accepted_message( peer_connection* originating_peer,\n                                           const connection_accepted_message& ) const;\n\n      void on_connection_rejected_message( peer_connection* originating_peer,\n                                           const connection_rejected_message& connection_rejected_message_received );\n\n      void on_address_request_message( peer_connection* originating_peer, const address_request_message&);\n\n      void on_address_message( peer_connection* originating_peer,\n                               const address_message& address_message_received );\n\n      void on_fetch_blockchain_item_ids_message( peer_connection* originating_peer,\n                                                 const fetch_blockchain_item_ids_message& fetch_blockchain_item_ids_message_received );\n\n      void on_blockchain_item_ids_inventory_message( peer_connection* originating_peer,\n                                                     const blockchain_item_ids_inventory_message& blockchain_item_ids_inventory_message_received );\n\n      void on_fetch_items_message( peer_connection* originating_peer,\n                                   const fetch_items_message& fetch_items_message_received );\n\n      void on_item_not_available_message( peer_connection* originating_peer,\n                                          const item_not_available_message& item_not_available_message_received );\n\n      void on_item_ids_inventory_message( peer_connection* originating_peer,\n                                          const item_ids_inventory_message& item_ids_inventory_message_received );\n\n      void on_closing_connection_message( peer_connection* originating_peer,\n                                          const closing_connection_message& closing_connection_message_received );\n\n      void on_current_time_request_message( peer_connection* originating_peer,\n                                            const current_time_request_message& current_time_request_message_received );\n\n      void on_current_time_reply_message( peer_connection* originating_peer,\n                                          const current_time_reply_message& current_time_reply_message_received );\n\n      void on_connection_closed(peer_connection* originating_peer) override;\n\n      void send_sync_block_to_node_delegate(const graphene::net::block_message& block_message_to_send);\n      void process_backlog_of_sync_blocks();\n      void trigger_process_backlog_of_sync_blocks();\n      void process_block_during_syncing(\n                  peer_connection* originating_peer,\n                  const graphene::net::block_message& block_message,\n                  const message_hash_type& message_hash);\n      void process_block_when_in_sync(\n                  peer_connection* originating_peer,\n                  const graphene::net::block_message& block_message,\n                  const message_hash_type& message_hash);\n      void process_block_message(\n                  peer_connection* originating_peer,\n                  const message& message_to_process,\n                  const message_hash_type& message_hash);\n\n      void process_ordinary_message(\n                  peer_connection* originating_peer,\n                  const message& message_to_process,\n                  const message_hash_type& message_hash);\n\n      void start_synchronizing();\n      void start_synchronizing_with_peer(const peer_connection_ptr& peer);\n\n      /// Called after a peer finishes handshaking, kicks off syncing\n      void new_peer_just_added(const peer_connection_ptr& peer);\n\n      void close();\n\n      void accept_connection_task(peer_connection_ptr new_peer);\n      void accept_loop();\n      void send_hello_message(const peer_connection_ptr& peer);\n      void connect_to_task(peer_connection_ptr new_peer, const fc::ip::endpoint& remote_endpoint);\n      bool is_connected_to_endpoint(const fc::ip::endpoint& remote_endpoint) const;\n\n      void move_peer_to_active_list(const peer_connection_ptr& peer);\n      void move_peer_to_closing_list(const peer_connection_ptr& peer);\n      void move_peer_to_terminating_list(const peer_connection_ptr& peer);\n\n      /***\n       * Look for an active connection at the given address\n       * @param remote_endpoint the address we are interested in\n       * @returns the connection, or peer_connection_ptr() if not found\n       */\n      peer_connection_ptr get_active_conn_for_endpoint( const fc::ip::endpoint& remote_endpoint ) const;\n      /***\n       * Look for a connection that is either active or currently in the handshaking process\n       * @param remote_endpoint the address we are interested in\n       * @returns the connection, or peer_connection_ptr() if not found\n       */\n      peer_connection_ptr get_connection_for_endpoint( const fc::ip::endpoint& remote_endpoint ) const;\n\n      void dump_node_status();\n\n      void delayed_peer_deletion_task();\n      void schedule_peer_for_deletion(const peer_connection_ptr& peer_to_delete);\n\n      void disconnect_from_peer( peer_connection* originating_peer,\n                               const std::string& reason_for_disconnect,\n                                bool caused_by_error = false,\n                               const fc::oexception& additional_data = fc::oexception() );\n\n      // methods implementing node's public interface\n      void set_node_delegate(std::shared_ptr<node_delegate> del, fc::thread* thread_for_delegate_calls);\n      void load_configuration( const fc::path& configuration_directory );\n      void listen_to_p2p_network();\n      void connect_to_p2p_network(node_impl_ptr self);\n      void add_node( const fc::ip::endpoint& ep );\n      void set_advertise_algorithm( const std::string& algo,\n            const std::vector<std::string>& advertise_or_exclude_list );\n      void add_seed_node( const std::string& seed_string );\n      void resolve_seed_node_and_add( const std::string& seed_string );\n      void initiate_connect_to(const peer_connection_ptr& peer);\n      void connect_to_endpoint(const fc::ip::endpoint& ep);\n      void set_listen_endpoint(const fc::ip::endpoint& ep , bool wait_if_not_available);\n      void set_inbound_endpoint( const fc::ip::endpoint& ep );\n      void set_accept_incoming_connections(bool accept);\n      void set_connect_to_new_peers( bool connect );\n\n      fc::ip::endpoint         get_actual_listening_endpoint() const;\n      std::vector<peer_status> get_connected_peers() const;\n      uint32_t                 get_connection_count() const;\n\n      void broadcast(const message& item_to_broadcast, const message_propagation_data& propagation_data);\n      void broadcast(const message& item_to_broadcast);\n      void sync_from(const item_id& current_head_block, const std::vector<uint32_t>& hard_fork_block_numbers);\n      bool is_connected() const;\n      std::vector<potential_peer_record> get_potential_peers() const;\n      void set_advanced_node_parameters( const fc::variant_object& params );\n\n      fc::variant_object         get_advanced_node_parameters();\n      message_propagation_data   get_tx_propagation_data(\n                                       const graphene::net::transaction_id_type& transaction_id ) const;\n      message_propagation_data   get_block_propagation_data( const graphene::net::block_id_type& block_id ) const;\n\n      node_id_t                  get_node_id() const;\n      void                       set_allowed_peers( const std::vector<node_id_t>& allowed_peers );\n      void                       clear_peer_database();\n      void                       set_total_bandwidth_limit( uint32_t upload_bytes_per_second,\n                                                            uint32_t download_bytes_per_second );\n      fc::variant_object         get_call_statistics() const;\n      graphene::net::message     get_message_for_item(const item_id& item) override;\n\n      fc::variant_object         network_get_info() const;\n      fc::variant_object         network_get_usage_stats() const;\n\n      bool is_hard_fork_block(uint32_t block_number) const;\n      uint32_t get_next_known_hard_fork_block_number(uint32_t block_number) const;\n    }; // end class node_impl\n\n    struct node_impl_deleter\n    {\n      void operator()(node_impl*);\n    };\n\n}}} // end of namespace graphene::net::detail\n\nFC_REFLECT( graphene::net::detail::node_configuration,\n            (listen_endpoint)\n            (inbound_endpoint)\n            (accept_incoming_connections)\n            (connect_to_new_peers)\n            (wait_if_endpoint_is_busy)\n            (private_key) )\n"
  },
  {
    "path": "libraries/net/peer_connection.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/net/peer_connection.hpp>\n#include <graphene/net/exceptions.hpp>\n#include <graphene/net/config.hpp>\n#include <graphene/chain/config.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/thread/thread.hpp>\n\n#include <boost/scope_exit.hpp>\n\n#ifdef DEFAULT_LOGGER\n# undef DEFAULT_LOGGER\n#endif\n#define DEFAULT_LOGGER \"p2p\"\n\n#ifndef NDEBUG\n# define VERIFY_CORRECT_THREAD() assert(_thread->is_current())\n#else\n# define VERIFY_CORRECT_THREAD() do {} while (0)\n#endif\n\nnamespace graphene { namespace net\n  {\n    message peer_connection::real_queued_message::get_message(peer_connection_delegate*)\n    {\n      if (message_send_time_field_offset != (size_t)-1)\n      {\n        // patch the current time into the message.  Since this operates on the packed version of the structure,\n        // it won't work for anything after a variable-length field\n        std::vector<char> packed_current_time = fc::raw::pack(fc::time_point::now());\n        assert(message_send_time_field_offset + packed_current_time.size() <= message_to_send.data.size());\n        memcpy(message_to_send.data.data() + message_send_time_field_offset,\n               packed_current_time.data(), packed_current_time.size());\n      }\n      return message_to_send;\n    }\n    size_t peer_connection::real_queued_message::get_size_in_queue()\n    {\n      return message_to_send.data.size();\n    }\n    message peer_connection::virtual_queued_message::get_message(peer_connection_delegate* node)\n    {\n      return node->get_message_for_item(item_to_send);\n    }\n\n    size_t peer_connection::virtual_queued_message::get_size_in_queue()\n    {\n      return sizeof(item_id);\n    }\n\n    peer_connection::peer_connection(peer_connection_delegate* delegate) :\n      _node(delegate),\n      _message_connection(this),\n      _total_queued_messages_size(0),\n      direction(peer_connection_direction::unknown),\n      our_state(our_connection_state::disconnected),\n      they_have_requested_close(false),\n      their_state(their_connection_state::disconnected),\n      we_have_requested_close(false),\n      negotiation_status(connection_negotiation_status::disconnected),\n      number_of_unfetched_item_ids(0),\n      peer_needs_sync_items_from_us(true),\n      we_need_sync_items_from_peer(true),\n      inhibit_fetching_sync_blocks(false),\n      transaction_fetching_inhibited_until(fc::time_point::min()),\n      last_known_fork_block_number(0),\n#ifndef NDEBUG\n      _thread(&fc::thread::current()),\n      _send_message_queue_tasks_running(0),\n#endif\n      _currently_handling_message(false)\n    {\n    }\n\n    peer_connection_ptr peer_connection::make_shared(peer_connection_delegate* delegate)\n    {\n      // The lifetime of peer_connection objects is managed by shared_ptrs in node.  The peer_connection\n      // is responsible for notifying the node when it should be deleted, and the process of deleting it\n      // cleans up the peer connection's asynchronous tasks which are responsible for notifying the node\n      // when it should be deleted.\n      // To ease this vicious cycle, we slightly delay the execution of the destructor until the\n      // current task yields.  In the (not uncommon) case where it is the task executing\n      // connect_to or read_loop, this allows the task to finish before the destructor is forced\n      // to cancel it.\n\n      // Implementation: derive peer_connection so that make_shared has access to the constructor\n      class peer_connection_subclass : public peer_connection\n      {\n      public:\n         explicit peer_connection_subclass(peer_connection_delegate* delegate) : peer_connection(delegate) {}\n      };\n      return std::make_shared<peer_connection_subclass>(delegate);\n    }\n\n    void peer_connection::destroy()\n    {\n      VERIFY_CORRECT_THREAD();\n\n#if 0 // this gets too verbose\n#ifndef NDEBUG\n      struct scope_logger {\n        fc::optional<fc::ip::endpoint> endpoint;\n        scope_logger(const fc::optional<fc::ip::endpoint>& endpoint) : endpoint(endpoint) { dlog(\"entering peer_connection::destroy() for peer ${endpoint}\", (\"endpoint\", endpoint)); }\n        ~scope_logger() { dlog(\"leaving peer_connection::destroy() for peer ${endpoint}\", (\"endpoint\", endpoint)); }\n      } send_message_scope_logger(get_remote_endpoint());\n#endif\n#endif\n\n      try\n      {\n        dlog(\"calling close_connection()\");\n        close_connection();\n        dlog(\"close_connection completed normally\");\n      }\n      catch ( const fc::canceled_exception& )\n      {\n        assert(false && \"the task that deletes peers should not be canceled because it will prevent us from cleaning up correctly\");\n      }\n      catch ( ... )\n      {\n        dlog(\"close_connection threw\");\n      }\n\n      try\n      {\n        dlog(\"canceling _send_queued_messages task\");\n        _send_queued_messages_done.cancel_and_wait(__FUNCTION__);\n        dlog(\"cancel_and_wait completed normally\");\n      }\n      catch( const fc::exception& e )\n      {\n        wlog(\"Unexpected exception from peer_connection's send_queued_messages_task : ${e}\", (\"e\", e));\n      }\n      catch( ... )\n      {\n        wlog(\"Unexpected exception from peer_connection's send_queued_messages_task\");\n      }\n\n      try\n      {\n        dlog(\"canceling accept_or_connect_task\");\n        accept_or_connect_task_done.cancel_and_wait(__FUNCTION__);\n        dlog(\"accept_or_connect_task completed normally\");\n      }\n      catch( const fc::exception& e )\n      {\n        wlog(\"Unexpected exception from peer_connection's accept_or_connect_task : ${e}\", (\"e\", e));\n      }\n      catch( ... )\n      {\n        wlog(\"Unexpected exception from peer_connection's accept_or_connect_task\");\n      }\n\n      _message_connection.destroy_connection(); // shut down the read loop\n    }\n\n    peer_connection::~peer_connection()\n    {\n      VERIFY_CORRECT_THREAD();\n      destroy();\n    }\n\n    fc::tcp_socket& peer_connection::get_socket()\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_socket();\n    }\n\n    void peer_connection::accept_connection()\n    {\n      VERIFY_CORRECT_THREAD();\n\n      struct scope_logger {\n        scope_logger() { dlog(\"entering peer_connection::accept_connection()\"); }\n        ~scope_logger() { dlog(\"leaving peer_connection::accept_connection()\"); }\n      } accept_connection_scope_logger;\n\n      try\n      {\n        assert( our_state == our_connection_state::disconnected &&\n                their_state == their_connection_state::disconnected );\n        direction = peer_connection_direction::inbound;\n        negotiation_status = connection_negotiation_status::accepting;\n        _message_connection.accept();           // perform key exchange\n        negotiation_status = connection_negotiation_status::accepted;\n        _remote_endpoint = _message_connection.get_socket().remote_endpoint();\n\n        // firewall-detecting info is pretty useless for inbound connections, but initialize\n        // it the best we can\n        fc::ip::endpoint local_endpoint = _message_connection.get_socket().local_endpoint();\n        inbound_address = local_endpoint.get_address();\n        inbound_port = local_endpoint.port();\n        outbound_port = inbound_port;\n\n        their_state = their_connection_state::just_connected;\n        our_state = our_connection_state::just_connected;\n        ilog( \"established inbound connection from ${remote_endpoint}, sending hello\",\n              (\"remote_endpoint\", _message_connection.get_socket().remote_endpoint() ) );\n      }\n      catch ( const fc::exception& e )\n      {\n        wlog( \"error accepting connection ${e}\", (\"e\", e.to_detail_string() ) );\n        throw;\n      }\n    }\n\n    void peer_connection::connect_to( const fc::ip::endpoint& remote_endpoint,\n                                      const fc::optional<fc::ip::endpoint>& local_endpoint )\n    {\n      VERIFY_CORRECT_THREAD();\n      try\n      {\n        assert( our_state == our_connection_state::disconnected &&\n                their_state == their_connection_state::disconnected );\n        direction = peer_connection_direction::outbound;\n\n        _remote_endpoint = remote_endpoint;\n        bool failed_to_bind = false;\n        if( local_endpoint )\n        {\n          // the caller wants us to bind the local side of this socket to a specific ip/port\n          // This depends on the ip/port being unused, and on being able to set the\n          // SO_REUSEADDR/SO_REUSEPORT flags, and either of these might fail, so we need to\n          // detect if this fails.\n          try\n          {\n            _message_connection.bind( *local_endpoint );\n          }\n          catch ( const fc::canceled_exception& )\n          {\n            throw;\n          }\n          catch ( const fc::exception& except )\n          {\n             failed_to_bind = true;\n             wlog( \"Failed to bind to desired local endpoint ${endpoint}, will connect using an OS-selected \"\n                   \"endpoint: ${except}\",\n                   (\"endpoint\", *local_endpoint )(\"except\", except ) );\n          }\n        }\n        negotiation_status = connection_negotiation_status::connecting;\n        bool retry = false;\n        try\n        {\n           _message_connection.connect_to( remote_endpoint );\n        }\n        catch ( const fc::canceled_exception& )\n        {\n           throw;\n        }\n        catch ( const fc::exception& except )\n        {\n           if( local_endpoint && !failed_to_bind )\n           {\n              retry = true;\n              wlog( \"Failed to connect to remote endpoint ${remote_endpoint} from local endpoint ${local_endpoint}, \"\n                    \"will connect using an OS-selected endpoint: ${except}\",\n                    (\"remote_endpoint\", remote_endpoint )(\"local_endpoint\", *local_endpoint )(\"except\", except ) );\n           }\n           else\n              throw;\n        }\n        if( retry )\n        {\n           get_socket().close();\n           get_socket().open();\n           _message_connection.connect_to( remote_endpoint );\n        }\n        negotiation_status = connection_negotiation_status::connected;\n        their_state = their_connection_state::just_connected;\n        our_state = our_connection_state::just_connected;\n        remote_inbound_endpoint = remote_endpoint;\n        ilog( \"established outbound connection to ${remote_endpoint}\", (\"remote_endpoint\", remote_endpoint ) );\n      }\n      catch ( fc::exception& e )\n      {\n        wlog( \"error connecting to peer ${remote_endpoint}: ${e}\",\n              (\"remote_endpoint\", remote_endpoint )(\"e\", e.to_detail_string() ) );\n        throw;\n      }\n    } // connect_to()\n\n    void peer_connection::on_message( message_oriented_connection* originating_connection,\n                                      const message& received_message )\n    {\n      VERIFY_CORRECT_THREAD();\n      _currently_handling_message = true;\n      BOOST_SCOPE_EXIT(this_) {\n        this_->_currently_handling_message = false;\n      } BOOST_SCOPE_EXIT_END\n      _node->on_message( this, received_message );\n    }\n\n    void peer_connection::on_connection_closed( message_oriented_connection* originating_connection )\n    {\n      VERIFY_CORRECT_THREAD();\n      negotiation_status = connection_negotiation_status::closed;\n      _node->on_connection_closed( this );\n    }\n\n    void peer_connection::send_queued_messages_task()\n    {\n      VERIFY_CORRECT_THREAD();\n#ifndef NDEBUG\n      struct counter {\n        unsigned& _send_message_queue_tasks_counter;\n        counter(unsigned& var) : _send_message_queue_tasks_counter(var) { /* dlog(\"entering peer_connection::send_queued_messages_task()\"); */ assert(_send_message_queue_tasks_counter == 0); ++_send_message_queue_tasks_counter; }\n        ~counter() { assert(_send_message_queue_tasks_counter == 1); --_send_message_queue_tasks_counter; /* dlog(\"leaving peer_connection::send_queued_messages_task()\"); */ }\n      } concurrent_invocation_counter(_send_message_queue_tasks_running);\n#endif\n      while (!_queued_messages.empty())\n      {\n        _queued_messages.front()->transmission_start_time = fc::time_point::now();\n        message message_to_send = _queued_messages.front()->get_message(_node);\n        try\n        {\n          //dlog(\"peer_connection::send_queued_messages_task() calling message_oriented_connection::send_message() \"\n          //     \"to send message of type ${type} for peer ${endpoint}\",\n          //     (\"type\", message_to_send.msg_type)(\"endpoint\", get_remote_endpoint()));\n          _message_connection.send_message(message_to_send);\n          //dlog(\"peer_connection::send_queued_messages_task()'s call to message_oriented_connection::send_message() completed normally for peer ${endpoint}\",\n          //     (\"endpoint\", get_remote_endpoint()));\n        }\n        catch (const fc::canceled_exception&)\n        {\n          dlog(\"message_oriented_connection::send_message() was canceled, rethrowing canceled_exception\");\n          throw;\n        }\n        catch (const fc::exception& send_error)\n        {\n          wlog(\"Error sending message: ${exception}.  Closing connection.\", (\"exception\", send_error));\n          try\n          {\n            close_connection();\n          }\n          catch (const fc::exception& close_error)\n          {\n            wlog(\"Caught error while closing connection: ${exception}\", (\"exception\", close_error));\n          }\n          return;\n        }\n        catch (const std::exception& e)\n        {\n          wlog(\"message_oriented_exception::send_message() threw a std::exception(): ${what}\", (\"what\", e.what()));\n        }\n        catch (...)\n        {\n          wlog(\"message_oriented_exception::send_message() threw an unhandled exception\");\n        }\n        _queued_messages.front()->transmission_finish_time = fc::time_point::now();\n        _total_queued_messages_size -= _queued_messages.front()->get_size_in_queue();\n        _queued_messages.pop();\n      }\n      //dlog(\"leaving peer_connection::send_queued_messages_task() due to queue exhaustion\");\n    }\n\n    void peer_connection::send_queueable_message(std::unique_ptr<queued_message>&& message_to_send)\n    {\n      VERIFY_CORRECT_THREAD();\n      _total_queued_messages_size += message_to_send->get_size_in_queue();\n      _queued_messages.emplace(std::move(message_to_send));\n      if (_total_queued_messages_size > GRAPHENE_NET_MAXIMUM_QUEUED_MESSAGES_IN_BYTES)\n      {\n        wlog(\"send queue exceeded maximum size of ${max} bytes (current size ${current} bytes)\",\n             (\"max\", GRAPHENE_NET_MAXIMUM_QUEUED_MESSAGES_IN_BYTES)(\"current\", _total_queued_messages_size));\n        try\n        {\n          close_connection();\n        }\n        catch (const fc::exception& e)\n        {\n          wlog(\"Caught error while closing connection: ${exception}\", (\"exception\", e));\n        }\n        return;\n      }\n\n      if( _send_queued_messages_done.valid() && _send_queued_messages_done.canceled() )\n        FC_THROW_EXCEPTION(fc::exception, \"Attempting to send a message on a connection that is being shut down\");\n\n      if (!_send_queued_messages_done.valid() || _send_queued_messages_done.ready())\n      {\n        //dlog(\"peer_connection::send_message() is firing up send_queued_message_task\");\n        _send_queued_messages_done = fc::async([this](){ send_queued_messages_task(); }, \"send_queued_messages_task\");\n      }\n      //else\n      //  dlog(\"peer_connection::send_message() doesn't need to fire up send_queued_message_task, it's already running\");\n    }\n\n    void peer_connection::send_message(const message& message_to_send, size_t message_send_time_field_offset)\n    {\n      VERIFY_CORRECT_THREAD();\n      //dlog(\"peer_connection::send_message() enqueueing message of type ${type} for peer ${endpoint}\",\n      //     (\"type\", message_to_send.msg_type)(\"endpoint\", get_remote_endpoint())); // for debug\n      auto message_to_enqueue = std::make_unique<real_queued_message>(\n                                      message_to_send, message_send_time_field_offset );\n      send_queueable_message(std::move(message_to_enqueue));\n    }\n\n    void peer_connection::send_item(const item_id& item_to_send)\n    {\n      VERIFY_CORRECT_THREAD();\n      //dlog(\"peer_connection::send_item() enqueueing message of type ${type} for peer ${endpoint}\",\n      //     (\"type\", item_to_send.item_type)(\"endpoint\", get_remote_endpoint())); // for debug\n      auto message_to_enqueue = std::make_unique<virtual_queued_message>(item_to_send);\n      send_queueable_message(std::move(message_to_enqueue));\n    }\n\n    void peer_connection::close_connection()\n    {\n      VERIFY_CORRECT_THREAD();\n      negotiation_status = connection_negotiation_status::closing;\n      if (connection_terminated_time != fc::time_point::min())\n        connection_terminated_time = fc::time_point::now();\n      _message_connection.close_connection();\n    }\n\n    void peer_connection::destroy_connection()\n    {\n      VERIFY_CORRECT_THREAD();\n      negotiation_status = connection_negotiation_status::closing;\n      destroy();\n    }\n\n    uint64_t peer_connection::get_total_bytes_sent() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_total_bytes_sent();\n    }\n\n    uint64_t peer_connection::get_total_bytes_received() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_total_bytes_received();\n    }\n\n    fc::time_point peer_connection::get_last_message_sent_time() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_last_message_sent_time();\n    }\n\n    fc::time_point peer_connection::get_last_message_received_time() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_last_message_received_time();\n    }\n\n    fc::optional<fc::ip::endpoint> peer_connection::get_remote_endpoint()\n    {\n      VERIFY_CORRECT_THREAD();\n      return _remote_endpoint;\n    }\n    fc::ip::endpoint peer_connection::get_local_endpoint()\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_socket().local_endpoint();\n    }\n\n    void peer_connection::set_remote_endpoint( fc::optional<fc::ip::endpoint> new_remote_endpoint )\n    {\n      VERIFY_CORRECT_THREAD();\n      _remote_endpoint = new_remote_endpoint;\n    }\n\n    bool peer_connection::busy() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return !items_requested_from_peer.empty() || !sync_items_requested_from_peer.empty() || item_ids_requested_from_peer;\n    }\n\n    bool peer_connection::idle() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return !busy();\n    }\n\n    bool peer_connection::is_currently_handling_message() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _currently_handling_message;\n    }\n\n    bool peer_connection::is_transaction_fetching_inhibited() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return transaction_fetching_inhibited_until > fc::time_point::now();\n    }\n\n    fc::sha512 peer_connection::get_shared_secret() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return _message_connection.get_shared_secret();\n    }\n\n    void peer_connection::clear_old_inventory()\n    {\n      VERIFY_CORRECT_THREAD();\n      fc::time_point_sec oldest_inventory_to_keep(fc::time_point::now() - fc::minutes(GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES));\n\n      // expire old items from inventory_advertised_to_peer\n      auto oldest_inventory_to_keep_iter = inventory_advertised_to_peer.get<timestamp_index>().lower_bound(oldest_inventory_to_keep);\n      auto begin_iter = inventory_advertised_to_peer.get<timestamp_index>().begin();\n      unsigned number_of_elements_advertised_to_peer_to_discard = std::distance(begin_iter, oldest_inventory_to_keep_iter);\n      inventory_advertised_to_peer.get<timestamp_index>().erase(begin_iter, oldest_inventory_to_keep_iter);\n\n      // also expire items from inventory_peer_advertised_to_us\n      oldest_inventory_to_keep_iter = inventory_peer_advertised_to_us.get<timestamp_index>().lower_bound(oldest_inventory_to_keep);\n      begin_iter = inventory_peer_advertised_to_us.get<timestamp_index>().begin();\n      unsigned number_of_elements_peer_advertised_to_discard = std::distance(begin_iter, oldest_inventory_to_keep_iter);\n      inventory_peer_advertised_to_us.get<timestamp_index>().erase(begin_iter, oldest_inventory_to_keep_iter);\n      dlog(\"Expiring old inventory for peer ${peer}: removing ${to_peer} items advertised to peer (${remain_to_peer} left), and ${to_us} advertised to us (${remain_to_us} left)\",\n           (\"peer\", get_remote_endpoint())\n           (\"to_peer\", number_of_elements_advertised_to_peer_to_discard)(\"remain_to_peer\", inventory_advertised_to_peer.size())\n           (\"to_us\", number_of_elements_peer_advertised_to_discard)(\"remain_to_us\", inventory_peer_advertised_to_us.size()));\n    }\n\n    // we have a higher limit for blocks than transactions so we will still fetch blocks even when transactions are throttled\n    bool peer_connection::is_inventory_advertised_to_us_list_full_for_transactions() const\n    {\n      VERIFY_CORRECT_THREAD();\n      return inventory_peer_advertised_to_us.size() > GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES * GRAPHENE_NET_MAX_TRX_PER_SECOND * 60;\n    }\n\n    bool peer_connection::is_inventory_advertised_to_us_list_full() const\n    {\n      VERIFY_CORRECT_THREAD();\n      // allow the total inventory size to be the maximum number of transactions we'll store in the inventory (above)\n      // plus the maximum number of blocks that would be generated in GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES (plus one,\n      // to give us some wiggle room)\n      return inventory_peer_advertised_to_us.size() >\n        GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES * GRAPHENE_NET_MAX_TRX_PER_SECOND * 60 +\n        (GRAPHENE_NET_MAX_INVENTORY_SIZE_IN_MINUTES + 1) * 60 / GRAPHENE_MIN_BLOCK_INTERVAL;\n    }\n\n    fc::optional<fc::ip::endpoint> peer_connection::get_endpoint_for_connecting() const\n    {\n      return remote_inbound_endpoint;\n    }\n\n} } // end namespace graphene::net\n"
  },
  {
    "path": "libraries/net/peer_database.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n#include <boost/multi_index/tag.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/io/raw_variant.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/io/json.hpp>\n\n#include <graphene/net/peer_database.hpp>\n#include <graphene/net/config.hpp>\n\nnamespace graphene { namespace net {\n  namespace detail\n  {\n    using namespace boost::multi_index;\n\n    class peer_database_impl\n    {\n    public:\n      struct last_seen_time_index {};\n      struct endpoint_index {};\n      typedef boost::multi_index_container<potential_peer_record, \n                                           indexed_by<ordered_non_unique<tag<last_seen_time_index>, \n                                                                         member<potential_peer_record, \n                                                                                fc::time_point_sec, \n                                                                                &potential_peer_record::last_seen_time>,\n                                                                         std::greater<fc::time_point_sec> >,\n                                                      hashed_unique<tag<endpoint_index>, \n                                                                    member<potential_peer_record, \n                                                                           fc::ip::endpoint, \n                                                                           &potential_peer_record::endpoint>, \n                                                                    std::hash<fc::ip::endpoint> > > > potential_peer_set;\n\n    private:\n      potential_peer_set     _potential_peer_set;\n      fc::path _peer_database_filename;\n\n    public:\n      void open(const fc::path& databaseFilename);\n      void close();\n      void clear();\n      void erase(const fc::ip::endpoint& endpointToErase);\n      void update_entry(const potential_peer_record& updatedRecord);\n      potential_peer_record lookup_or_create_entry_for_ep(const fc::ip::endpoint& endpointToLookup)const;\n      fc::optional<potential_peer_record> lookup_entry_for_endpoint(const fc::ip::endpoint& endpointToLookup)const;\n\n      peer_database::iterator begin() const;\n      peer_database::iterator end() const;\n      size_t size() const;\n    };\n\n    class peer_database_iterator_impl\n    {\n    public:\n      typedef peer_database_impl::potential_peer_set::index<peer_database_impl::last_seen_time_index>::type::iterator last_seen_time_index_iterator;\n      last_seen_time_index_iterator _iterator;\n      explicit peer_database_iterator_impl(const last_seen_time_index_iterator& iterator) :\n        _iterator(iterator)\n      {}\n    };\n    peer_database_iterator::peer_database_iterator( const peer_database_iterator& c ) :\n      boost::iterator_facade<peer_database_iterator, const potential_peer_record, boost::forward_traversal_tag>(c){}\n\n    void peer_database_impl::open(const fc::path& peer_database_filename)\n    {\n      _peer_database_filename = peer_database_filename;\n      if (fc::exists(_peer_database_filename))\n      {\n        try\n        {\n          std::vector<potential_peer_record> peer_records = fc::json::from_file(_peer_database_filename).as<std::vector<potential_peer_record> >( GRAPHENE_NET_MAX_NESTED_OBJECTS );\n          std::copy(peer_records.begin(), peer_records.end(), std::inserter(_potential_peer_set, _potential_peer_set.end()));\n          if (_potential_peer_set.size() > MAXIMUM_PEERDB_SIZE)\n          {\n            // prune database to a reasonable size\n            auto iter = _potential_peer_set.begin();\n            std::advance(iter, MAXIMUM_PEERDB_SIZE);\n            _potential_peer_set.erase(iter, _potential_peer_set.end());\n          }\n        }\n        catch (const fc::exception& e)\n        {\n          elog(\"error opening peer database file ${peer_database_filename}, starting with a clean database\", \n               (\"peer_database_filename\", _peer_database_filename));\n        }\n      }\n    }\n\n    void peer_database_impl::close()\n    {\n      std::vector<potential_peer_record> peer_records;\n      peer_records.reserve(_potential_peer_set.size());\n      std::copy(_potential_peer_set.begin(), _potential_peer_set.end(), std::back_inserter(peer_records));\n\n      try\n      {\n        fc::path peer_database_filename_dir = _peer_database_filename.parent_path();\n        if (!fc::exists(peer_database_filename_dir))\n          fc::create_directories(peer_database_filename_dir);\n        fc::json::save_to_file( peer_records, _peer_database_filename, GRAPHENE_NET_MAX_NESTED_OBJECTS );\n        dlog( \"Saved peer database to file ${filename}\", ( \"filename\", _peer_database_filename) );\n      }\n      catch (const fc::exception& e)\n      {\n        wlog( \"error saving peer database to file ${peer_database_filename}: ${error}\",\n              (\"peer_database_filename\", _peer_database_filename)(\"error\", e.to_detail_string()) );\n      }\n      _potential_peer_set.clear();\n    }\n\n    void peer_database_impl::clear()\n    {\n      _potential_peer_set.clear();\n    }\n\n    void peer_database_impl::erase(const fc::ip::endpoint& endpointToErase)\n    {\n      auto iter = _potential_peer_set.get<endpoint_index>().find(endpointToErase);\n      if (iter != _potential_peer_set.get<endpoint_index>().end())\n        _potential_peer_set.get<endpoint_index>().erase(iter);\n    }\n\n    void peer_database_impl::update_entry(const potential_peer_record& updatedRecord)\n    {\n      auto iter = _potential_peer_set.get<endpoint_index>().find(updatedRecord.endpoint);\n      if (iter != _potential_peer_set.get<endpoint_index>().end())\n        _potential_peer_set.get<endpoint_index>().modify(iter, [&updatedRecord](potential_peer_record& record) { record = updatedRecord; });\n      else\n        _potential_peer_set.get<endpoint_index>().insert(updatedRecord);\n    }\n\n    potential_peer_record peer_database_impl::lookup_or_create_entry_for_ep(\n          const fc::ip::endpoint& endpointToLookup ) const\n    {\n      auto iter = _potential_peer_set.get<endpoint_index>().find(endpointToLookup);\n      if (iter != _potential_peer_set.get<endpoint_index>().end())\n        return *iter;\n      return potential_peer_record(endpointToLookup);\n    }\n\n    fc::optional<potential_peer_record> peer_database_impl::lookup_entry_for_endpoint(\n          const fc::ip::endpoint& endpointToLookup ) const\n    {\n      auto iter = _potential_peer_set.get<endpoint_index>().find(endpointToLookup);\n      if (iter != _potential_peer_set.get<endpoint_index>().end())\n        return *iter;\n      return fc::optional<potential_peer_record>();\n    }\n\n    peer_database::iterator peer_database_impl::begin() const\n    {\n      return peer_database::iterator( std::make_unique<peer_database_iterator_impl>(\n                   _potential_peer_set.get<last_seen_time_index>().begin() ) );\n    }\n\n    peer_database::iterator peer_database_impl::end() const\n    {\n      return peer_database::iterator( std::make_unique<peer_database_iterator_impl>(\n                   _potential_peer_set.get<last_seen_time_index>().end() ) );\n    }\n\n    size_t peer_database_impl::size() const\n    {\n      return _potential_peer_set.size();\n    }\n\n    peer_database_iterator::peer_database_iterator()\n    {\n    }\n\n    peer_database_iterator::~peer_database_iterator()\n    {\n    }\n\n    peer_database_iterator::peer_database_iterator( std::unique_ptr<peer_database_iterator_impl>&& impl) :\n      my( std::move(impl) )\n    {\n    }\n\n    void peer_database_iterator::increment()\n    {\n      ++my->_iterator;\n    }\n\n    bool peer_database_iterator::equal(const peer_database_iterator& other) const\n    {\n      return my->_iterator == other.my->_iterator;\n    }\n\n    const potential_peer_record& peer_database_iterator::dereference() const\n    {\n      return *my->_iterator;\n    }\n\n  } // end namespace detail\n\n  peer_database::peer_database() :\n    my( std::make_unique<detail::peer_database_impl>() )\n  {\n  }\n\n  peer_database::~peer_database()\n  {}\n\n  void peer_database::open(const fc::path& databaseFilename)\n  {\n    my->open(databaseFilename);\n  }\n\n  void peer_database::close()\n  {\n    my->close();\n  }\n\n  void peer_database::clear()\n  {\n    my->clear();\n  }\n\n  void peer_database::erase(const fc::ip::endpoint& endpointToErase)\n  {\n    my->erase(endpointToErase);\n  }\n\n  void peer_database::update_entry(const potential_peer_record& updatedRecord)\n  {\n    my->update_entry(updatedRecord);\n  }\n\n  potential_peer_record peer_database::lookup_or_create_entry_for_ep(\n        const fc::ip::endpoint& endpointToLookup ) const\n  {\n    return my->lookup_or_create_entry_for_ep(endpointToLookup);\n  }\n\n  fc::optional<potential_peer_record> peer_database::lookup_entry_for_endpoint(\n        const fc::ip::endpoint& endpoint_to_lookup ) const\n  {\n    return my->lookup_entry_for_endpoint(endpoint_to_lookup);\n  }\n\n  peer_database::iterator peer_database::begin() const\n  {\n    return my->begin();\n  }\n\n  peer_database::iterator peer_database::end() const\n  {\n    return my->end();\n  }\n\n  size_t peer_database::size() const\n  {\n    return my->size();\n  }\n\n} } // end namespace graphene::net\n\nFC_REFLECT_ENUM( graphene::net::potential_peer_last_connection_disposition,\n                 (never_attempted_to_connect)\n                 (last_connection_failed)(last_connection_rejected)\n                 (last_connection_handshaking_failed)(last_connection_succeeded) )\nFC_REFLECT_DERIVED_NO_TYPENAME( graphene::net::potential_peer_record, BOOST_PP_SEQ_NIL,\n                                (endpoint)(last_seen_time)(last_connection_disposition)\n                                (last_connection_attempt_time)(number_of_successful_connection_attempts)\n                                (number_of_failed_connection_attempts)(last_error) )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::net::potential_peer_record)\n"
  },
  {
    "path": "libraries/net/stcp_socket.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <assert.h>\n\n#include <algorithm>\n\n#include <fc/crypto/hex.hpp>\n#include <fc/crypto/aes.hpp>\n#include <fc/crypto/city.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/network/ip.hpp>\n#include <fc/exception/exception.hpp>\n\n#include <graphene/net/stcp_socket.hpp>\n\nnamespace graphene { namespace net {\n\nstcp_socket::stcp_socket()\n//:_buf_len(0)\n#ifndef NDEBUG\n   : _read_buffer_in_use(false),\n     _write_buffer_in_use(false)\n#endif\n{\n}\nstcp_socket::~stcp_socket()\n{\n}\n\nvoid stcp_socket::do_key_exchange()\n{\n  _priv_key = fc::ecc::private_key::generate();\n  fc::ecc::public_key pub = _priv_key.get_public_key();\n  fc::ecc::public_key_data s = pub.serialize();\n  std::shared_ptr<char> serialized_key_buffer(new char[sizeof(fc::ecc::public_key_data)], [](char* p){ delete[] p; });\n  memcpy(serialized_key_buffer.get(), (char*)&s, sizeof(fc::ecc::public_key_data));\n  _sock.write( serialized_key_buffer, sizeof(fc::ecc::public_key_data) );\n  _sock.read( serialized_key_buffer, sizeof(fc::ecc::public_key_data) );\n  fc::ecc::public_key_data rpub;\n  memcpy((char*)&rpub, serialized_key_buffer.get(), sizeof(fc::ecc::public_key_data));\n\n  _shared_secret = _priv_key.get_shared_secret( rpub );\n//    ilog(\"shared secret ${s}\", (\"s\", shared_secret) );\n  _send_aes.init( fc::sha256::hash( (char*)&_shared_secret, sizeof(_shared_secret) ), \n                  fc::city_hash_crc_128((char*)&_shared_secret,sizeof(_shared_secret) ) );\n  _recv_aes.init( fc::sha256::hash( (char*)&_shared_secret, sizeof(_shared_secret) ), \n                  fc::city_hash_crc_128((char*)&_shared_secret,sizeof(_shared_secret) ) );\n}\n\n\nvoid stcp_socket::connect_to( const fc::ip::endpoint& remote_endpoint )\n{\n  _sock.connect_to( remote_endpoint );\n  do_key_exchange();\n}\n\nvoid stcp_socket::bind( const fc::ip::endpoint& local_endpoint )\n{\n  _sock.bind(local_endpoint);\n}\n\n/**\n *   This method must read at least 16 bytes at a time from\n *   the underlying TCP socket so that it can decrypt them. It\n *   will buffer any left-over.\n */\nsize_t stcp_socket::readsome( char* buffer, size_t len )\n{ try {\n    assert( len > 0 && (len % 16) == 0 );\n\n#ifndef NDEBUG\n    // This code was written with the assumption that you'd only be making one call to readsome \n    // at a time so it reuses _read_buffer.  If you really need to make concurrent calls to \n    // readsome(), you'll need to prevent reusing _read_buffer here\n    struct check_buffer_in_use {\n      bool& _buffer_in_use;\n      check_buffer_in_use(bool& buffer_in_use) : _buffer_in_use(buffer_in_use) { assert(!_buffer_in_use); _buffer_in_use = true; }\n      ~check_buffer_in_use() { assert(_buffer_in_use); _buffer_in_use = false; }\n    } buffer_in_use_checker(_read_buffer_in_use);\n#endif\n\n    const size_t read_buffer_length = 4096;\n    if (!_read_buffer)\n      _read_buffer.reset(new char[read_buffer_length], [](char* p){ delete[] p; });\n\n    len = std::min<size_t>(read_buffer_length, len);\n\n    size_t s = _sock.readsome( _read_buffer, len, 0 );\n    if( s % 16 ) \n    {\n      _sock.read(_read_buffer, 16 - (s%16), s);\n      s += 16-(s%16);\n    }\n    _recv_aes.decode( _read_buffer.get(), s, buffer );\n    return s;\n} FC_RETHROW_EXCEPTIONS( warn, \"\", (\"len\",len) ) }\n\nsize_t stcp_socket::readsome( const std::shared_ptr<char>& buf, size_t len, size_t offset ) \n{\n  return readsome(buf.get() + offset, len);\n}\n\nbool stcp_socket::eof()const\n{\n  return _sock.eof();\n}\n\nsize_t stcp_socket::writesome( const char* buffer, size_t len )\n{ try {\n    assert( len > 0 && (len % 16) == 0 );\n\n#ifndef NDEBUG\n    // This code was written with the assumption that you'd only be making one call to writesome\n    // at a time so it reuses _write_buffer.  If you really need to make concurrent calls to \n    // writesome(), you'll need to prevent reusing _write_buffer here\n    struct check_buffer_in_use {\n      bool& _buffer_in_use;\n      check_buffer_in_use(bool& buffer_in_use) : _buffer_in_use(buffer_in_use) { assert(!_buffer_in_use); _buffer_in_use = true; }\n      ~check_buffer_in_use() { assert(_buffer_in_use); _buffer_in_use = false; }\n    } buffer_in_use_checker(_write_buffer_in_use);\n#endif\n\n    const std::size_t write_buffer_length = 4096;\n    if (!_write_buffer)\n      _write_buffer.reset(new char[write_buffer_length], [](char* p){ delete[] p; });\n    len = std::min<size_t>(write_buffer_length, len);\n    memset(_write_buffer.get(), 0, len); // just in case aes.encode screws up\n    /**\n     * every sizeof(crypt_buf) bytes the aes channel\n     * has an error and doesn't decrypt properly...  disable\n     * for now because we are going to upgrade to something\n     * better.\n     */\n    uint32_t ciphertext_len = _send_aes.encode( buffer, len, _write_buffer.get() );\n    assert(ciphertext_len == len);\n    _sock.write( _write_buffer, ciphertext_len );\n    return ciphertext_len;\n} FC_RETHROW_EXCEPTIONS( warn, \"\", (\"len\",len) ) }\n\nsize_t stcp_socket::writesome( const std::shared_ptr<const char>& buf, size_t len, size_t offset )\n{\n  return writesome(buf.get() + offset, len);\n}\n\nvoid stcp_socket::flush()\n{\n  _sock.flush();\n}\n\n\nvoid stcp_socket::close()\n{\n  try \n  {\n    _sock.close();\n  }FC_RETHROW_EXCEPTIONS( warn, \"error closing stcp socket\" );\n}\n\nvoid stcp_socket::accept()\n{\n  do_key_exchange();\n}\n\n\n}} // namespace graphene::net\n\n"
  },
  {
    "path": "libraries/plugins/CMakeLists.txt",
    "content": "add_subdirectory( witness )\nadd_subdirectory( account_history )\nadd_subdirectory( elasticsearch )\nadd_subdirectory( market_history )\nadd_subdirectory( grouped_orders )\nadd_subdirectory( delayed_node )\nadd_subdirectory( debug_witness )\nadd_subdirectory( snapshot )\nadd_subdirectory( es_objects )\nadd_subdirectory( api_helper_indexes )\nadd_subdirectory( custom_operations )\n"
  },
  {
    "path": "libraries/plugins/README.md",
    "content": "# BitShares Plugins\n\nThe bitshares plugins are a collection of tools that brings new functionality without the need of modifications in the consensus and more sensitive areas of the bitshares-core.\n\nThe main source of I/O of the bitshares blockchain is the API. Plugins are a more powerful alternative to build more complex developments for when the current API is not enough.\n\nPlugins are optional to run by node operator according to their needs. However, all plugins here will be compiled. There are plans for optional build of plugins at: [Issue 533](https://github.com/bitshares/bitshares-core/issues/533).\n\nThe [make_new_plugin.sh](make_new_plugin.sh) script can be used to create a skeleton of a new plugin quickly from a [template](template_plugin).\n\n# Available Plugins\n\nFolder                             | Name                     | Description                                                                 | Category       | Status        | SpaceID     \n-----------------------------------|--------------------------|-----------------------------------------------------------------------------|----------------|---------------|--------------|\n[account_history](account_history) | Account History          | Save account history data                                                   | History        | Stable        | 4\n[api_helper_indexes](api_helper_indexes) | API Helper Indexes | Provides some helper indexes used by various API calls                                                 | Database API   | Stable        | \n[custom_operations](custom_operations) | Custom Operations    | Store and retrieve account catalogs of key=>value data using custom operations | Additional data   | Experimental        | 7\n[debug_witness](debug_witness)     | Debug Witness            | Run \"what-if\" tests                                                         | Debug          | Stable        |\n[delayed_node](delayed_node)       | Delayed Node             | Avoid forks by running a several times confirmed and delayed blockchain     | Business       | Stable        |\n[elasticsearch](elasticsearch)     | ElasticSearch Operations | Save account history data into elasticsearch database                       | History        | Experimental  | 6\n[es_objects](es_objects)           | ElasticSearch Objects    | Save selected objects into elasticsearch database                           | History        | Experimental  |\n[grouped_orders](grouped_orders)   | Grouped Orders           | Expose api to create a grouped order book of bitshares markets              | Market data    | Experimental  |\n[market_history](market_history)   | Market History           | Save market history data                                                    | Market data    | Stable        | 5\n[snapshot](snapshot)               | Snapshot                 | Get a json of all objects in blockchain at a specificed time or block       | Debug          | Stable        | \n[witness](witness)                 | Witness                  | Generate and sign blocks                                                    | Block producer | Stable        | \n"
  },
  {
    "path": "libraries/plugins/account_history/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/account_history/*.hpp\")\n\nadd_library( graphene_account_history \n             account_history_plugin.cpp\n           )\n\ntarget_link_libraries( graphene_account_history graphene_app graphene_chain )\ntarget_include_directories( graphene_account_history\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties( account_history_plugin.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_account_history\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/account_history\" )\n\n"
  },
  {
    "path": "libraries/plugins/account_history/account_history_plugin.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/account_history/account_history_plugin.hpp>\n\n#include <graphene/chain/impacted.hpp>\n\n#include <graphene/chain/account_evaluator.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/config.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/transaction_evaluation_state.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/utilities/boost_program_options.hpp>\n\n#include <fc/thread/thread.hpp>\n\nnamespace graphene { namespace account_history {\n\nnamespace detail\n{\n\n\nclass account_history_plugin_impl\n{\n   public:\n      explicit account_history_plugin_impl(account_history_plugin& _plugin)\n         : _self( _plugin )\n      { }\n\n   private:\n      /** this method is called as a callback after a block is applied\n       * and will process/index all operations that were applied in the block.\n       */\n      void update_account_histories( const signed_block& b );\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n      friend class graphene::account_history::account_history_plugin;\n\n      account_history_plugin& _self;\n      flat_set<account_id_type> _tracked_accounts;\n      flat_set<account_id_type> _extended_history_accounts;\n      flat_set<account_id_type> _extended_history_registrars;\n      bool _partial_operations = false;\n      primary_index< operation_history_index >* _oho_index;\n      uint64_t _max_ops_per_account = -1;\n      uint64_t _extended_max_ops_per_account = -1;\n      uint32_t _min_blocks_to_keep = 30000;\n      uint64_t _max_ops_per_acc_by_min_blocks = 1000;\n\n      uint32_t _latest_block_number_to_remove = 0;\n\n      uint64_t get_max_ops_to_keep( const account_id_type& account_id );\n\n      /** add one history record, then check and remove the earliest history record(s) */\n      void add_account_history( const account_id_type& account_id, const operation_history_object& op );\n\n      void remove_old_histories_by_account( const account_statistics_object& stats_obj,\n                                            const exceeded_account_object* p_exa_obj = nullptr );\n\n      void remove_old_histories();\n\n      /// When the partial_operations option is set,\n      /// if the specified operation history object is no longer referenced, remove it from database\n      void check_and_remove_op_history_obj( const operation_history_object& op );\n\n      void init_program_options(const boost::program_options::variables_map& options);\n};\n\ntemplate< typename T >\nstatic T get_biggest_number_to_remove( T biggest_number, T amount_to_keep )\n{\n   return ( biggest_number > amount_to_keep ) ? ( biggest_number - amount_to_keep ) : 0;\n}\n\nvoid account_history_plugin_impl::update_account_histories( const signed_block& b )\n{\n   _latest_block_number_to_remove = get_biggest_number_to_remove( b.block_num(), _min_blocks_to_keep );\n\n   graphene::chain::database& db = database();\n   const vector<optional< operation_history_object > >& hist = db.get_applied_operations();\n   bool is_first = true;\n   auto skip_oho_id = [&is_first,&db,this]() {\n      if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo\n      {\n         db.remove( db.create<operation_history_object>( []( operation_history_object& obj) {} ) );\n         is_first = false;\n      }\n      else\n         _oho_index->use_next_id();\n   };\n\n   for( const optional< operation_history_object >& o_op : hist )\n   {\n      optional<operation_history_object> oho;\n\n      auto create_oho = [&]() {\n         is_first = false;\n         return optional<operation_history_object>( db.create<operation_history_object>( [&]( operation_history_object& h )\n         {\n            if( o_op.valid() )\n            {\n               h.op           = o_op->op;\n               h.result       = o_op->result;\n               h.block_num    = o_op->block_num;\n               h.trx_in_block = o_op->trx_in_block;\n               h.op_in_trx    = o_op->op_in_trx;\n               h.virtual_op   = o_op->virtual_op;\n               h.is_virtual   = o_op->is_virtual;\n               h.block_time   = o_op->block_time;\n            }\n         } ) );\n      };\n\n      if( !o_op.valid() || ( _max_ops_per_account == 0 && _partial_operations ) )\n      {\n         // Note: the 2nd and 3rd checks above are for better performance, when the db is not clean,\n         //       they will break consistency of account_stats.total_ops and removed_ops and most_recent_op\n         skip_oho_id();\n         continue;\n      }\n      else if( !_partial_operations )\n         // add to the operation history index\n         oho = create_oho();\n\n      const operation_history_object& op = *o_op;\n\n      // get the set of accounts this operation applies to\n      flat_set<account_id_type> impacted;\n      vector<authority> other;\n      // fee payer is added here\n      operation_get_required_authorities( op.op, impacted, impacted, other,\n                                          MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );\n\n      if( op.op.is_type< account_create_operation >() )\n         impacted.insert( account_id_type( op.result.get<object_id_type>() ) );\n\n      // https://github.com/bitshares/bitshares-core/issues/265\n      if( HARDFORK_CORE_265_PASSED(b.timestamp) || !op.op.is_type< account_create_operation >() )\n      {\n         operation_get_impacted_accounts( op.op, impacted,\n                                          MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );\n      }\n\n      if( op.result.is_type<extendable_operation_result>() )\n      {\n         const auto& op_result = op.result.get<extendable_operation_result>();\n         if( op_result.value.impacted_accounts.valid() )\n         {\n            for( const auto& a : *op_result.value.impacted_accounts )\n               impacted.insert( a );\n         }\n      }\n\n      for( auto& a : other )\n         for( auto& item : a.account_auths )\n            impacted.insert( item.first );\n\n      // be here, either _max_ops_per_account > 0, or _partial_operations == false, or both\n      // if _partial_operations == false, oho should have been created above\n      // so the only case should be checked here is:\n      //    whether need to create oho if _max_ops_per_account > 0 and _partial_operations == true\n\n      // for each operation this account applies to that is in the config link it into the history\n      if( _tracked_accounts.size() == 0 ) // tracking all accounts\n      {\n         // if tracking all accounts, when impacted is not empty (although it will always be),\n         //    still need to create oho if _max_ops_per_account > 0 and _partial_operations == true\n         //    so always need to create oho if not done\n         if (!impacted.empty() && !oho.valid()) { oho = create_oho(); }\n\n         if( _max_ops_per_account > 0 )\n         {\n            // Note: the check above is for better performance, when the db is not clean,\n            //       it breaks consistency of account_stats.total_ops and removed_ops and most_recent_op,\n            //       but it ensures it's safe to remove old entries in add_account_history(...)\n            for( auto& account_id : impacted )\n            {\n               // we don't do index_account_keys here anymore, because\n               // that indexing now happens in observers' post_evaluate()\n\n               // add history\n               add_account_history( account_id, *oho );\n            }\n         }\n      }\n      else // tracking a subset of accounts\n      {\n         // whether need to create oho if _max_ops_per_account > 0 and _partial_operations == true ?\n         // the answer: only need to create oho if a tracked account is impacted and need to save history\n\n         if( _max_ops_per_account > 0 )\n         {\n            // Note: the check above is for better performance, when the db is not clean,\n            //       it breaks consistency of account_stats.total_ops and removed_ops and most_recent_op,\n            //       but it ensures it's safe to remove old entries in add_account_history(...)\n            for( auto account_id : _tracked_accounts )\n            {\n               if( impacted.find( account_id ) != impacted.end() )\n               {\n                  if (!oho.valid()) { oho = create_oho(); }\n                  // add history\n                  add_account_history( account_id, *oho );\n               }\n            }\n         }\n      }\n      if (_partial_operations && ! oho.valid())\n         skip_oho_id();\n   }\n\n   remove_old_histories();\n}\n\nvoid account_history_plugin_impl::add_account_history( const account_id_type& account_id,\n                                                       const operation_history_object& op )\n{\n   graphene::chain::database& db = database();\n   const auto& stats_obj = db.get_account_stats_by_owner( account_id );\n   // add new entry\n   const auto& aho = db.create<account_history_object>( [&account_id,&op,&stats_obj](account_history_object& obj){\n       obj.operation_id = op.id;\n       obj.account = account_id;\n       obj.sequence = stats_obj.total_ops + 1;\n       obj.next = stats_obj.most_recent_op;\n   });\n   db.modify( stats_obj, [&aho]( account_statistics_object& obj ){\n       obj.most_recent_op = aho.id;\n       obj.total_ops = aho.sequence;\n   });\n   // Remove the earliest account history entries if too many.\n   remove_old_histories_by_account( stats_obj );\n}\n\nuint64_t account_history_plugin_impl::get_max_ops_to_keep( const account_id_type& account_id )\n{\n   const graphene::chain::database& db = database();\n   // Amount of history to keep depends on if account is in the \"extended history\" list\n   bool extended_hist = ( _extended_history_accounts.find( account_id ) != _extended_history_accounts.end() );\n   if( !extended_hist && !_extended_history_registrars.empty() )\n   {\n      const account_id_type& registrar_id = account_id(db).registrar;\n      extended_hist = ( _extended_history_registrars.find( registrar_id ) != _extended_history_registrars.end() );\n   }\n   // _max_ops_per_account is guaranteed to be non-zero outside; max_ops_to_keep\n   // will likewise be non-zero, and also non-negative (it is unsigned).\n   auto max_ops_to_keep = _max_ops_per_account;\n   if( extended_hist && _extended_max_ops_per_account > max_ops_to_keep )\n   {\n      max_ops_to_keep = _extended_max_ops_per_account;\n   }\n   if( 0 == max_ops_to_keep )\n      return 1;\n   return max_ops_to_keep;\n}\n\nvoid account_history_plugin_impl::remove_old_histories()\n{\n   if( 0 == _latest_block_number_to_remove )\n      return;\n\n   const graphene::chain::database& db = database();\n   const auto& exa_idx = db.get_index_type<exceeded_account_index>().indices().get<by_block_num>();\n   auto itr = exa_idx.begin();\n   while( itr != exa_idx.end() && itr->block_num <= _latest_block_number_to_remove )\n   {\n      const auto& stats_obj = db.get_account_stats_by_owner( itr->account_id );\n      remove_old_histories_by_account( stats_obj, &(*itr) );\n      itr = exa_idx.begin();\n   }\n}\n\nvoid account_history_plugin_impl::check_and_remove_op_history_obj( const operation_history_object& op )\n{\n   if( _partial_operations )\n   {\n      // check for references\n      graphene::chain::database& db = database();\n      const auto& his_idx = db.get_index_type<account_history_index>();\n      const auto& by_opid_idx = his_idx.indices().get<by_opid>();\n      if( by_opid_idx.find( op.get_id() ) == by_opid_idx.end() )\n      {\n         // if no reference, remove\n         db.remove( op );\n      }\n   }\n}\n\n// Remove the earliest account history entries if too many.\nvoid account_history_plugin_impl::remove_old_histories_by_account( const account_statistics_object& stats_obj,\n                                                                   const exceeded_account_object* p_exa_obj )\n{\n   graphene::chain::database& db = database();\n   const account_id_type& account_id = stats_obj.owner;\n   auto max_ops_to_keep = get_max_ops_to_keep( account_id ); // >= 1\n   auto number_of_ops_to_remove = get_biggest_number_to_remove( stats_obj.total_ops, max_ops_to_keep );\n   auto number_of_ops_to_remove_by_blks = get_biggest_number_to_remove( stats_obj.total_ops,\n                                                                        _max_ops_per_acc_by_min_blocks );\n\n   const auto& his_idx = db.get_index_type<account_history_index>();\n   const auto& by_seq_idx = his_idx.indices().get<by_seq>();\n\n   auto removed_ops = stats_obj.removed_ops;\n   // look for the earliest entry if needed\n   auto aho_itr = ( removed_ops < number_of_ops_to_remove ) ? by_seq_idx.lower_bound( account_id )\n                                                            : by_seq_idx.begin();\n\n   uint32_t oldest_block_num = _latest_block_number_to_remove;\n   while( removed_ops < number_of_ops_to_remove )\n   {\n      // make sure don't remove the latest one\n      // this should always be false, just check to be safe\n      if( aho_itr == by_seq_idx.end() || aho_itr->account != account_id || aho_itr->id == stats_obj.most_recent_op )\n         break;\n\n      // if found, check whether to remove\n      const auto& aho_to_remove = *aho_itr;\n      const auto& remove_op = aho_to_remove.operation_id(db);\n      oldest_block_num = remove_op.block_num;\n      if( remove_op.block_num > _latest_block_number_to_remove && removed_ops >= number_of_ops_to_remove_by_blks )\n         break;\n\n      // remove the entry\n      ++aho_itr;\n      db.remove( aho_to_remove );\n      ++removed_ops;\n\n      // remove the operation history entry (1.11.x) if configured and no reference left\n      check_and_remove_op_history_obj( remove_op );\n   }\n   // adjust account stats object and the oldest entry\n   if( removed_ops != stats_obj.removed_ops )\n   {\n      db.modify( stats_obj, [removed_ops]( account_statistics_object& obj ){\n          obj.removed_ops = removed_ops;\n      });\n      // modify previous node's next pointer\n      // this should be always true, but just have a check here\n      if( aho_itr != by_seq_idx.end() && aho_itr->account == account_id )\n      {\n         db.modify( *aho_itr, []( account_history_object& obj ){\n            obj.next = account_history_id_type();\n         });\n      }\n      // else need to modify the head pointer, but it shouldn't be true\n   }\n   // deal with exceeded_account_object\n   if( !p_exa_obj )\n   {\n      const auto& exa_idx = db.get_index_type<exceeded_account_index>().indices().get<by_account>();\n      auto exa_itr = exa_idx.find( account_id );\n      if( exa_itr != exa_idx.end() )\n         p_exa_obj = &(*exa_itr);\n   }\n   if( stats_obj.removed_ops < number_of_ops_to_remove )\n   {\n      // create or update exceeded_account_object\n      if( p_exa_obj )\n         db.modify( *p_exa_obj, [oldest_block_num]( exceeded_account_object& obj ){\n            obj.block_num = oldest_block_num;\n         });\n      else\n         db.create<exceeded_account_object>(\n               [&account_id, oldest_block_num]( exceeded_account_object& obj ){\n            obj.account_id = account_id;\n            obj.block_num = oldest_block_num;\n         });\n   }\n   // remove exceeded_account_object if found\n   else if( p_exa_obj )\n      db.remove( *p_exa_obj );\n}\n\n} // end namespace detail\n\n\naccount_history_plugin::account_history_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::account_history_plugin_impl>(*this) )\n{\n   // Nothing else to do\n}\n\naccount_history_plugin::~account_history_plugin() = default;\n\nstd::string account_history_plugin::plugin_name()const\n{\n   return \"account_history\";\n}\n\nvoid account_history_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"track-account\", boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),\n          \"Account ID to track history for (may specify multiple times; if unset will track all accounts)\")\n         (\"partial-operations\", boost::program_options::value<bool>(),\n          \"Keep only those operations in memory that are related to account history tracking\")\n         (\"max-ops-per-account\", boost::program_options::value<uint64_t>(),\n          \"Maximum number of operations per account that will be kept in memory. \"\n          \"Note that the actual number may be higher due to the min-blocks-to-keep option.\")\n         (\"extended-max-ops-per-account\", boost::program_options::value<uint64_t>(),\n          \"Maximum number of operations to keep for accounts for which extended history is kept. \"\n          \"This option only takes effect when track-account is not used and max-ops-per-account is not zero.\")\n         (\"extended-history-by-account\",\n          boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),\n          \"Track longer history for these accounts (may specify multiple times)\")\n         (\"extended-history-by-registrar\",\n          boost::program_options::value<std::vector<std::string>>()->composing()->multitoken(),\n          \"Track longer history for accounts with this registrar (may specify multiple times)\")\n         (\"min-blocks-to-keep\", boost::program_options::value<uint32_t>(),\n          \"Operations which are in the latest X blocks will be kept in memory. \"\n          \"This option only takes effect when track-account is not used and max-ops-per-account is not zero. \"\n          \"Note that this option may cause more history records to be kept in memory than the limit defined by the \"\n          \"max-ops-per-account option, but the amount will be limited by the max-ops-per-acc-by-min-blocks option. \"\n          \"(default: 30000)\")\n         (\"max-ops-per-acc-by-min-blocks\", boost::program_options::value<uint64_t>(),\n          \"A potential higher limit on the maximum number of operations per account to be kept in memory \"\n          \"when the min-blocks-to-keep option causes the amount to exceed the limit defined by the \"\n          \"max-ops-per-account option. If this is less than max-ops-per-account, max-ops-per-account will be used. \"\n          \"(default: 1000)\")\n         ;\n   cfg.add(cli);\n}\n\nvoid account_history_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{\n   my->init_program_options( options );\n\n   // connect with group 0 to process before some special steps (e.g. snapshot or next_object_id)\n   database().applied_block.connect( 0, [this]( const signed_block& b){ my->update_account_histories(b); } );\n   my->_oho_index = database().add_index< primary_index< operation_history_index > >();\n   database().add_index< primary_index< account_history_index > >();\n\n   database().add_index< primary_index< exceeded_account_index > >();\n}\n\nvoid detail::account_history_plugin_impl::init_program_options(const boost::program_options::variables_map& options)\n{\n   LOAD_VALUE_SET(options, \"track-account\", _tracked_accounts, graphene::chain::account_id_type);\n\n   utilities::get_program_option( options, \"partial-operations\", _partial_operations );\n   utilities::get_program_option( options, \"max-ops-per-account\", _max_ops_per_account );\n   utilities::get_program_option( options, \"extended-max-ops-per-account\", _extended_max_ops_per_account );\n   if( _extended_max_ops_per_account < _max_ops_per_account )\n      _extended_max_ops_per_account = _max_ops_per_account;\n\n   LOAD_VALUE_SET(options, \"extended-history-by-account\", _extended_history_accounts,\n                  graphene::chain::account_id_type);\n   LOAD_VALUE_SET(options, \"extended-history-by-registrar\", _extended_history_registrars,\n                  graphene::chain::account_id_type);\n\n   utilities::get_program_option( options, \"min-blocks-to-keep\", _min_blocks_to_keep );\n   utilities::get_program_option( options, \"max-ops-per-acc-by-min-blocks\", _max_ops_per_acc_by_min_blocks );\n   if( _max_ops_per_acc_by_min_blocks < _max_ops_per_account )\n      _max_ops_per_acc_by_min_blocks = _max_ops_per_account;\n}\n\nvoid account_history_plugin::plugin_startup()\n{\n}\n\nflat_set<account_id_type> account_history_plugin::tracked_accounts() const\n{\n   return my->_tracked_accounts;\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace account_history {\n   using namespace chain;\n\n//\n// Plugins should #define their SPACE_ID's so plugins with\n// conflicting SPACE_ID assignments can be compiled into the\n// same binary (by simply re-assigning some of the conflicting #defined\n// SPACE_ID's in a build script).\n//\n// Assignment of SPACE_ID's cannot be done at run-time because\n// various template automagic depends on them being known at compile\n// time.\n//\n#ifndef ACCOUNT_HISTORY_SPACE_ID\n#define ACCOUNT_HISTORY_SPACE_ID 4\n#endif\n\nenum account_history_object_type\n{\n   exceeded_account_object_type = 0\n};\n\n/// This struct tracks accounts that have exceeded the max-ops-per-account limit\nstruct exceeded_account_object : public abstract_object<exceeded_account_object,\n                                           ACCOUNT_HISTORY_SPACE_ID, exceeded_account_object_type>\n{\n   /// The ID of the account\n   account_id_type account_id;\n   /// The height of the block containing the oldest (not yet removed) operation related to this account\n   uint32_t        block_num;\n};\n\nstruct by_account;\nstruct by_block_num;\nusing exceeded_account_multi_idx_type = multi_index_container<\n   exceeded_account_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_account>,\n         member< exceeded_account_object, account_id_type, &exceeded_account_object::account_id > >,\n      ordered_unique< tag<by_block_num>,\n         composite_key<\n            exceeded_account_object,\n            member< exceeded_account_object, uint32_t, &exceeded_account_object::block_num >,\n            member< object, object_id_type, &object::id >\n         >\n      >\n   >\n>;\n\nusing exceeded_account_index = generic_index< exceeded_account_object, exceeded_account_multi_idx_type >;\n\nnamespace detail\n{\n    class account_history_plugin_impl;\n}\n\nclass account_history_plugin : public graphene::app::plugin\n{\n   public:\n      explicit account_history_plugin(graphene::app::application& app);\n      ~account_history_plugin() override;\n\n      std::string plugin_name()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n\n      flat_set<account_id_type> tracked_accounts()const;\n\n   private:\n      std::unique_ptr<detail::account_history_plugin_impl> my;\n};\n\n} } //graphene::account_history\n\nFC_REFLECT_DERIVED( graphene::account_history::exceeded_account_object, (graphene::db::object),\n                    (account_id)(block_num) )\n"
  },
  {
    "path": "libraries/plugins/api_helper_indexes/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/api_helper_indexes/*.hpp\")\n\nadd_library( graphene_api_helper_indexes\n        api_helper_indexes.cpp\n           )\n\ntarget_link_libraries( graphene_api_helper_indexes graphene_app graphene_chain )\ntarget_include_directories( graphene_api_helper_indexes\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties(api_helper_indexes.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_api_helper_indexes\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/api_helper_indexes\" )\n"
  },
  {
    "path": "libraries/plugins/api_helper_indexes/api_helper_indexes.cpp",
    "content": "/*\n * Copyright (c) 2018 api_helper_indexes and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/api_helper_indexes/api_helper_indexes.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/chain_property_object.hpp>\n\nnamespace graphene { namespace api_helper_indexes {\n\nvoid amount_in_collateral_index::object_inserted( const object& objct )\n{ try {\n   const call_order_object& o = static_cast<const call_order_object&>( objct );\n\n   {\n      auto itr = in_collateral.find( o.collateral_type() );\n      if( itr == in_collateral.end() )\n         in_collateral[o.collateral_type()] = o.collateral;\n      else\n         itr->second += o.collateral;\n   }\n\n   {\n      auto itr = backing_collateral.find( o.debt_type() );\n      if( itr == backing_collateral.end() )\n         backing_collateral[o.debt_type()] = o.collateral;\n      else\n         itr->second += o.collateral;\n   }\n\n} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE\n\nvoid amount_in_collateral_index::object_removed( const object& objct )\n{ try {\n   const call_order_object& o = static_cast<const call_order_object&>( objct );\n\n   {\n      auto itr = in_collateral.find( o.collateral_type() );\n      if( itr != in_collateral.end() ) // should always be true\n         itr->second -= o.collateral;\n   }\n\n   {\n      auto itr = backing_collateral.find( o.debt_type() );\n      if( itr != backing_collateral.end() ) // should always be true\n         itr->second -= o.collateral;\n   }\n\n} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE\n\nvoid amount_in_collateral_index::about_to_modify( const object& objct )\n{ try {\n   object_removed( objct );\n} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE\n\nvoid amount_in_collateral_index::object_modified( const object& objct )\n{ try {\n   object_inserted( objct );\n} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE\n\nshare_type amount_in_collateral_index::get_amount_in_collateral( const asset_id_type& asst )const\n{ try {\n   auto itr = in_collateral.find( asst );\n   if( itr == in_collateral.end() ) return 0;\n   return itr->second;\n} FC_CAPTURE_AND_RETHROW( (asst) ) } // GCOVR_EXCL_LINE\n\nshare_type amount_in_collateral_index::get_backing_collateral( const asset_id_type& asst )const\n{ try {\n   auto itr = backing_collateral.find( asst );\n   if( itr == backing_collateral.end() ) return 0;\n   return itr->second;\n} FC_CAPTURE_AND_RETHROW( (asst) ) } // GCOVR_EXCL_LINE\n\nvoid asset_in_liquidity_pools_index::object_inserted( const object& objct )\n{ try {\n   const auto& o = static_cast<const liquidity_pool_object&>( objct );\n   const liquidity_pool_id_type pool_id = o.get_id();\n   asset_in_pools_map[ o.asset_a ].insert( pool_id ); // Note: [] operator will create an entry if not found\n   asset_in_pools_map[ o.asset_b ].insert( pool_id );\n} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE\n\nvoid asset_in_liquidity_pools_index::object_removed( const object& objct )\n{ try {\n   const auto& o = static_cast<const liquidity_pool_object&>( objct );\n   const liquidity_pool_id_type pool_id = o.get_id();\n   asset_in_pools_map[ o.asset_a ].erase( pool_id );\n   asset_in_pools_map[ o.asset_b ].erase( pool_id );\n   // Note: do not erase entries with an empty set from the map in order to avoid read/write race conditions\n} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE\n\nvoid asset_in_liquidity_pools_index::about_to_modify( const object& objct )\n{\n   // this secondary index has no interest in the modifications, nothing to do here\n}\n\nvoid asset_in_liquidity_pools_index::object_modified( const object& objct )\n{\n   // this secondary index has no interest in the modifications, nothing to do here\n}\n\nconst flat_set<liquidity_pool_id_type>& asset_in_liquidity_pools_index::get_liquidity_pools_by_asset(\n            const asset_id_type& a )const\n{\n   auto itr = asset_in_pools_map.find( a );\n   if( itr != asset_in_pools_map.end() )\n      return itr->second;\n   return empty_set;\n}\n\nnamespace detail\n{\n\nclass api_helper_indexes_impl\n{\n   public:\n      explicit api_helper_indexes_impl(api_helper_indexes& _plugin)\n         : _self( _plugin )\n      {  }\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n   private:\n      api_helper_indexes& _self;\n};\n\n} // end namespace detail\n\napi_helper_indexes::api_helper_indexes(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::api_helper_indexes_impl>(*this) )\n{\n   // Nothing else to do\n}\n\napi_helper_indexes::~api_helper_indexes() = default;\n\nstd::string api_helper_indexes::plugin_name()const\n{\n   return \"api_helper_indexes\";\n}\nstd::string api_helper_indexes::plugin_description()const\n{\n   return \"Provides some helper indexes used by various API calls\";\n}\n\nvoid api_helper_indexes::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n}\n\nvoid api_helper_indexes::plugin_initialize(const boost::program_options::variables_map& options)\n{\n}\n\nvoid api_helper_indexes::plugin_startup()\n{\n   ilog(\"api_helper_indexes: plugin_startup() begin\");\n   amount_in_collateral_idx = database().add_secondary_index< primary_index<call_order_index>,\n                                                              amount_in_collateral_index >();\n   for( const auto& call : database().get_index_type<call_order_index>().indices() )\n      amount_in_collateral_idx->object_inserted( call );\n\n   auto& account_members = *database().add_secondary_index< primary_index<account_index>, account_member_index >();\n   for( const auto& account : database().get_index_type< account_index >().indices() )\n      account_members.object_inserted( account );\n\n   auto& approvals = *database().add_secondary_index< primary_index<proposal_index>, required_approval_index >();\n   for( const auto& proposal : database().get_index_type< proposal_index >().indices() )\n      approvals.object_inserted( proposal );\n\n   asset_in_liquidity_pools_idx = database().add_secondary_index< primary_index<liquidity_pool_index>,\n                                                        asset_in_liquidity_pools_index >();\n   for( const auto& pool : database().get_index_type<liquidity_pool_index>().indices() )\n      asset_in_liquidity_pools_idx->object_inserted( pool );\n\n   next_object_ids_idx = database().add_secondary_index< primary_index<simple_index<chain_property_object>>,\n                                                        next_object_ids_index >();\n   refresh_next_ids();\n   // connect with no group specified to process after the ones with a group specified\n   database().applied_block.connect( [this]( const chain::signed_block& )\n   {\n      refresh_next_ids();\n      _next_ids_map_initialized = true;\n   });\n}\n\nvoid api_helper_indexes::refresh_next_ids()\n{\n   const auto& db = database();\n   if( _next_ids_map_initialized )\n   {\n      for( auto& item : next_object_ids_idx->_next_ids )\n      {\n         item.second = db.get_index( item.first.first, item.first.second ).get_next_id();\n      }\n      return;\n   }\n\n   // Assuming that all indexes have been created when processing the first block,\n   // for better performance, only do this twice, one on plugin startup, the other on the first block.\n   size_t count = 0;\n   size_t failed_count = 0;\n   for( uint8_t space = 0; space < chain::database::_index_size; ++space )\n   {\n      for( uint8_t type = 0; type < chain::database::_index_size; ++type )\n      {\n         try\n         {\n            const auto& idx = db.get_index( space, type );\n            next_object_ids_idx->_next_ids[ std::make_pair( space, type ) ] = idx.get_next_id();\n            ++count;\n         }\n         catch( const fc::exception& )\n         {\n            ++failed_count;\n         }\n      }\n   }\n   dlog( \"${count} indexes detected, ${failed_count} not found\", (\"count\",count)(\"failed_count\",failed_count) );\n}\n\nobject_id_type next_object_ids_index::get_next_id( uint8_t space_id, uint8_t type_id ) const\n{ try {\n   return _next_ids.at( std::make_pair( space_id, type_id ) );\n} FC_CAPTURE_AND_RETHROW( (space_id)(type_id) ) } // GCOVR_EXCL_LINE\n\n} }\n"
  },
  {
    "path": "libraries/plugins/api_helper_indexes/include/graphene/api_helper_indexes/api_helper_indexes.hpp",
    "content": "/*\n * Copyright (c) 2018 api_helper_indexes and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace api_helper_indexes {\nusing namespace chain;\n\n/**\n *  @brief This secondary index tracks how much of each asset is locked up as collateral for MPAs, and how much\n *         collateral is backing an MPA in total.\n *  @note This is implemented with \\c flat_map considering there aren't too many MPAs and PMs in the system thus\n *        the performance would be acceptable.\n */\nclass amount_in_collateral_index : public secondary_index\n{\n   public:\n      void object_inserted( const object& obj ) override;\n      void object_removed( const object& obj ) override;\n      void about_to_modify( const object& before ) override;\n      void object_modified( const object& after ) override;\n\n      share_type get_amount_in_collateral( const asset_id_type& asset )const;\n      share_type get_backing_collateral( const asset_id_type& asset )const;\n\n   private:\n      flat_map<asset_id_type, share_type> in_collateral;\n      flat_map<asset_id_type, share_type> backing_collateral;\n};\n\n/**\n *  @brief This secondary index maintains a map to make it easier to find liquidity pools by any asset in the pool.\n *  @note This is implemented with \\c flat_map and \\c flat_set considering there aren't too many liquidity pools\n *        in the system thus the performance would be acceptable.\n */\nclass asset_in_liquidity_pools_index: public secondary_index\n{\n   public:\n      void object_inserted( const object& obj ) override;\n      void object_removed( const object& obj ) override;\n      void about_to_modify( const object& before ) override;\n      void object_modified( const object& after ) override;\n\n      const flat_set<liquidity_pool_id_type>& get_liquidity_pools_by_asset( const asset_id_type& a )const;\n\n   private:\n      flat_set<liquidity_pool_id_type> empty_set;\n      flat_map<asset_id_type, flat_set<liquidity_pool_id_type>> asset_in_pools_map;\n};\n\n/**\n *  @brief This secondary index tracks the next ID of all object types.\n *  @note This is implemented with \\c flat_map considering there aren't too many object types in the system thus\n *        the performance would be acceptable.\n */\nclass next_object_ids_index : public secondary_index\n{\n   public:\n      object_id_type get_next_id( uint8_t space_id, uint8_t type_id ) const;\n\n   private:\n      friend class api_helper_indexes;\n      flat_map< std::pair<uint8_t,uint8_t>, object_id_type > _next_ids;\n};\n\nnamespace detail\n{\n    class api_helper_indexes_impl;\n}\n\nclass api_helper_indexes : public graphene::app::plugin\n{\n   public:\n      explicit api_helper_indexes(graphene::app::application& app);\n      ~api_helper_indexes() override;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n\n      friend class detail::api_helper_indexes_impl;\n\n   private:\n      std::unique_ptr<detail::api_helper_indexes_impl> my;\n      amount_in_collateral_index* amount_in_collateral_idx = nullptr;\n      asset_in_liquidity_pools_index* asset_in_liquidity_pools_idx = nullptr;\n      next_object_ids_index* next_object_ids_idx = nullptr;\n\n      bool _next_ids_map_initialized = false;\n      void refresh_next_ids();\n};\n\n} } //graphene::template\n"
  },
  {
    "path": "libraries/plugins/custom_operations/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/custom_operations/*.hpp\")\n\nadd_library( graphene_custom_operations\n        custom_operations_plugin.cpp\n        custom_operations.cpp\n        custom_evaluators.cpp\n           )\n\ntarget_link_libraries( graphene_custom_operations graphene_app graphene_chain )\ntarget_include_directories( graphene_custom_operations\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties(custom_operations_plugin.cpp custom_operations.cpp custom_evaluators.cpp\n          PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_custom_operations\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/custom_operations\" )\n"
  },
  {
    "path": "libraries/plugins/custom_operations/custom_evaluators.cpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n#include <graphene/custom_operations/custom_objects.hpp>\n#include <graphene/custom_operations/custom_evaluators.hpp>\n\nnamespace graphene { namespace custom_operations {\n\ncustom_generic_evaluator::custom_generic_evaluator(database& db, const account_id_type account)\n{\n   _db = &db;\n   _account = account;\n}\n\nvector<object_id_type> custom_generic_evaluator::do_apply(const account_storage_map& op)\n{\n   const auto &index = _db->get_index_type<account_storage_index>().indices().get<by_account_catalog_key>();\n   vector<object_id_type> results;\n   results.reserve( op.key_values.size() );\n\n   if (op.remove)\n   {\n      for(auto const& row: op.key_values) {\n         auto itr = index.find(make_tuple(_account, op.catalog, row.first));\n         if(itr != index.end()) {\n            results.push_back(itr->id);\n            _db->remove(*itr);\n         }\n      }\n   }\n   else {\n      for(auto const& row: op.key_values) {\n         if(row.first.length() > CUSTOM_OPERATIONS_MAX_KEY_SIZE)\n         {\n            wlog(\"Key can't be bigger than ${max} characters\", (\"max\", CUSTOM_OPERATIONS_MAX_KEY_SIZE));\n            continue;\n         }\n         auto itr = index.find(make_tuple(_account, op.catalog, row.first));\n         if(itr == index.end())\n         {\n            try {\n               const auto& created = _db->create<account_storage_object>(\n                                        [&op, this, &row]( account_storage_object& aso ) {\n                  aso.account = _account;\n                  aso.catalog = op.catalog;\n                  aso.key = row.first;\n                  if(row.second.valid())\n                     aso.value = fc::json::from_string(*row.second);\n               });\n               results.push_back(created.id);\n            }\n            catch(const fc::parse_error_exception& e) { wlog(e.to_detail_string()); }\n         }\n         else\n         {\n            try {\n               _db->modify(*itr, [&row](account_storage_object &aso) {\n                  if(row.second.valid())\n                     aso.value = fc::json::from_string(*row.second);\n                  else\n                     aso.value.reset();\n               });\n               results.push_back(itr->id);\n            }\n            catch(const fc::parse_error_exception& e) { wlog((e.to_detail_string())); }\n         }\n      }\n   }\n   return results;\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/custom_operations/custom_operations.cpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/custom_operations/custom_operations.hpp>\n\nnamespace graphene { namespace custom_operations {\n\nvoid account_storage_map::validate()const\n{\n   FC_ASSERT(catalog.length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE && catalog.length() > 0);\n}\n\n} } //graphene::custom_operations\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map )\n"
  },
  {
    "path": "libraries/plugins/custom_operations/custom_operations_plugin.cpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n\n#include <graphene/chain/operation_history_object.hpp>\n\nnamespace graphene { namespace custom_operations {\n\nnamespace detail\n{\nclass custom_operations_plugin_impl\n{\n   public:\n      explicit custom_operations_plugin_impl(custom_operations_plugin& _plugin)\n         : _self( _plugin )\n      {  }\n\n      void onBlock();\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n      friend class graphene::custom_operations::custom_operations_plugin;\n\n   private:\n      custom_operations_plugin& _self;\n\n      uint32_t _start_block = 45000000;\n};\n\nstruct custom_op_visitor\n{\n   typedef void result_type;\n   account_id_type _fee_payer;\n   database* _db;\n\n   custom_op_visitor(database& db, account_id_type fee_payer) { _db = &db; _fee_payer = fee_payer; };\n\n   template<typename T>\n   void operator()(T &v) const {\n      v.validate();\n      custom_generic_evaluator evaluator(*_db, _fee_payer);\n      evaluator.do_apply(v);\n   }\n};\n\nvoid custom_operations_plugin_impl::onBlock()\n{\n   graphene::chain::database& db = database();\n   const vector<optional< operation_history_object > >& hist = db.get_applied_operations();\n   for( const optional< operation_history_object >& o_operation : hist )\n   {\n      if(!o_operation.valid() || !o_operation->op.is_type<custom_operation>())\n         continue;\n\n      const custom_operation& custom_op = o_operation->op.get<custom_operation>();\n\n      if(custom_op.data.size() == 0)\n         continue;\n\n      try {\n         auto unpacked = fc::raw::unpack<custom_plugin_operation>(custom_op.data);\n         custom_op_visitor vtor(db, custom_op.fee_payer());\n         unpacked.visit(vtor);\n      }\n      catch (fc::exception& e) { // only api node will know if the unpack, validate or apply fails\n         wlog(\"Custom operations plugin serializing error: ${ex} in operation: ${op}\",\n               (\"ex\", e.to_detail_string())(\"op\", fc::json::to_string(custom_op)));\n         continue;\n      }\n   }\n}\n\n} // end namespace detail\n\ncustom_operations_plugin::custom_operations_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::custom_operations_plugin_impl>(*this) )\n{\n   // Nothing to do\n}\n\ncustom_operations_plugin::~custom_operations_plugin() = default;\n\nstd::string custom_operations_plugin::plugin_name()const\n{\n   return \"custom_operations\";\n}\nstd::string custom_operations_plugin::plugin_description()const\n{\n   return \"Stores arbitrary data for accounts by creating specially crafted custom operations.\";\n}\n\nvoid custom_operations_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"custom-operations-start-block\", boost::program_options::value<uint32_t>()->default_value(45000000),\n          \"Start processing custom operations transactions with the plugin only after this block\")\n         ;\n   cfg.add(cli);\n\n}\n\nvoid custom_operations_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{\n   database().add_index< primary_index< account_storage_index  > >();\n\n   if (options.count(\"custom-operations-start-block\") > 0) {\n      my->_start_block = options[\"custom-operations-start-block\"].as<uint32_t>();\n   }\n\n   // connect with group 0 to process before some special steps (e.g. snapshot or next_object_id)\n   database().applied_block.connect( 0, [this]( const signed_block& b) {\n      if( b.block_num() >= my->_start_block )\n         my->onBlock();\n   } );\n}\n\nvoid custom_operations_plugin::plugin_startup()\n{\n   ilog(\"custom_operations: plugin_startup() begin\");\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/custom_operations/custom_objects.hpp>\n#include <graphene/custom_operations/custom_operations.hpp>\n\nnamespace graphene { namespace custom_operations {\n\nclass custom_generic_evaluator\n{\n   public:\n      database* _db;\n      account_id_type _account;\n      custom_generic_evaluator(database& db, const account_id_type account);\n\n      vector<object_id_type> do_apply(const account_storage_map& o);\n};\n\n} }\n\nFC_REFLECT_TYPENAME( graphene::custom_operations::custom_generic_evaluator )\n"
  },
  {
    "path": "libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <boost/multi_index/composite_key.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace custom_operations {\n\nusing namespace chain;\n\nconstexpr uint8_t CUSTOM_OPERATIONS_SPACE_ID = 7;\n\nconstexpr uint16_t CUSTOM_OPERATIONS_MAX_KEY_SIZE = 200;\n\nenum class custom_operations_object_types {\n   account_map = 0\n};\n\nstruct account_storage_object : public abstract_object<account_storage_object, CUSTOM_OPERATIONS_SPACE_ID,\n                                          static_cast<uint8_t>( custom_operations_object_types::account_map )>\n{\n   account_id_type account;\n   string catalog;\n   string key;\n   optional<variant> value;\n};\n\nstruct by_account_catalog_key;\nstruct by_account_catalog;\nstruct by_account;\nstruct by_catalog_key;\nstruct by_catalog;\n\nusing account_storage_multi_idx_type = multi_index_container<\n      account_storage_object,\n      indexed_by<\n            ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n            ordered_unique< tag<by_account_catalog_key>,\n                  composite_key< account_storage_object,\n                        member< account_storage_object, account_id_type, &account_storage_object::account >,\n                        member< account_storage_object, string, &account_storage_object::catalog >,\n                        member< account_storage_object, string, &account_storage_object::key >\n                  >\n            >,\n            ordered_unique< tag<by_account_catalog>,\n                  composite_key< account_storage_object,\n                        member< account_storage_object, account_id_type, &account_storage_object::account >,\n                        member< account_storage_object, string, &account_storage_object::catalog >,\n                        member< object, object_id_type, &object::id >\n                  >\n            >,\n            ordered_unique< tag<by_account>,\n                  composite_key< account_storage_object,\n                        member< account_storage_object, account_id_type, &account_storage_object::account >,\n                        member< object, object_id_type, &object::id >\n                  >\n            >,\n            ordered_unique< tag<by_catalog_key>,\n                  composite_key< account_storage_object,\n                        member< account_storage_object, string, &account_storage_object::catalog >,\n                        member< account_storage_object, string, &account_storage_object::key >,\n                        member< object, object_id_type, &object::id >\n                  >\n            >,\n            ordered_unique< tag<by_catalog>,\n                  composite_key< account_storage_object,\n                        member< account_storage_object, string, &account_storage_object::catalog >,\n                        member< object, object_id_type, &object::id >\n                  >\n            >\n      >\n>;\n\nusing account_storage_index = generic_index<account_storage_object, account_storage_multi_idx_type>;\n\nusing account_storage_id_type = object_id<account_storage_object::space_id, account_storage_object::type_id>;\n\n} } //graphene::custom_operations\n\nFC_REFLECT_DERIVED( graphene::custom_operations::account_storage_object, (graphene::db::object),\n                    (account)(catalog)(key)(value))\nFC_REFLECT_ENUM( graphene::custom_operations::custom_operations_object_types, (account_map))\n"
  },
  {
    "path": "libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/chain/database.hpp>\n\n#include \"custom_objects.hpp\"\n\nnamespace graphene { namespace custom_operations {\n\nusing namespace std;\nusing graphene::protocol::account_id_type;\n\nstruct account_storage_map : chain::base_operation\n{\n   bool remove;\n   string catalog;\n   flat_map<string, optional<string>> key_values;\n\n   void validate()const;\n};\n\n} } //graphene::custom_operations\n\nFC_REFLECT( graphene::custom_operations::account_storage_map, (remove)(catalog)(key_values) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map )\n"
  },
  {
    "path": "libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <graphene/custom_operations/custom_objects.hpp>\n#include <graphene/custom_operations/custom_operations.hpp>\n#include <graphene/custom_operations/custom_evaluators.hpp>\n\nnamespace graphene { namespace custom_operations {\nusing namespace chain;\n\nnamespace detail\n{\n    class custom_operations_plugin_impl;\n}\n\nclass custom_operations_plugin : public graphene::app::plugin\n{\n   public:\n      explicit custom_operations_plugin(graphene::app::application& app);\n      ~custom_operations_plugin() override;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n\n   private:\n      std::unique_ptr<detail::custom_operations_plugin_impl> my;\n};\n\ntypedef fc::static_variant<account_storage_map> custom_plugin_operation;\n\n} } //graphene::custom_operations\n\nFC_REFLECT_TYPENAME( graphene::custom_operations::custom_plugin_operation )\n"
  },
  {
    "path": "libraries/plugins/debug_witness/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/debug_witness/*.hpp\")\n\nadd_library( graphene_debug_witness\n             debug_api.cpp\n             debug_witness.cpp\n           )\n\ntarget_link_libraries( graphene_debug_witness graphene_app graphene_chain )\ntarget_include_directories( graphene_debug_witness\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\ninstall( TARGETS\n   graphene_debug_witness\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/debug_witness\" )\n"
  },
  {
    "path": "libraries/plugins/debug_witness/debug_api.cpp",
    "content": "\n#include <fc/filesystem.hpp>\n#include <fc/optional.hpp>\n#include <fc/variant_object.hpp>\n\n#include <graphene/app/application.hpp>\n\n#include <graphene/chain/block_database.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <graphene/utilities/key_conversion.hpp>\n\n#include <graphene/debug_witness/debug_api.hpp>\n#include <graphene/debug_witness/debug_witness.hpp>\n\nnamespace graphene { namespace debug_witness {\n\nnamespace detail {\n\nclass debug_api_impl\n{\n   public:\n      explicit debug_api_impl( graphene::app::application& _app );\n\n      void debug_push_blocks( const std::string& src_filename, uint32_t count );\n      void debug_generate_blocks( const std::string& debug_key, uint32_t count );\n      void debug_update_object( const fc::variant_object& update );\n      void debug_stream_json_objects( const std::string& filename );\n      void debug_stream_json_objects_flush();\n      std::shared_ptr< graphene::debug_witness_plugin::debug_witness_plugin > get_plugin();\n\n      graphene::app::application& app;\n};\n\ndebug_api_impl::debug_api_impl( graphene::app::application& _app ) : app( _app )\n{\n   // Nothing else to do\n}\n\n\nvoid debug_api_impl::debug_push_blocks( const std::string& src_filename, uint32_t count )\n{\n   if( count == 0 )\n      return;\n\n   std::shared_ptr< graphene::chain::database > db = app.chain_database();\n   fc::path src_path = fc::path( src_filename );\n   if( fc::is_directory( src_path ) )\n   {\n      ilog( \"Loading ${n} from block_database ${fn}\", (\"n\", count)(\"fn\", src_filename) );\n      graphene::chain::block_database bdb;\n      bdb.open( src_path );\n      uint32_t first_block = db->head_block_num()+1;\n      for( uint32_t i=0; i<count; i++ )\n      {\n         fc::optional< graphene::chain::signed_block > block = bdb.fetch_by_number( first_block+i );\n         if( !block.valid() )\n         {\n            wlog( \"Block database ${fn} only contained ${i} of ${n} requested blocks\", (\"i\", i)(\"n\", count)(\"fn\", src_filename) );\n            return;\n         }\n         try\n         {\n            db->push_block( *block );\n         }\n         catch( const fc::exception& e )\n         {\n            elog( \"Got exception pushing block ${bn} : ${bid} (${i} of ${n})\", (\"bn\", block->block_num())(\"bid\", block->id())(\"i\", i)(\"n\", count) );\n            elog( \"Exception backtrace: ${bt}\", (\"bt\", e.to_detail_string()) );\n         }\n      }\n      ilog( \"Completed loading block_database successfully\" );\n   }\n}\n\nvoid debug_api_impl::debug_generate_blocks( const std::string& debug_key, uint32_t count )\n{\n   if( count == 0 )\n      return;\n\n   fc::optional<fc::ecc::private_key> debug_private_key = graphene::utilities::wif_to_key( debug_key );\n   FC_ASSERT( debug_private_key.valid() );\n   graphene::chain::public_key_type debug_public_key = debug_private_key->get_public_key();\n\n   std::shared_ptr< graphene::chain::database > db = app.chain_database();\n   for( uint32_t i=0; i<count; i++ )\n   {\n      graphene::chain::witness_id_type scheduled_witness = db->get_scheduled_witness( 1 );\n      fc::time_point_sec scheduled_time = db->get_slot_time( 1 );\n      graphene::chain::public_key_type scheduled_key = scheduled_witness( *db ).signing_key;\n      if( scheduled_key != debug_public_key )\n      {\n         ilog( \"Modified key for witness ${w}\", (\"w\", scheduled_witness) );\n         fc::limited_mutable_variant_object update( GRAPHENE_MAX_NESTED_OBJECTS );\n         update(\"_action\", \"update\")(\"id\", scheduled_witness)(\"signing_key\", debug_public_key);\n         db->debug_update( update );\n      }\n      db->generate_block( scheduled_time, scheduled_witness, *debug_private_key, graphene::chain::database::skip_nothing );\n   }\n}\n\nvoid debug_api_impl::debug_update_object( const fc::variant_object& update )\n{\n   std::shared_ptr< graphene::chain::database > db = app.chain_database();\n   db->debug_update( update );\n}\n\nstd::shared_ptr< graphene::debug_witness_plugin::debug_witness_plugin > debug_api_impl::get_plugin()\n{\n   return app.get_plugin< graphene::debug_witness_plugin::debug_witness_plugin >( \"debug_witness\" );\n}\n\nvoid debug_api_impl::debug_stream_json_objects( const std::string& filename )\n{\n   get_plugin()->set_json_object_stream( filename );\n}\n\nvoid debug_api_impl::debug_stream_json_objects_flush()\n{\n   get_plugin()->flush_json_object_stream();\n}\n\n} // detail\n\ndebug_api::debug_api( graphene::app::application& app )\n{\n   my = std::make_shared< detail::debug_api_impl >(app);\n}\n\nvoid debug_api::debug_push_blocks( std::string source_filename, uint32_t count )\n{\n   my->debug_push_blocks( source_filename, count );\n}\n\nvoid debug_api::debug_generate_blocks( std::string debug_key, uint32_t count )\n{\n   my->debug_generate_blocks( debug_key, count );\n}\n\nvoid debug_api::debug_update_object( fc::variant_object update )\n{\n   my->debug_update_object( update );\n}\n\nvoid debug_api::debug_stream_json_objects( std::string filename )\n{\n   my->debug_stream_json_objects( filename );\n}\n\nvoid debug_api::debug_stream_json_objects_flush()\n{\n   my->debug_stream_json_objects_flush();\n}\n\n\n} } // graphene::debug_witness\n"
  },
  {
    "path": "libraries/plugins/debug_witness/debug_witness.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/debug_witness/debug_witness.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <graphene/utilities/key_conversion.hpp>\n\n#include <fc/thread/thread.hpp>\n\n#include <iostream>\n\nusing namespace graphene::debug_witness_plugin;\nusing std::string;\nusing std::vector;\n\nnamespace bpo = boost::program_options;\n\ndebug_witness_plugin::~debug_witness_plugin()\n{\n   cleanup();\n}\n\nvoid debug_witness_plugin::plugin_set_program_options(\n   boost::program_options::options_description& command_line_options,\n   boost::program_options::options_description& config_file_options)\n{\n   auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string(\"nathan\")));\n   command_line_options.add_options()\n         (\"debug-private-key\", bpo::value<vector<string>>()->composing()->multitoken()->\n          DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))),\n          \"Tuple of [PublicKey, WIF private key] (may specify multiple times)\");\n   config_file_options.add(command_line_options);\n}\n\nstd::string debug_witness_plugin::plugin_name()const\n{\n   return \"debug_witness\";\n}\n\nvoid debug_witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{ try {\n   ilog(\"debug_witness plugin:  plugin_initialize() begin\");\n   _options = &options;\n\n   if( options.count(\"debug-private-key\") > 0 )\n   {\n      const std::vector<std::string> key_id_to_wif_pair_strings = options[\"debug-private-key\"].as<std::vector<std::string>>();\n      for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)\n      {\n         auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::public_key_type, std::string> >(key_id_to_wif_pair_string, GRAPHENE_MAX_NESTED_OBJECTS);\n         idump((key_id_to_wif_pair));\n         fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);\n         if (!private_key)\n         {\n            // the key isn't in WIF format; see if they are still passing the old native private key format.  This is\n            // just here to ease the transition, can be removed soon\n            try\n            {\n               private_key = fc::variant( key_id_to_wif_pair.second, GRAPHENE_MAX_NESTED_OBJECTS ).as<fc::ecc::private_key>( GRAPHENE_MAX_NESTED_OBJECTS );\n            }\n            catch (const fc::exception&)\n            {\n               FC_THROW(\"Invalid WIF-format private key ${key_string}\", (\"key_string\", key_id_to_wif_pair.second));\n            }\n         }\n         _private_keys[key_id_to_wif_pair.first] = *private_key;\n      }\n   }\n   ilog(\"debug_witness plugin:  plugin_initialize() end\");\n} FC_LOG_AND_RETHROW() }\n\nvoid debug_witness_plugin::plugin_startup()\n{\n   ilog(\"debug_witness_plugin::plugin_startup() begin\");\n   chain::database& db = database();\n\n   // connect needed signals\n\n   _applied_block_conn  = db.applied_block.connect([this](const graphene::chain::signed_block& b){ on_applied_block(b); });\n   _changed_objects_conn = db.changed_objects.connect([this](const std::vector<graphene::db::object_id_type>& ids, const fc::flat_set<graphene::chain::account_id_type>& impacted_accounts){ on_changed_objects(ids, impacted_accounts); });\n   _removed_objects_conn = db.removed_objects.connect([this](const std::vector<graphene::db::object_id_type>& ids, const std::vector<const graphene::db::object*>& objs, const fc::flat_set<graphene::chain::account_id_type>& impacted_accounts){ on_removed_objects(ids, objs, impacted_accounts); });\n\n}\n\nvoid debug_witness_plugin::on_changed_objects( const std::vector<graphene::db::object_id_type>& ids, const fc::flat_set<graphene::chain::account_id_type>& impacted_accounts )\n{\n   if( _json_object_stream && (ids.size() > 0) )\n   {\n      const chain::database& db = database();\n      for( const graphene::db::object_id_type& oid : ids )\n      {\n         const graphene::db::object* obj = db.find_object( oid );\n         if( obj != nullptr )\n         {\n            (*_json_object_stream) << fc::json::to_string( obj->to_variant() ) << '\\n';\n         }\n      }\n   }\n}\n\nvoid debug_witness_plugin::on_removed_objects( const std::vector<graphene::db::object_id_type>& ids, const std::vector<const graphene::db::object*> objs, const fc::flat_set<graphene::chain::account_id_type>& impacted_accounts )\n{\n   if( _json_object_stream )\n   {\n      for( const graphene::db::object* obj : objs )\n      {\n         (*_json_object_stream) << \"{\\\"id\\\":\" << fc::json::to_string( obj->id ) << \"}\\n\";\n      }\n   }\n}\n\nvoid debug_witness_plugin::on_applied_block( const graphene::chain::signed_block& b )\n{\n   if( _json_object_stream )\n   {\n      (*_json_object_stream) << \"{\\\"bn\\\":\" << fc::to_string( b.block_num() ) << \"}\\n\";\n   }\n}\n\nvoid debug_witness_plugin::set_json_object_stream( const std::string& filename )\n{\n   if( _json_object_stream )\n   {\n      _json_object_stream->close();\n      _json_object_stream.reset();\n   }\n   _json_object_stream = std::make_shared< std::ofstream >( filename );\n}\n\nvoid debug_witness_plugin::flush_json_object_stream()\n{\n   if( _json_object_stream )\n      _json_object_stream->flush();\n}\n\nvoid debug_witness_plugin::plugin_shutdown()\n{\n   cleanup();\n}\n\nvoid debug_witness_plugin::cleanup()\n{\n   if( _json_object_stream )\n   {\n      _json_object_stream->close();\n      _json_object_stream.reset();\n   }\n}\n"
  },
  {
    "path": "libraries/plugins/debug_witness/include/graphene/debug_witness/debug_api.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <memory>\n#include <string>\n\n#include <fc/api.hpp>\n#include <fc/variant_object.hpp>\n\nnamespace graphene { namespace app {\nclass application;\n} }\n\nnamespace graphene { namespace debug_witness {\n\nnamespace detail {\nclass debug_api_impl;\n}\n\nclass debug_api\n{\n   public:\n      debug_api( graphene::app::application& app );\n\n      /**\n       * Push blocks from existing database.\n       */\n      void debug_push_blocks( std::string src_filename, uint32_t count );\n\n      /**\n       * Generate blocks locally.\n       */\n      void debug_generate_blocks( std::string debug_key, uint32_t count );\n\n      /**\n       * Directly manipulate database objects (will undo and re-apply last block with new changes post-applied).\n       */\n      void debug_update_object( fc::variant_object update );\n\n      /**\n       * Start a node with given initial path.\n       */\n      // not implemented\n      //void start_node( std::string name, std::string initial_db_path );\n\n      /**\n       * Save the database to disk.\n       */\n      // not implemented\n      //void save_db( std::string db_path );\n\n      /**\n       * Stream objects to file.  (Hint:  Create with mkfifo and pipe it to a script)\n       */\n\n      void debug_stream_json_objects( std::string filename );\n\n      /**\n       * Flush streaming file.\n       */\n      void debug_stream_json_objects_flush();\n\n      std::shared_ptr< detail::debug_api_impl > my;\n};\n\n} }\n\nFC_API(graphene::debug_witness::debug_api,\n       (debug_push_blocks)\n       (debug_generate_blocks)\n       (debug_update_object)\n       (debug_stream_json_objects)\n       (debug_stream_json_objects_flush)\n     )\n"
  },
  {
    "path": "libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/protocol/types.hpp>\n\n#include <fc/thread/future.hpp>\n#include <fc/container/flat.hpp>\n\nnamespace graphene { namespace debug_witness_plugin {\n\nclass debug_witness_plugin : public graphene::app::plugin {\npublic:\n   using graphene::app::plugin::plugin;\n   ~debug_witness_plugin() override;\n\n   std::string plugin_name()const override;\n\n   void plugin_set_program_options(\n      boost::program_options::options_description &command_line_options,\n      boost::program_options::options_description &config_file_options\n      ) override;\n\n   void plugin_initialize( const boost::program_options::variables_map& options ) override;\n   void plugin_startup() override;\n   void plugin_shutdown() override;\n\n   void set_json_object_stream( const std::string& filename );\n   void flush_json_object_stream();\n\nprivate:\n   void cleanup();\n\n   void on_changed_objects( const std::vector<graphene::db::object_id_type>& ids, const fc::flat_set<graphene::chain::account_id_type>& impacted_accounts );\n   void on_removed_objects( const std::vector<graphene::db::object_id_type>& ids, const std::vector<const graphene::db::object*> objs, const fc::flat_set<graphene::chain::account_id_type>& impacted_accounts );\n   void on_applied_block( const graphene::chain::signed_block& b );\n\n   boost::program_options::variables_map _options;\n\n   std::map<chain::public_key_type, fc::ecc::private_key, chain::pubkey_comparator> _private_keys;\n\n   std::shared_ptr< std::ofstream > _json_object_stream;\n   boost::signals2::scoped_connection _applied_block_conn;\n   boost::signals2::scoped_connection _changed_objects_conn;\n   boost::signals2::scoped_connection _removed_objects_conn;\n};\n\n} } //graphene::debug_witness_plugin\n"
  },
  {
    "path": "libraries/plugins/delayed_node/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/delayed_node/*.hpp\")\n\nadd_library( graphene_delayed_node \n             delayed_node_plugin.cpp\n           )\n\ntarget_link_libraries( graphene_delayed_node graphene_app graphene_chain )\ntarget_include_directories( graphene_delayed_node\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties( delayed_node_plugin.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_delayed_node\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "libraries/plugins/delayed_node/delayed_node_plugin.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/delayed_node/delayed_node_plugin.hpp>\n#include <graphene/protocol/types.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/app/api.hpp>\n\n#include <fc/network/http/websocket.hpp>\n#include <fc/rpc/websocket_api.hpp>\n#include <fc/api.hpp>\n\nnamespace graphene { namespace delayed_node {\nnamespace bpo = boost::program_options;\n\nnamespace detail {\nstruct delayed_node_plugin_impl {\n   std::string remote_endpoint;\n   fc::http::websocket_client client;\n   std::shared_ptr<fc::rpc::websocket_api_connection> client_connection;\n   fc::api<graphene::app::database_api> database_api;\n   boost::signals2::scoped_connection client_connection_closed;\n   graphene::chain::block_id_type last_received_remote_head;\n   graphene::chain::block_id_type last_processed_remote_head;\n};\n}\n\ndelayed_node_plugin::delayed_node_plugin(graphene::app::application& app) :\n   plugin(app)\n{\n   // Nothing else to do\n}\n\ndelayed_node_plugin::~delayed_node_plugin() = default;\n\nvoid delayed_node_plugin::plugin_set_program_options(bpo::options_description& cli, bpo::options_description& cfg)\n{\n   cli.add_options()\n         (\"trusted-node\", boost::program_options::value<std::string>(),\n          \"RPC endpoint of a trusted validating node (required for delayed_node)\")\n         ;\n   cfg.add(cli);\n}\n\nvoid delayed_node_plugin::connect()\n{\n   fc::http::websocket_connection_ptr con;\n   try\n   {\n      con = my->client.connect(my->remote_endpoint);\n   }\n   catch( const fc::exception& e )\n   {\n      wlog(\"Error while connecting: ${e}\", (\"e\", e.to_detail_string()));\n      connection_failed();\n      return;\n   }\n   my->client_connection = std::make_shared<fc::rpc::websocket_api_connection>(\n           con, GRAPHENE_NET_MAX_NESTED_OBJECTS );\n   my->database_api = my->client_connection->get_remote_api<graphene::app::database_api>(0);\n   my->database_api->set_block_applied_callback([this]( const fc::variant& block_id )\n   {\n      fc::from_variant( block_id, my->last_received_remote_head, GRAPHENE_MAX_NESTED_OBJECTS );\n   } );\n   my->client_connection_closed = my->client_connection->closed.connect([this] {\n      connection_failed();\n   });\n}\n\nvoid delayed_node_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{\n   FC_ASSERT(options.count(\"trusted-node\") > 0);\n   my = std::make_unique<detail::delayed_node_plugin_impl>();\n   my->remote_endpoint = \"ws://\" + options.at(\"trusted-node\").as<std::string>();\n}\n\nvoid delayed_node_plugin::sync_with_trusted_node()\n{\n   auto& db = database();\n   uint32_t synced_blocks = 0;\n   uint32_t pass_count = 0;\n   while( true )\n   {\n      graphene::chain::dynamic_global_property_object remote_dpo = my->database_api->get_dynamic_global_properties();\n      if( remote_dpo.last_irreversible_block_num <= db.head_block_num() )\n      {\n         if( remote_dpo.last_irreversible_block_num < db.head_block_num() )\n         {\n            wlog( \"Trusted node seems to be behind delayed node\" );\n         }\n         if( synced_blocks > 1 )\n         {\n            ilog( \"Delayed node finished syncing ${n} blocks in ${k} passes\", (\"n\", synced_blocks)(\"k\", pass_count) );\n         }\n         break;\n      }\n      pass_count++;\n      while( remote_dpo.last_irreversible_block_num > db.head_block_num() )\n      {\n         fc::optional<graphene::chain::signed_block> block = my->database_api->get_block( db.head_block_num()+1 );\n         // TODO: during sync, decouple requesting blocks from preprocessing + applying them\n         FC_ASSERT(block, \"Trusted node claims it has blocks it doesn't actually have.\");\n         ilog(\"Pushing block #${n}\", (\"n\", block->block_num()));\n         db.precompute_parallel( *block, graphene::chain::database::skip_nothing ).wait();\n         db.push_block(*block);\n         synced_blocks++;\n      }\n   }\n}\n\nvoid delayed_node_plugin::mainloop()\n{\n   while( true )\n   {\n      try\n      {\n         fc::usleep( fc::microseconds( 296645 ) );  // wake up a little over 3Hz\n\n         if( my->last_received_remote_head == my->last_processed_remote_head )\n            continue;\n\n         sync_with_trusted_node();\n         my->last_processed_remote_head = my->last_received_remote_head;\n      }\n      catch( const fc::exception& e )\n      {\n         elog(\"Error during connection: ${e}\", (\"e\", e.to_detail_string()));\n      }\n   }\n}\n\nvoid delayed_node_plugin::plugin_startup()\n{\n   fc::async([this]()\n   {\n      mainloop();\n   });\n\n   connect();\n}\n\nvoid delayed_node_plugin::connection_failed()\n{\n   my->last_received_remote_head = my->last_processed_remote_head;\n   elog(\"Connection to trusted node failed; retrying in 5 seconds...\");\n   fc::schedule([this]{connect();}, fc::time_point::now() + fc::seconds(5));\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/delayed_node/include/graphene/delayed_node/delayed_node_plugin.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n\nnamespace graphene { namespace delayed_node {\nnamespace detail { struct delayed_node_plugin_impl; }\n\nclass delayed_node_plugin : public graphene::app::plugin\n{\n   std::unique_ptr<detail::delayed_node_plugin_impl> my;\npublic:\n   explicit delayed_node_plugin(graphene::app::application& app);\n   ~delayed_node_plugin() override;\n\n   std::string plugin_name()const override { return \"delayed_node\"; }\n   void plugin_set_program_options(boost::program_options::options_description&,\n                                   boost::program_options::options_description& cfg) override;\n   void plugin_initialize(const boost::program_options::variables_map& options) override;\n   void plugin_startup() override;\n   void mainloop();\n\nprotected:\n   void connection_failed();\n   void connect();\n   void sync_with_trusted_node();\n};\n\n} } //graphene::account_history\n\n"
  },
  {
    "path": "libraries/plugins/elasticsearch/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/elasticsearch/*.hpp\")\n\nadd_library( graphene_elasticsearch\n        elasticsearch_plugin.cpp\n           )\n\nif(MSVC)\n  set_source_files_properties(elasticsearch_plugin.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\ntarget_link_libraries( graphene_elasticsearch graphene_app graphene_chain )\ntarget_include_directories( graphene_elasticsearch\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\n\ninstall( TARGETS\n   graphene_elasticsearch\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/elasticsearch\" )\n\n"
  },
  {
    "path": "libraries/plugins/elasticsearch/elasticsearch_plugin.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/elasticsearch/elasticsearch_plugin.hpp>\n#include <graphene/chain/impacted.hpp>\n#include <graphene/chain/account_evaluator.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <boost/algorithm/string.hpp>\n\n#include <graphene/utilities/boost_program_options.hpp>\n\nnamespace graphene { namespace elasticsearch {\n\nnamespace detail\n{\n\nclass elasticsearch_plugin_impl\n{\n   public:\n      explicit elasticsearch_plugin_impl(elasticsearch_plugin& _plugin)\n         : _self( _plugin )\n      { }\n\n   private:\n      friend class graphene::elasticsearch::elasticsearch_plugin;\n\n      struct plugin_options\n      {\n         std::string elasticsearch_url = \"http://localhost:9200/\";\n         std::string auth = \"\";\n         uint32_t bulk_replay = 10000;\n         uint32_t bulk_sync = 100;\n\n         std::string index_prefix = \"bitshares-\";\n\n         /// For the \"index.mapping.depth.limit\" setting in ES. The default value is 20.\n         uint16_t max_mapping_depth = 20;\n\n         uint32_t start_es_after_block = 0;\n\n         bool visitor = false;\n         bool operation_object = true;\n         bool operation_string = false;\n\n         mode elasticsearch_mode = mode::only_save;\n\n         void init(const boost::program_options::variables_map& options);\n      };\n\n      void update_account_histories( const signed_block& b );\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n      elasticsearch_plugin& _self;\n      plugin_options _options;\n\n      primary_index< operation_history_index >* _oho_index;\n\n      uint32_t limit_documents = _options.bulk_replay;\n\n      std::unique_ptr<graphene::utilities::es_client> es;\n\n      vector <string> bulk_lines; //  vector of op lines\n      size_t approximate_bulk_size = 0;\n\n      bulk_struct bulk_line_struct;\n\n      std::string index_name;\n      bool is_sync = false;\n      bool is_es_version_7_or_above = true;\n\n      void add_elasticsearch( const account_id_type& account_id, const optional<operation_history_object>& oho,\n                              uint32_t block_number );\n      void send_bulk( uint32_t block_num );\n\n      void doOperationHistory(const optional <operation_history_object>& oho, operation_history_struct& os) const;\n      void doBlock(uint32_t trx_in_block, const signed_block& b, block_struct& bs) const;\n      void doVisitor(const optional <operation_history_object>& oho, visitor_struct& vs) const;\n      void checkState(const fc::time_point_sec& block_time);\n      void cleanObjects(const account_history_object& ath, const account_id_type& account_id);\n\n      void init_program_options(const boost::program_options::variables_map& options);\n};\n\nstatic std::string generateIndexName( const fc::time_point_sec& block_date,\n                                      const std::string& index_prefix )\n{\n   auto block_date_string = block_date.to_iso_string();\n   std::vector<std::string> parts;\n   boost::split(parts, block_date_string, boost::is_any_of(\"-\"));\n   std::string index_name = index_prefix + parts[0] + \"-\" + parts[1];\n   return index_name;\n}\n\nvoid elasticsearch_plugin_impl::update_account_histories( const signed_block& b )\n{\n   checkState(b.timestamp);\n   index_name = generateIndexName(b.timestamp, _options.index_prefix);\n\n   graphene::chain::database& db = database();\n   const vector<optional< operation_history_object > >& hist = db.get_applied_operations();\n   bool is_first = true;\n   auto skip_oho_id = [&is_first,&db,this]() {\n      if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo\n      {\n         db.remove( db.create<operation_history_object>( []( operation_history_object& obj) {} ) );\n         is_first = false;\n      }\n      else\n         _oho_index->use_next_id();\n   };\n   for( const optional< operation_history_object >& o_op : hist ) {\n      optional <operation_history_object> oho;\n\n      auto create_oho = [&]() {\n         is_first = false;\n         return optional<operation_history_object>(\n               db.create<operation_history_object>([&](operation_history_object &h) {\n                  if (o_op.valid())\n                  {\n                     h.op           = o_op->op;\n                     h.result       = o_op->result;\n                     h.block_num    = o_op->block_num;\n                     h.trx_in_block = o_op->trx_in_block;\n                     h.op_in_trx    = o_op->op_in_trx;\n                     h.virtual_op   = o_op->virtual_op;\n                     h.is_virtual   = o_op->is_virtual;\n                     h.block_time   = o_op->block_time;\n                  }\n               }));\n      };\n\n      if( !o_op.valid() ) {\n         skip_oho_id();\n         continue;\n      }\n      oho = create_oho();\n\n      // populate what we can before impacted loop\n      if( o_op->block_num > _options.start_es_after_block )\n      {\n         bulk_line_struct.operation_type = oho->op.which();\n         bulk_line_struct.operation_id_num = oho->id.instance();\n         doOperationHistory( oho, bulk_line_struct.operation_history );\n         doBlock( oho->trx_in_block, b, bulk_line_struct.block_data );\n         if( _options.visitor )\n            doVisitor( oho, *bulk_line_struct.additional_data );\n      }\n\n      const operation_history_object& op = *o_op;\n\n      // get the set of accounts this operation applies to\n      flat_set<account_id_type> impacted;\n      vector<authority> other;\n      // fee_payer is added here\n      operation_get_required_authorities( op.op, impacted, impacted, other,\n                                          MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );\n\n      if( op.op.is_type< account_create_operation >() )\n         impacted.insert( account_id_type( op.result.get<object_id_type>() ) );\n\n      // https://github.com/bitshares/bitshares-core/issues/265\n      if( HARDFORK_CORE_265_PASSED(b.timestamp) || !op.op.is_type< account_create_operation >() )\n      {\n         operation_get_impacted_accounts( op.op, impacted,\n                                          MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) );\n      }\n\n      if( op.result.is_type<extendable_operation_result>() )\n      {\n         const auto& op_result = op.result.get<extendable_operation_result>();\n         if( op_result.value.impacted_accounts.valid() )\n         {\n            for( const auto& a : *op_result.value.impacted_accounts )\n               impacted.insert( a );\n         }\n      }\n\n      for( const auto& a : other )\n         for( const auto& item : a.account_auths )\n            impacted.insert( item.first );\n\n      for( const auto& account_id : impacted )\n      {\n         // Note: we send bulk if there are too many items in bulk_lines\n         add_elasticsearch( account_id, oho, b.block_num() );\n      }\n\n   }\n\n   // we send bulk at end of block when we are in sync for better real time client experience\n   if( is_sync && !bulk_lines.empty() )\n      send_bulk( b.block_num() );\n\n}\n\nvoid elasticsearch_plugin_impl::send_bulk( uint32_t block_num )\n{\n   ilog( \"Sending ${n} lines of bulk data to ElasticSearch at block ${b}, approximate size ${s}\",\n         (\"n\",bulk_lines.size())(\"b\",block_num)(\"s\",approximate_bulk_size) );\n   if( !es->send_bulk( bulk_lines ) )\n   {\n      elog( \"Error sending ${n} lines of bulk data to ElasticSearch, the first lines are:\",\n            (\"n\",bulk_lines.size()) );\n      const auto log_max = std::min( bulk_lines.size(), size_t(10) );\n      for( size_t i = 0; i < log_max; ++i )\n      {\n         edump( (bulk_lines[i]) );\n      }\n      FC_THROW_EXCEPTION( graphene::chain::plugin_exception,\n            \"Error populating ES database, we are going to keep trying.\" );\n   }\n   bulk_lines.clear();\n   approximate_bulk_size = 0;\n   bulk_lines.reserve(limit_documents);\n}\n\nvoid elasticsearch_plugin_impl::checkState(const fc::time_point_sec& block_time)\n{\n   if((fc::time_point::now() - block_time) < fc::seconds(30))\n   {\n      limit_documents = _options.bulk_sync;\n      is_sync = true;\n   }\n   else\n   {\n      limit_documents = _options.bulk_replay;\n      is_sync = false;\n   }\n   bulk_lines.reserve(limit_documents);\n}\n\nstruct get_fee_payer_visitor\n{\n   using result_type = account_id_type;\n\n   template<typename OpType>\n   account_id_type operator()(const OpType& op) const\n   {\n      return op.fee_payer();\n   }\n};\n\nvoid elasticsearch_plugin_impl::doOperationHistory( const optional <operation_history_object>& oho,\n                                                    operation_history_struct& os ) const\n{ try {\n   os.trx_in_block = oho->trx_in_block;\n   os.op_in_trx = oho->op_in_trx;\n   os.virtual_op = oho->virtual_op;\n   os.is_virtual = oho->is_virtual;\n   os.fee_payer = oho->op.visit( get_fee_payer_visitor() );\n\n   if(_options.operation_string)\n      os.op = fc::json::to_string(oho->op);\n\n   os.operation_result = fc::json::to_string(oho->result);\n\n   if(_options.operation_object) {\n      constexpr uint16_t current_depth = 2;\n      // op\n      oho->op.visit(fc::from_static_variant(os.op_object, FC_PACK_MAX_DEPTH));\n      os.op_object = graphene::utilities::es_data_adaptor::adapt( os.op_object.get_object(),\n                                                                  _options.max_mapping_depth - current_depth );\n      // operation_result\n      variant v;\n      fc::to_variant( oho->result, v, FC_PACK_MAX_DEPTH );\n      os.operation_result_object = graphene::utilities::es_data_adaptor::adapt_static_variant( v.get_array(),\n                                         _options.max_mapping_depth - current_depth );\n   }\n} FC_CAPTURE_LOG_AND_RETHROW( (oho) ) } // GCOVR_EXCL_LINE\n\nvoid elasticsearch_plugin_impl::doBlock(uint32_t trx_in_block, const signed_block& b, block_struct& bs) const\n{\n   std::string trx_id = \"\";\n   if(trx_in_block < b.transactions.size())\n      trx_id = b.transactions[trx_in_block].id().str();\n   bs.block_num = b.block_num();\n   bs.block_time = b.timestamp;\n   bs.trx_id = trx_id;\n}\n\nstruct operation_visitor\n{\n   using result_type = void;\n\n   share_type fee_amount;\n   asset_id_type fee_asset;\n\n   asset_id_type transfer_asset_id;\n   share_type transfer_amount;\n   account_id_type transfer_from;\n   account_id_type transfer_to;\n\n   void operator()( const graphene::chain::transfer_operation& o )\n   {\n      fee_asset = o.fee.asset_id;\n      fee_amount = o.fee.amount;\n\n      transfer_asset_id = o.amount.asset_id;\n      transfer_amount = o.amount.amount;\n      transfer_from = o.from;\n      transfer_to = o.to;\n   }\n\n   object_id_type      fill_order_id;\n   account_id_type     fill_account_id;\n   asset_id_type       fill_pays_asset_id;\n   share_type          fill_pays_amount;\n   asset_id_type       fill_receives_asset_id;\n   share_type          fill_receives_amount;\n   double              fill_fill_price;\n   bool                fill_is_maker;\n\n   void operator()( const graphene::chain::fill_order_operation& o )\n   {\n      fee_asset = o.fee.asset_id;\n      fee_amount = o.fee.amount;\n\n      fill_order_id = o.order_id;\n      fill_account_id = o.account_id;\n      fill_pays_asset_id = o.pays.asset_id;\n      fill_pays_amount = o.pays.amount;\n      fill_receives_asset_id = o.receives.asset_id;\n      fill_receives_amount = o.receives.amount;\n      fill_fill_price = o.fill_price.to_real();\n      fill_is_maker = o.is_maker;\n   }\n\n   template<typename T>\n   void operator()( const T& o )\n   {\n      fee_asset = o.fee.asset_id;\n      fee_amount = o.fee.amount;\n   }\n};\n\nvoid elasticsearch_plugin_impl::doVisitor(const optional <operation_history_object>& oho, visitor_struct& vs) const\n{\n   const graphene::chain::database& db = _self.database();\n\n   operation_visitor o_v;\n   oho->op.visit(o_v);\n\n   auto fee_asset = o_v.fee_asset(db);\n   vs.fee_data.asset = o_v.fee_asset;\n   vs.fee_data.asset_name = fee_asset.symbol;\n   vs.fee_data.amount = o_v.fee_amount;\n   vs.fee_data.amount_units = (o_v.fee_amount.value)/(double)asset::scaled_precision(fee_asset.precision).value;\n\n   auto transfer_asset = o_v.transfer_asset_id(db);\n   vs.transfer_data.asset = o_v.transfer_asset_id;\n   vs.transfer_data.asset_name = transfer_asset.symbol;\n   vs.transfer_data.amount = o_v.transfer_amount;\n   vs.transfer_data.amount_units = (o_v.transfer_amount.value)\n                                 / (double)asset::scaled_precision(transfer_asset.precision).value;\n   vs.transfer_data.from = o_v.transfer_from;\n   vs.transfer_data.to = o_v.transfer_to;\n\n   auto fill_pays_asset = o_v.fill_pays_asset_id(db);\n   auto fill_receives_asset = o_v.fill_receives_asset_id(db);\n   vs.fill_data.order_id = o_v.fill_order_id;\n   vs.fill_data.account_id = o_v.fill_account_id;\n   vs.fill_data.pays_asset_id = o_v.fill_pays_asset_id;\n   vs.fill_data.pays_asset_name = fill_pays_asset.symbol;\n   vs.fill_data.pays_amount = o_v.fill_pays_amount;\n   vs.fill_data.pays_amount_units = (o_v.fill_pays_amount.value)\n                                  / (double)asset::scaled_precision(fill_pays_asset.precision).value;\n   vs.fill_data.receives_asset_id = o_v.fill_receives_asset_id;\n   vs.fill_data.receives_asset_name = fill_receives_asset.symbol;\n   vs.fill_data.receives_amount = o_v.fill_receives_amount;\n   vs.fill_data.receives_amount_units = (o_v.fill_receives_amount.value)\n                                      / (double)asset::scaled_precision(fill_receives_asset.precision).value;\n\n   auto fill_price = (o_v.fill_receives_amount.value\n                      / (double)asset::scaled_precision(fill_receives_asset.precision).value)\n                   / (o_v.fill_pays_amount.value\n                      / (double)asset::scaled_precision(fill_pays_asset.precision).value);\n   vs.fill_data.fill_price_units = fill_price;\n   vs.fill_data.fill_price = o_v.fill_fill_price;\n   vs.fill_data.is_maker = o_v.fill_is_maker;\n}\n\nvoid elasticsearch_plugin_impl::add_elasticsearch( const account_id_type& account_id,\n                                                   const optional<operation_history_object>& oho,\n                                                   uint32_t block_number )\n{\n   graphene::chain::database& db = database();\n\n   const auto &stats_obj = db.get_account_stats_by_owner( account_id );\n\n   const auto &ath = db.create<account_history_object>(\n         [&oho,&account_id,&stats_obj]( account_history_object &obj ) {\n      obj.operation_id = oho->id;\n      obj.account = account_id;\n      obj.sequence = stats_obj.total_ops + 1;\n      obj.next = stats_obj.most_recent_op;\n   });\n\n   db.modify( stats_obj, [&ath]( account_statistics_object &obj ) {\n      obj.most_recent_op = ath.id;\n      obj.total_ops = ath.sequence;\n   });\n\n   if( block_number > _options.start_es_after_block )\n   {\n      bulk_line_struct.account_history = ath;\n\n      auto bulk_line = fc::json::to_string(bulk_line_struct, fc::json::legacy_generator);\n\n      fc::mutable_variant_object bulk_header;\n      bulk_header[\"_index\"] = index_name;\n      if( !is_es_version_7_or_above )\n         bulk_header[\"_type\"] = \"_doc\";\n      bulk_header[\"_id\"] = std::string( ath.id );\n      auto prepare = graphene::utilities::createBulk(bulk_header, std::move(bulk_line));\n      std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk_lines));\n\n      approximate_bulk_size += bulk_lines.back().size();\n\n      if( bulk_lines.size() >= limit_documents\n            || approximate_bulk_size >= graphene::utilities::es_client::request_size_threshold )\n         send_bulk( block_number );\n   }\n   cleanObjects(ath, account_id);\n}\n\nvoid elasticsearch_plugin_impl::cleanObjects( const account_history_object& ath,\n                                              const account_id_type& account_id )\n{\n   graphene::chain::database& db = database();\n   // remove everything except current object from ath\n   const auto &his_idx = db.get_index_type<account_history_index>();\n   const auto &by_seq_idx = his_idx.indices().get<by_seq>();\n   auto itr = by_seq_idx.lower_bound(boost::make_tuple(account_id, 0));\n   if (itr != by_seq_idx.end() && itr->account == account_id && itr->id != ath.id) {\n      // if found, remove the entry\n      const auto remove_op_id = itr->operation_id;\n      const auto itr_remove = itr;\n      ++itr;\n      db.remove( *itr_remove );\n      // modify previous node's next pointer\n      // this should be always true, but just have a check here\n      if( itr != by_seq_idx.end() && itr->account == account_id )\n      {\n         db.modify( *itr, []( account_history_object& obj ){\n            obj.next = account_history_id_type();\n         });\n      }\n      // do the same on oho\n      const auto &by_opid_idx = his_idx.indices().get<by_opid>();\n      if (by_opid_idx.find(remove_op_id) == by_opid_idx.end()) {\n         db.remove(remove_op_id(db));\n      }\n   }\n}\n\n} // end namespace detail\n\nelasticsearch_plugin::elasticsearch_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::elasticsearch_plugin_impl>(*this) )\n{\n   // Nothing else to do\n}\n\nelasticsearch_plugin::~elasticsearch_plugin() = default;\n\nstd::string elasticsearch_plugin::plugin_name()const\n{\n   return \"elasticsearch\";\n}\nstd::string elasticsearch_plugin::plugin_description()const\n{\n   return \"Stores account history data in elasticsearch database(EXPERIMENTAL).\";\n}\n\nvoid elasticsearch_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"elasticsearch-node-url\", boost::program_options::value<std::string>(),\n               \"Elastic Search database node url(http://localhost:9200/)\")\n         (\"elasticsearch-basic-auth\", boost::program_options::value<std::string>(),\n               \"Pass basic auth to elasticsearch database('')\")\n         (\"elasticsearch-bulk-replay\", boost::program_options::value<uint32_t>(),\n               \"Number of bulk documents to index on replay(10000)\")\n         (\"elasticsearch-bulk-sync\", boost::program_options::value<uint32_t>(),\n               \"Number of bulk documents to index on a syncronied chain(100)\")\n         (\"elasticsearch-index-prefix\", boost::program_options::value<std::string>(),\n               \"Add a prefix to the index(bitshares-)\")\n         (\"elasticsearch-max-mapping-depth\", boost::program_options::value<uint16_t>(),\n               \"The maximum index mapping depth (index.mapping.depth.limit) setting in ES, \"\n               \"should be >=2. (20)\")\n         (\"elasticsearch-start-es-after-block\", boost::program_options::value<uint32_t>(),\n               \"Start doing ES job after block(0)\")\n         (\"elasticsearch-visitor\", boost::program_options::value<bool>(),\n               \"Use visitor to index additional data(slows down the replay(false))\")\n         (\"elasticsearch-operation-object\", boost::program_options::value<bool>(),\n               \"Save operation as object(true)\")\n         (\"elasticsearch-operation-string\", boost::program_options::value<bool>(),\n               \"Save operation as string. Needed to serve history api calls(false)\")\n         (\"elasticsearch-mode\", boost::program_options::value<uint16_t>(),\n               \"Mode of operation: only_save(0), only_query(1), all(2) - Default: 0\")\n         ;\n   cfg.add(cli);\n}\n\nvoid detail::elasticsearch_plugin_impl::init_program_options(const boost::program_options::variables_map& options)\n{\n   _options.init( options );\n\n   if( _options.visitor )\n      bulk_line_struct.additional_data = visitor_struct();\n\n   es = std::make_unique<graphene::utilities::es_client>( _options.elasticsearch_url, _options.auth );\n\n   FC_ASSERT( es->check_status(), \"ES database is not up in url ${url}\", (\"url\", _options.elasticsearch_url) );\n\n   es->check_version_7_or_above( is_es_version_7_or_above );\n}\n\nvoid detail::elasticsearch_plugin_impl::plugin_options::init(const boost::program_options::variables_map& options)\n{\n   utilities::get_program_option( options, \"elasticsearch-node-url\",     elasticsearch_url );\n   utilities::get_program_option( options, \"elasticsearch-basic-auth\",   auth );\n   utilities::get_program_option( options, \"elasticsearch-bulk-replay\",  bulk_replay );\n   utilities::get_program_option( options, \"elasticsearch-bulk-sync\",    bulk_sync );\n   utilities::get_program_option( options, \"elasticsearch-index-prefix\",         index_prefix );\n   utilities::get_program_option( options, \"elasticsearch-max-mapping-depth\",    max_mapping_depth );\n   utilities::get_program_option( options, \"elasticsearch-start-es-after-block\", start_es_after_block );\n   utilities::get_program_option( options, \"elasticsearch-visitor\",          visitor );\n   utilities::get_program_option( options, \"elasticsearch-operation-object\", operation_object );\n   utilities::get_program_option( options, \"elasticsearch-operation-string\", operation_string );\n\n   FC_ASSERT( max_mapping_depth >= 2, \"The minimum value of elasticsearch-max-mapping-depth is 2\" );\n\n   auto es_mode = static_cast<uint16_t>( elasticsearch_mode );\n   utilities::get_program_option( options, \"elasticsearch-mode\", es_mode );\n   if( es_mode > static_cast<uint16_t>( mode::all ) )\n      FC_THROW_EXCEPTION( graphene::chain::plugin_exception, \"Elasticsearch mode not valid\" );\n   elasticsearch_mode = static_cast<mode>( es_mode );\n\n   if( mode::all == elasticsearch_mode && !operation_string )\n   {\n      FC_THROW_EXCEPTION( graphene::chain::plugin_exception,\n            \"If elasticsearch-mode is set to all then elasticsearch-operation-string need to be true\");\n   }\n}\n\nvoid elasticsearch_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{\n   my->init_program_options( options );\n\n   my->_oho_index = database().add_index< primary_index< operation_history_index > >();\n   database().add_index< primary_index< account_history_index > >();\n\n   if( my->_options.elasticsearch_mode != mode::only_query )\n   {\n      // connect with group 0 to process before some special steps (e.g. snapshot or next_object_id)\n      database().applied_block.connect( 0, [this](const signed_block &b) {\n         my->update_account_histories(b);\n      });\n   }\n}\n\nvoid elasticsearch_plugin::plugin_startup()\n{\n   // Nothing to do\n}\n\nstatic operation_history_object fromEStoOperation(const variant& source)\n{\n   operation_history_object result;\n\n   const auto operation_id = source[\"account_history\"][\"operation_id\"];\n   fc::from_variant( operation_id, result.id, GRAPHENE_MAX_NESTED_OBJECTS );\n\n   const auto op = fc::json::from_string(source[\"operation_history\"][\"op\"].as_string());\n   fc::from_variant( op, result.op, GRAPHENE_MAX_NESTED_OBJECTS );\n\n   const auto operation_result = fc::json::from_string(source[\"operation_history\"][\"operation_result\"].as_string());\n   fc::from_variant( operation_result, result.result, GRAPHENE_MAX_NESTED_OBJECTS );\n\n   result.block_num = source[\"block_data\"][\"block_num\"].as_uint64();\n   result.trx_in_block = source[\"operation_history\"][\"trx_in_block\"].as_uint64();\n   result.op_in_trx = source[\"operation_history\"][\"op_in_trx\"].as_uint64();\n   result.trx_in_block = source[\"operation_history\"][\"virtual_op\"].as_uint64();\n   result.is_virtual = source[\"operation_history\"][\"is_virtual\"].as_bool();\n\n   result.block_time = fc::time_point_sec::from_iso_string( source[\"block_data\"][\"block_time\"].as_string() );\n\n   return result;\n}\n\noperation_history_object elasticsearch_plugin::get_operation_by_id( const operation_history_id_type& id ) const\n{\n   const string operation_id_string = std::string(object_id_type(id));\n\n   const string query = R\"(\n   {\n      \"query\": {\n         \"match\":\n         {\n            \"account_history.operation_id\": \")\" + operation_id_string + R\"(\"\n         }\n      }\n   }\n   )\";\n\n   const auto uri = my->_options.index_prefix + ( my->is_es_version_7_or_above ? \"*/_search\" : \"*/_doc/_search\" );\n   const auto response = my->es->query( uri, query );\n   variant variant_response = fc::json::from_string(response);\n   const auto source = variant_response[\"hits\"][\"hits\"][size_t(0)][\"_source\"];\n   return fromEStoOperation(source);\n}\n\nvector<operation_history_object> elasticsearch_plugin::get_account_history(\n      const account_id_type& account_id,\n      const operation_history_id_type& stop,\n      uint64_t limit,\n      const operation_history_id_type& start ) const\n{\n   const auto account_id_string = std::string( account_id );\n\n   const auto stop_number = stop.instance.value;\n   const auto start_number = start.instance.value;\n\n   string range = \"\";\n   if(stop_number == 0)\n      range = \" AND operation_id_num: [\"+fc::to_string(stop_number)+\" TO \"+fc::to_string(start_number)+\"]\";\n   else if(stop_number > 0)\n      range = \" AND operation_id_num: {\"+fc::to_string(stop_number)+\" TO \"+fc::to_string(start_number)+\"]\";\n   // FIXME the code above is either redundant or buggy\n\n   const string query = R\"(\n   {\n      \"size\": )\" + fc::to_string(limit) + R\"(,\n      \"sort\" : [{ \"operation_id_num\" : {\"order\" : \"desc\"}}],\n      \"query\": {\n         \"bool\": {\n            \"must\": [\n            {\n               \"query_string\": {\n                  \"query\": \"account_history.account: )\" + account_id_string +  range + R\"(\"\n               }\n            }\n            ]\n         }\n      }\n   }\n   )\";\n\n   vector<operation_history_object> result;\n\n   if( !my->es->check_status() )\n      return result;\n\n   const auto uri = my->_options.index_prefix + ( my->is_es_version_7_or_above ? \"*/_search\" : \"*/_doc/_search\" );\n   const auto response = my->es->query( uri, query );\n\n   variant variant_response = fc::json::from_string(response);\n\n   const auto hits = variant_response[\"hits\"][\"total\"];\n   size_t size;\n   if( hits.is_object() ) // ES-7 ?\n      size = hits[\"value\"].as_uint64();\n   else // probably ES-6\n      size = hits.as_uint64();\n   size = std::min( size, size_t(limit) );\n\n   const auto& data = variant_response[\"hits\"][\"hits\"];\n   for( size_t i=0; i<size; ++i )\n   {\n      const auto& source = data[i][\"_source\"];\n      result.push_back(fromEStoOperation(source));\n   }\n   return result;\n}\n\nmode elasticsearch_plugin::get_running_mode() const\n{\n   return my->_options.elasticsearch_mode;\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/elasticsearch/include/graphene/elasticsearch/elasticsearch_plugin.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/utilities/elasticsearch.hpp>\n\nnamespace graphene { namespace elasticsearch {\n   using namespace chain;\n\n//\n// Plugins should #define their SPACE_ID's so plugins with\n// conflicting SPACE_ID assignments can be compiled into the\n// same binary (by simply re-assigning some of the conflicting #defined\n// SPACE_ID's in a build script).\n//\n// Assignment of SPACE_ID's cannot be done at run-time because\n// various template automagic depends on them being known at compile\n// time.\n//\n#ifndef ELASTICSEARCH_SPACE_ID\n#define ELASTICSEARCH_SPACE_ID 6\n#endif\n\nnamespace detail\n{\n    class elasticsearch_plugin_impl;\n}\n\nenum class mode { only_save = 0 , only_query = 1, all = 2 };\n\nclass elasticsearch_plugin : public graphene::app::plugin\n{\n   public:\n      explicit elasticsearch_plugin(graphene::app::application& app);\n      ~elasticsearch_plugin() override;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n\n      operation_history_object get_operation_by_id(const operation_history_id_type& id) const;\n      vector<operation_history_object> get_account_history(\n            const account_id_type& account_id,\n            const operation_history_id_type& stop = operation_history_id_type(),\n            uint64_t limit = 100,\n            const operation_history_id_type& start = operation_history_id_type() ) const;\n      mode get_running_mode() const;\n\n   private:\n      std::unique_ptr<detail::elasticsearch_plugin_impl> my;\n};\n\n\nstruct operation_history_struct {\n   uint16_t trx_in_block;\n   uint16_t op_in_trx;\n   uint32_t virtual_op;\n   bool is_virtual;\n   account_id_type fee_payer;\n   std::string op;\n   std::string operation_result;\n   variant op_object;\n   variant operation_result_object;\n};\n\nstruct block_struct {\n   uint32_t block_num;\n   fc::time_point_sec block_time;\n   std::string trx_id;\n};\n\nstruct fee_struct {\n   asset_id_type asset;\n   std::string asset_name;\n   share_type amount;\n   double amount_units;\n};\n\nstruct transfer_struct {\n   asset_id_type asset;\n   std::string asset_name;\n   share_type amount;\n   double amount_units;\n   account_id_type from;\n   account_id_type to;\n};\n\nstruct fill_struct {\n   object_id_type order_id;\n   account_id_type account_id;\n   asset_id_type pays_asset_id;\n   std::string pays_asset_name;\n   share_type pays_amount;\n   double pays_amount_units;\n   asset_id_type receives_asset_id;\n   std::string receives_asset_name;\n   share_type receives_amount;\n   double receives_amount_units;\n   double fill_price;\n   double fill_price_units;\n   bool is_maker;\n};\n\nstruct visitor_struct {\n   fee_struct fee_data;\n   transfer_struct transfer_data;\n   fill_struct fill_data;\n};\n\nstruct bulk_struct {\n   account_history_object account_history;\n   operation_history_struct operation_history;\n   int64_t  operation_type;\n   uint64_t operation_id_num;\n   block_struct block_data;\n   optional<visitor_struct> additional_data;\n};\n\n} } //graphene::elasticsearch\n\nFC_REFLECT_ENUM( graphene::elasticsearch::mode, (only_save)(only_query)(all) )\nFC_REFLECT( graphene::elasticsearch::operation_history_struct,\n            (trx_in_block)(op_in_trx)(virtual_op)(is_virtual)(fee_payer)\n            (op)(operation_result)(op_object)(operation_result_object) )\nFC_REFLECT( graphene::elasticsearch::block_struct, (block_num)(block_time)(trx_id) )\nFC_REFLECT( graphene::elasticsearch::fee_struct, (asset)(asset_name)(amount)(amount_units) )\nFC_REFLECT( graphene::elasticsearch::transfer_struct, (asset)(asset_name)(amount)(amount_units)(from)(to) )\nFC_REFLECT( graphene::elasticsearch::fill_struct,\n            (order_id)(account_id)(pays_asset_id)(pays_asset_name)(pays_amount)(pays_amount_units)\n            (receives_asset_id)(receives_asset_name)(receives_amount)(receives_amount_units)(fill_price)\n            (fill_price_units)(is_maker) )\nFC_REFLECT( graphene::elasticsearch::visitor_struct, (fee_data)(transfer_data)(fill_data) )\nFC_REFLECT( graphene::elasticsearch::bulk_struct,\n            (account_history)(operation_history)(operation_type)(operation_id_num)(block_data)(additional_data) )\n"
  },
  {
    "path": "libraries/plugins/es_objects/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/es_objects/*.hpp\")\n\nadd_library( graphene_es_objects\n        es_objects.cpp\n           )\n\nif(MSVC)\n  set_source_files_properties(es_objects.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ntarget_link_libraries( graphene_es_objects graphene_app graphene_chain )\ntarget_include_directories( graphene_es_objects\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\n\ninstall( TARGETS\n   graphene_es_objects\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/es_objects\" )\n\n"
  },
  {
    "path": "libraries/plugins/es_objects/es_objects.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/es_objects/es_objects.hpp>\n\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/budget_record_object.hpp>\n\n#include <graphene/utilities/elasticsearch.hpp>\n#include <graphene/utilities/boost_program_options.hpp>\n\nnamespace graphene { namespace db {\n   template<uint8_t SpaceID, uint8_t TypeID>\n   constexpr uint16_t object_id<SpaceID, TypeID>::space_type;\n} };\n\nnamespace graphene { namespace es_objects {\n\nnamespace detail\n{\n\nclass es_objects_plugin_impl\n{\n   public:\n      explicit es_objects_plugin_impl(es_objects_plugin& _plugin)\n         : _self( _plugin )\n      { }\n\n   private:\n      friend class graphene::es_objects::es_objects_plugin;\n      friend struct data_loader;\n\n      struct plugin_options\n      {\n         struct object_options\n         {\n            object_options( bool e, bool su, bool nd, const string& in )\n            : enabled(e), store_updates(su), no_delete(nd), index_name(in)\n            {}\n\n            bool enabled = true;\n            bool store_updates = false;\n            bool no_delete = false;\n            string index_name = \"\";\n         };\n         std::string elasticsearch_url = \"http://localhost:9200/\";\n         std::string auth = \"\";\n         uint32_t bulk_replay = 10000;\n         uint32_t bulk_sync = 100;\n\n         object_options proposals      { true, false, true,  \"proposal\"   };\n         object_options accounts       { true, false, true,  \"account\"    };\n         object_options assets         { true, false, true,  \"asset\"      };\n         object_options balances       { true, false, true,  \"balance\"    };\n         object_options limit_orders   { true, false, false, \"limitorder\" };\n         object_options asset_bitasset { true, false, true,  \"bitasset\"   };\n         object_options budget         { true, false, true,  \"budget\"     };\n\n         std::string index_prefix = \"objects-\";\n\n         /// For the \"index.mapping.depth.limit\" setting in ES whose default value is 20,\n         /// and need to be even smaller to not trigger the index.mapping.total_fields.limit error\n         uint16_t max_mapping_depth = 10;\n\n         uint32_t start_es_after_block = 0;\n         bool sync_db_on_startup = false;\n\n         void init(const boost::program_options::variables_map& options);\n      };\n\n      enum class action_type\n      {\n         insertion,\n         update,\n         deletion\n      };\n\n      void on_objects_create(const vector<object_id_type>& ids)\n      { index_database( ids, action_type::insertion ); }\n\n      void on_objects_update(const vector<object_id_type>& ids)\n      { index_database( ids, action_type::update ); }\n\n      void on_objects_delete(const vector<object_id_type>& ids)\n      { index_database( ids, action_type::deletion ); }\n\n      void index_database(const vector<object_id_type>& ids, action_type action);\n      /// Load all data from the object database into ES\n      void sync_db( bool delete_before_load = false );\n      /// Delete one object from ES\n      void delete_from_database( const object_id_type& id, const plugin_options::object_options& opt );\n      /// Delete all objects of the specified type from ES\n      void delete_all_from_database( const plugin_options::object_options& opt ) const;\n\n      es_objects_plugin& _self;\n      plugin_options _options;\n\n      uint32_t limit_documents = _options.bulk_replay;\n\n      uint64_t docs_sent_batch = 0;\n      uint64_t docs_sent_total = 0;\n\n      std::unique_ptr<graphene::utilities::es_client> es;\n\n      vector<std::string> bulk_lines;\n      size_t approximate_bulk_size = 0;\n\n      uint32_t block_number = 0;\n      fc::time_point_sec block_time;\n      bool is_es_version_7_or_above = true;\n\n      template<typename T>\n      void prepareTemplate( const T& blockchain_object, const plugin_options::object_options& opt );\n\n      void init_program_options(const boost::program_options::variables_map& options);\n\n      void send_bulk_if_ready( bool force = false );\n};\n\nstruct data_loader\n{\n   es_objects_plugin_impl* my;\n   graphene::chain::database &db;\n\n   explicit data_loader( es_objects_plugin_impl* _my )\n   : my(_my), db( my->_self.database() )\n   { // Nothing to do\n   }\n\n   template<typename ObjType>\n   void load( const es_objects_plugin_impl::plugin_options::object_options& opt,\n              bool force_delete = false )\n   {\n      if( !opt.enabled )\n         return;\n\n      // If no_delete or store_updates is true, do not delete\n      if( force_delete || !( opt.no_delete || opt.store_updates ) )\n      {\n         ilog( \"Deleting all data in index \" + my->_options.index_prefix + opt.index_name );\n         my->delete_all_from_database( opt );\n      }\n\n      ilog( \"Loading data into index \" + my->_options.index_prefix + opt.index_name );\n      db.get_index( ObjType::space_id, ObjType::type_id ).inspect_all_objects(\n            [this, &opt](const graphene::db::object &o) {\n         my->prepareTemplate( static_cast<const ObjType&>(o), opt );\n      });\n      my->send_bulk_if_ready(true);\n      my->docs_sent_batch = 0;\n   }\n};\n\nvoid es_objects_plugin_impl::sync_db( bool delete_before_load )\n{\n   ilog(\"elasticsearch OBJECTS: loading data from the object database (chain state)\");\n\n   graphene::chain::database &db = _self.database();\n\n   block_number = db.head_block_num();\n   block_time = db.head_block_time();\n\n   data_loader loader( this );\n\n   loader.load<account_object             >( _options.accounts,       delete_before_load );\n   loader.load<asset_object               >( _options.assets,         delete_before_load );\n   loader.load<asset_bitasset_data_object >( _options.asset_bitasset, delete_before_load );\n   loader.load<account_balance_object     >( _options.balances,       delete_before_load );\n   loader.load<proposal_object            >( _options.proposals,      delete_before_load );\n   loader.load<limit_order_object         >( _options.limit_orders,   delete_before_load );\n   loader.load<budget_record_object       >( _options.budget,         delete_before_load );\n\n   ilog(\"elasticsearch OBJECTS: done loading data from the object database (chain state)\");\n}\n\nvoid es_objects_plugin_impl::index_database(const vector<object_id_type>& ids, action_type action)\n{\n   graphene::chain::database &db = _self.database();\n\n   block_number = db.head_block_num();\n\n   if( block_number <= _options.start_es_after_block )\n      return;\n\n   block_time = db.head_block_time();\n\n   // check if we are in replay or in sync and change number of bulk documents accordingly\n   if( (fc::time_point::now() - block_time) < fc::seconds(30) )\n      limit_documents = _options.bulk_sync;\n   else\n      limit_documents = _options.bulk_replay;\n\n   bulk_lines.reserve(limit_documents);\n\n   static const unordered_map<uint16_t,plugin_options::object_options&> data_type_map = {\n      { account_id_type::space_type,             _options.accounts       },\n      { account_balance_id_type::space_type,     _options.balances       },\n      { asset_id_type::space_type,               _options.assets         },\n      { asset_bitasset_data_id_type::space_type, _options.asset_bitasset },\n      { limit_order_id_type::space_type,         _options.limit_orders   },\n      { proposal_id_type::space_type,            _options.proposals      },\n      { budget_record_id_type::space_type,       _options.budget         }\n   };\n\n   for( const auto& value: ids )\n   {\n      const auto itr = data_type_map.find( value.space_type() );\n      if( itr == data_type_map.end() || !(itr->second.enabled) )\n         continue;\n      const auto& opt = itr->second;\n      if( action_type::deletion == action )\n         delete_from_database( value, opt );\n      else\n      {\n         switch( itr->first )\n         {\n         case account_id_type::space_type:\n            prepareTemplate( db.get<account_object>(value), opt );\n            break;\n         case account_balance_id_type::space_type:\n            prepareTemplate( db.get<account_balance_object>(value), opt );\n            break;\n         case asset_id_type::space_type:\n            prepareTemplate( db.get<asset_object>(value), opt );\n            break;\n         case asset_bitasset_data_id_type::space_type:\n            prepareTemplate( db.get<asset_bitasset_data_object>(value), opt );\n            break;\n         case limit_order_id_type::space_type:\n            prepareTemplate( db.get<limit_order_object>(value), opt );\n            break;\n         case proposal_id_type::space_type:\n            prepareTemplate( db.get<proposal_object>(value), opt );\n            break;\n         case budget_record_id_type::space_type:\n            prepareTemplate( db.get<budget_record_object>(value), opt );\n            break;\n         default:\n            break;\n         }\n      }\n   }\n\n}\n\nvoid es_objects_plugin_impl::delete_from_database(\n      const object_id_type& id, const es_objects_plugin_impl::plugin_options::object_options& opt )\n{\n   if( opt.no_delete )\n      return;\n\n   fc::mutable_variant_object delete_line;\n   delete_line[\"_id\"] = string(id); // Note: this does not work if `store_updates` is true\n   delete_line[\"_index\"] = _options.index_prefix + opt.index_name;\n   if( !is_es_version_7_or_above )\n      delete_line[\"_type\"] = \"_doc\";\n   fc::mutable_variant_object final_delete_line;\n   final_delete_line[\"delete\"] = std::move( delete_line );\n\n   bulk_lines.push_back( fc::json::to_string(final_delete_line) );\n\n   approximate_bulk_size += bulk_lines.back().size();\n\n   send_bulk_if_ready();\n}\n\nvoid es_objects_plugin_impl::delete_all_from_database( const plugin_options::object_options& opt ) const\n{\n   // Note:\n   // 1. The _delete_by_query API deletes the data but keeps the index mapping, so the function is OK.\n   //    Simply deleting the index is probably faster, but it requires the \"delete_index\" permission, and\n   //    may probably mess up the index mapping and other existing settings.\n   //    Don't know if there is a good way to only delete objects that do not exist in the object database.\n   // 2. We don't check the return value here, it's probably OK\n   es->query( _options.index_prefix + opt.index_name + \"/_delete_by_query\", R\"({\"query\":{\"match_all\":{}}})\" );\n}\n\ntemplate<typename T>\nvoid es_objects_plugin_impl::prepareTemplate(\n      const T& blockchain_object, const es_objects_plugin_impl::plugin_options::object_options& opt )\n{\n   fc::mutable_variant_object bulk_header;\n   bulk_header[\"_index\"] = _options.index_prefix + opt.index_name;\n   if( !is_es_version_7_or_above )\n      bulk_header[\"_type\"] = \"_doc\";\n   if( !opt.store_updates )\n   {\n      bulk_header[\"_id\"] = string(blockchain_object.id);\n   }\n\n   fc::variant blockchain_object_variant;\n   fc::to_variant( blockchain_object, blockchain_object_variant, GRAPHENE_NET_MAX_NESTED_OBJECTS );\n   fc::mutable_variant_object o( utilities::es_data_adaptor::adapt( blockchain_object_variant.get_object(),\n                                                                    _options.max_mapping_depth ) );\n\n   o[\"object_id\"] = string(blockchain_object.id);\n   o[\"block_time\"] = block_time;\n   o[\"block_number\"] = block_number;\n\n   string data = fc::json::to_string(o, fc::json::legacy_generator);\n\n   auto prepare = graphene::utilities::createBulk(bulk_header, std::move(data));\n   std::move(prepare.begin(), prepare.end(), std::back_inserter(bulk_lines));\n\n   approximate_bulk_size += bulk_lines.back().size();\n\n   send_bulk_if_ready();\n}\n\nvoid es_objects_plugin_impl::send_bulk_if_ready( bool force )\n{\n   if( bulk_lines.empty() )\n      return;\n   if( !force && bulk_lines.size() < limit_documents\n         && approximate_bulk_size < graphene::utilities::es_client::request_size_threshold )\n      return;\n   constexpr uint32_t log_count_threshold = 20000; // lines\n   constexpr uint32_t log_time_threshold = 3600; // seconds\n   static uint64_t next_log_count = log_count_threshold;\n   static fc::time_point next_log_time = fc::time_point::now() + fc::seconds(log_time_threshold);\n   docs_sent_batch += bulk_lines.size();\n   docs_sent_total += bulk_lines.size();\n   bool log_by_next = ( docs_sent_total >= next_log_count || fc::time_point::now() >= next_log_time );\n   if( log_by_next || limit_documents == _options.bulk_replay || force )\n   {\n      ilog( \"Sending ${n} lines of bulk data to ElasticSearch at block ${blk}, \"\n            \"this batch ${b}, total ${t}, approximate size ${s}\",\n            (\"n\",bulk_lines.size())(\"blk\",block_number)\n            (\"b\",docs_sent_batch)(\"t\",docs_sent_total)(\"s\",approximate_bulk_size) );\n      next_log_count = docs_sent_total + log_count_threshold;\n      next_log_time = fc::time_point::now() + fc::seconds(log_time_threshold);\n   }\n   // send data to elasticsearch when being forced or bulk is too large\n   if( !es->send_bulk( bulk_lines ) )\n   {\n      elog( \"Error sending ${n} lines of bulk data to ElasticSearch, the first lines are:\", // GCOVR_EXCL_LINE\n            (\"n\",bulk_lines.size()) ); // GCOVR_EXCL_LINE\n      const auto log_max = std::min( bulk_lines.size(), size_t(10) );\n      for( size_t i = 0; i < log_max; ++i )\n      {\n         edump( (bulk_lines[i]) ); // GCOVR_EXCL_LINE\n      }\n      FC_THROW_EXCEPTION( graphene::chain::plugin_exception,\n            \"Error populating ES database, we are going to keep trying.\" );\n   }\n   bulk_lines.clear();\n   bulk_lines.reserve(limit_documents);\n   approximate_bulk_size = 0;\n}\n\n} // end namespace detail\n\nes_objects_plugin::es_objects_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::es_objects_plugin_impl>(*this) )\n{\n   // Nothing else to do\n}\n\nes_objects_plugin::~es_objects_plugin() = default;\n\nstd::string es_objects_plugin::plugin_name()const\n{\n   return \"es_objects\";\n}\nstd::string es_objects_plugin::plugin_description()const\n{\n   return \"Stores blockchain objects in ES database. Experimental.\";\n}\n\nvoid es_objects_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"es-objects-elasticsearch-url\", boost::program_options::value<std::string>(),\n               \"Elasticsearch node url(http://localhost:9200/)\")\n         (\"es-objects-auth\", boost::program_options::value<std::string>(), \"Basic auth username:password('')\")\n         (\"es-objects-bulk-replay\", boost::program_options::value<uint32_t>(),\n               \"Number of bulk documents to index on replay(10000)\")\n         (\"es-objects-bulk-sync\", boost::program_options::value<uint32_t>(),\n               \"Number of bulk documents to index on a synchronized chain(100)\")\n\n         (\"es-objects-proposals\", boost::program_options::value<bool>(), \"Store proposal objects (true)\")\n         (\"es-objects-proposals-store-updates\", boost::program_options::value<bool>(),\n               \"Store all updates to the proposal objects (false)\")\n         (\"es-objects-proposals-no-delete\", boost::program_options::value<bool>(),\n               \"Do not delete a proposal from ES even if it is deleted from chain state. \"\n               \"It is implicitly true and can not be set to false if es-objects-proposals-store-updates is true. \"\n               \"(true)\")\n\n         (\"es-objects-accounts\", boost::program_options::value<bool>(), \"Store account objects (true)\")\n         (\"es-objects-accounts-store-updates\", boost::program_options::value<bool>(),\n               \"Store all updates to the account objects (false)\")\n\n         (\"es-objects-assets\", boost::program_options::value<bool>(), \"Store asset objects (true)\")\n         (\"es-objects-assets-store-updates\", boost::program_options::value<bool>(),\n               \"Store all updates to the asset objects (false)\")\n\n         (\"es-objects-balances\", boost::program_options::value<bool>(), \"Store account balances (true)\")\n         (\"es-objects-balances-store-updates\", boost::program_options::value<bool>(),\n               \"Store all updates to the account balances (false)\")\n\n         (\"es-objects-limit-orders\", boost::program_options::value<bool>(), \"Store limit order objects (true)\")\n         (\"es-objects-limit-orders-store-updates\", boost::program_options::value<bool>(),\n               \"Store all updates to the limit orders (false)\")\n         (\"es-objects-limit-orders-no-delete\", boost::program_options::value<bool>(),\n               \"Do not delete a limit order object from ES even if it is deleted from chain state. \"\n               \"It is implicitly true and can not be set to false if es-objects-limit-orders-store-updates is true. \"\n               \"(false)\")\n\n         (\"es-objects-asset-bitasset\", boost::program_options::value<bool>(),\n               \"Store bitasset data, including price feeds (true)\")\n         (\"es-objects-asset-bitasset-store-updates\", boost::program_options::value<bool>(),\n               \"Store all updates to the bitasset data (false)\")\n\n         (\"es-objects-budget-records\", boost::program_options::value<bool>(), \"Store budget records (true)\")\n\n         (\"es-objects-index-prefix\", boost::program_options::value<std::string>(),\n               \"Add a prefix to the index(objects-)\")\n         (\"es-objects-max-mapping-depth\", boost::program_options::value<uint16_t>(),\n               \"Can not exceed the maximum index mapping depth (index.mapping.depth.limit) setting in ES, \"\n               \"and need to be even smaller to not trigger the index.mapping.total_fields.limit error (10)\")\n         (\"es-objects-keep-only-current\", boost::program_options::value<bool>(),\n               \"Deprecated. Please use the store-updates or no-delete options. \"\n               \"Keep only current state of the objects(true)\")\n         (\"es-objects-start-es-after-block\", boost::program_options::value<uint32_t>(),\n               \"Start doing ES job after block(0)\")\n         (\"es-objects-sync-db-on-startup\", boost::program_options::value<bool>(),\n               \"Copy all applicable objects from the object database (chain state) to ES on program startup (false)\")\n         ;\n   cfg.add(cli);\n}\n\nvoid detail::es_objects_plugin_impl::init_program_options(const boost::program_options::variables_map& options)\n{\n   _options.init( options );\n\n   es = std::make_unique<graphene::utilities::es_client>( _options.elasticsearch_url, _options.auth );\n\n   FC_ASSERT( es->check_status(), \"ES database is not up in url ${url}\", (\"url\", _options.elasticsearch_url) );\n\n   es->check_version_7_or_above( is_es_version_7_or_above );\n}\n\nvoid detail::es_objects_plugin_impl::plugin_options::init(const boost::program_options::variables_map& options)\n{\n   utilities::get_program_option( options, \"es-objects-elasticsearch-url\", elasticsearch_url );\n   utilities::get_program_option( options, \"es-objects-auth\",              auth );\n   utilities::get_program_option( options, \"es-objects-bulk-replay\",       bulk_replay );\n   utilities::get_program_option( options, \"es-objects-bulk-sync\",         bulk_sync );\n   utilities::get_program_option( options, \"es-objects-proposals\",                    proposals.enabled );\n   utilities::get_program_option( options, \"es-objects-proposals-store-updates\",      proposals.store_updates );\n   utilities::get_program_option( options, \"es-objects-proposals-no-delete\",          proposals.no_delete );\n   utilities::get_program_option( options, \"es-objects-accounts\",                     accounts.enabled );\n   utilities::get_program_option( options, \"es-objects-accounts-store-updates\",       accounts.store_updates );\n   utilities::get_program_option( options, \"es-objects-assets\",                       assets.enabled );\n   utilities::get_program_option( options, \"es-objects-assets-store-updates\",         assets.store_updates );\n   utilities::get_program_option( options, \"es-objects-balances\",                     balances.enabled );\n   utilities::get_program_option( options, \"es-objects-balances-store-updates\",       balances.store_updates );\n   utilities::get_program_option( options, \"es-objects-limit-orders\",                 limit_orders.enabled );\n   utilities::get_program_option( options, \"es-objects-limit-orders-store-updates\",   limit_orders.store_updates );\n   utilities::get_program_option( options, \"es-objects-limit-orders-no-delete\",       limit_orders.no_delete );\n   utilities::get_program_option( options, \"es-objects-asset-bitasset\",               asset_bitasset.enabled );\n   utilities::get_program_option( options, \"es-objects-asset-bitasset-store-updates\", asset_bitasset.store_updates );\n   utilities::get_program_option( options, \"es-objects-budget-records\",               budget.enabled );\n   utilities::get_program_option( options, \"es-objects-index-prefix\",         index_prefix );\n   utilities::get_program_option( options, \"es-objects-max-mapping-depth\",    max_mapping_depth );\n   utilities::get_program_option( options, \"es-objects-start-es-after-block\", start_es_after_block );\n   utilities::get_program_option( options, \"es-objects-sync-db-on-startup\",   sync_db_on_startup );\n}\n\nvoid es_objects_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{\n   my->init_program_options( options );\n\n   database().new_objects.connect([this]( const vector<object_id_type>& ids,\n         const flat_set<account_id_type>& ) {\n      my->on_objects_create( ids );\n   });\n   database().changed_objects.connect([this]( const vector<object_id_type>& ids,\n         const flat_set<account_id_type>& ) {\n      my->on_objects_update( ids );\n   });\n   database().removed_objects.connect([this](const vector<object_id_type>& ids,\n         const vector<const object*>&, const flat_set<account_id_type>& ) {\n      my->on_objects_delete( ids );\n   });\n\n}\n\nvoid es_objects_plugin::plugin_startup()\n{\n   if( 0 == database().head_block_num() )\n      my->sync_db( true );\n   else if( my->_options.sync_db_on_startup )\n      my->sync_db();\n}\n\nvoid es_objects_plugin::plugin_shutdown()\n{\n   my->send_bulk_if_ready(true); // flush\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/es_objects/include/graphene/es_objects/es_objects.hpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace es_objects {\n\nusing namespace chain;\n\nnamespace detail\n{\n    class es_objects_plugin_impl;\n}\n\nclass es_objects_plugin : public graphene::app::plugin\n{\n   public:\n      explicit es_objects_plugin(graphene::app::application& app);\n      ~es_objects_plugin() override;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n      void plugin_shutdown() override;\n\n   private:\n      std::unique_ptr<detail::es_objects_plugin_impl> my;\n};\n\n} } //graphene::es_objects\n"
  },
  {
    "path": "libraries/plugins/grouped_orders/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/grouped_orders/*.hpp\")\n\nadd_library( graphene_grouped_orders\n             grouped_orders_plugin.cpp\n           )\n\ntarget_link_libraries( graphene_grouped_orders graphene_app graphene_chain )\ntarget_include_directories( graphene_grouped_orders\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties( grouped_orders_plugin.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_grouped_orders\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/grouped_orders\" )\n\n"
  },
  {
    "path": "libraries/plugins/grouped_orders/grouped_orders_plugin.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/grouped_orders/grouped_orders_plugin.hpp>\n\n#include <graphene/chain/market_object.hpp>\n\nnamespace graphene { namespace grouped_orders {\n\nnamespace detail\n{\n\nclass grouped_orders_plugin_impl\n{\n   public:\n      explicit grouped_orders_plugin_impl(grouped_orders_plugin& _plugin)\n      :_self( _plugin ) {}\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n      grouped_orders_plugin&     _self;\n      flat_set<uint16_t>         _tracked_groups;\n};\n\n/**\n *  @brief This secondary index is used to track changes on limit order objects.\n */\nclass limit_order_group_index : public secondary_index\n{\n   public:\n      limit_order_group_index( const flat_set<uint16_t>& groups ) : _tracked_groups( groups ) {};\n\n      virtual void object_inserted( const object& obj ) override;\n      virtual void object_removed( const object& obj ) override;\n      virtual void about_to_modify( const object& before ) override;\n      virtual void object_modified( const object& after  ) override;\n\n      const flat_set<uint16_t>& get_tracked_groups() const\n      { return _tracked_groups; }\n\n      const map< limit_order_group_key, limit_order_group_data >& get_order_groups() const\n      { return _og_data; }\n\n   private:\n      void remove_order( const limit_order_object& obj, bool remove_empty = true );\n\n      /** tracked groups */\n      flat_set<uint16_t> _tracked_groups;\n\n      /** maps the group key to group data */\n      map< limit_order_group_key, limit_order_group_data > _og_data;\n};\n\nvoid limit_order_group_index::object_inserted( const object& objct )\n{ try {\n   const limit_order_object& o = static_cast<const limit_order_object&>( objct );\n\n   auto& idx = _og_data;\n\n   for( uint16_t group : get_tracked_groups() )\n   {\n      auto create_ogo = [&]() {\n         idx[ limit_order_group_key( group, o.sell_price ) ] = limit_order_group_data( o.sell_price, o.for_sale );\n      };\n      // if idx is empty, insert this order\n      // Note: not capped\n      if( idx.empty() )\n      {\n         create_ogo();\n         continue;\n      }\n\n      // cap the price\n      price capped_price = o.sell_price;\n      price max = o.sell_price.max();\n      price min = o.sell_price.min();\n      bool capped_max = false;\n      bool capped_min = false;\n      if( o.sell_price > max )\n      {\n         capped_price = max;\n         capped_max = true;\n      }\n      else if( o.sell_price < min )\n      {\n         capped_price = min;\n         capped_min = true;\n      }\n      // if idx is not empty, find the group that is next to this order\n      auto itr = idx.lower_bound( limit_order_group_key( group, capped_price ) );\n      bool check_previous = false;\n      if( itr == idx.end() || itr->first.group != group\n            || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id\n            || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id )\n         // not same market or group type\n         check_previous = true;\n      else // same market and group type\n      {\n         bool update_max = false;\n         if( capped_price > itr->second.max_price ) // implies itr->min_price <= itr->max_price < max\n         {\n            update_max = true;\n            price max_price = itr->first.min_price * ratio_type( GRAPHENE_100_PERCENT + group, GRAPHENE_100_PERCENT );\n            // max_price should have been capped here\n            if( capped_price > max_price ) // new order is out of range\n               check_previous = true;\n         }\n         if( !check_previous ) // new order is within the range\n         {\n            if( capped_min && o.sell_price < itr->first.min_price )\n            {  // need to update itr->min_price here, if itr is below min, and new order is even lower\n               // TODO improve performance\n               limit_order_group_data data( itr->second.max_price, o.for_sale + itr->second.total_for_sale );\n               idx.erase( itr );\n               idx[ limit_order_group_key( group, o.sell_price ) ] = data;\n            }\n            else\n            {\n               if( update_max || ( capped_max && o.sell_price > itr->second.max_price ) )\n                  itr->second.max_price = o.sell_price; // store real price here, not capped\n               itr->second.total_for_sale += o.for_sale;\n            }\n         }\n      }\n\n      if( check_previous )\n      {\n         if( itr == idx.begin() ) // no previous\n            create_ogo();\n         else\n         {\n            --itr; // should be valid\n            if( itr->first.group != group || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id\n                                          || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id )\n               // not same market or group type\n               create_ogo();\n            else // same market and group type\n            {\n               // due to lower_bound, always true: capped_price < itr->first.min_price, so no need to check again,\n               // if new order is in range of itr group, always need to update itr->first.min_price, unless\n               //   o.sell_price is higher than max\n               price min_price = itr->second.max_price / ratio_type( GRAPHENE_100_PERCENT + group, GRAPHENE_100_PERCENT );\n               // min_price should have been capped here\n               if( capped_price < min_price ) // new order is out of range\n                  create_ogo();\n               else if( capped_max && o.sell_price >= itr->first.min_price )\n               {  // itr is above max, and price of new order is even higher\n                  if( o.sell_price > itr->second.max_price )\n                     itr->second.max_price = o.sell_price;\n                  itr->second.total_for_sale += o.for_sale;\n               }\n               else\n               {  // new order is within the range\n                  // TODO improve performance\n                  limit_order_group_data data( itr->second.max_price, o.for_sale + itr->second.total_for_sale );\n                  idx.erase( itr );\n                  idx[ limit_order_group_key( group, o.sell_price ) ] = data;\n               }\n            }\n         }\n      }\n   }\n} FC_CAPTURE_AND_RETHROW( (objct) ); }\n\nvoid limit_order_group_index::object_removed( const object& objct )\n{ try {\n   const limit_order_object& o = static_cast<const limit_order_object&>( objct );\n   remove_order( o );\n} FC_CAPTURE_AND_RETHROW( (objct) ); }\n\nvoid limit_order_group_index::about_to_modify( const object& objct )\n{ try {\n   const limit_order_object& o = static_cast<const limit_order_object&>( objct );\n   remove_order( o, false );\n} FC_CAPTURE_AND_RETHROW( (objct) ); }\n\nvoid limit_order_group_index::object_modified( const object& objct )\n{ try {\n   object_inserted( objct );\n} FC_CAPTURE_AND_RETHROW( (objct) ); }\n\nvoid limit_order_group_index::remove_order( const limit_order_object& o, bool remove_empty )\n{\n   auto& idx = _og_data;\n\n   for( uint16_t group : get_tracked_groups() )\n   {\n      // find the group that should contain this order\n      auto itr = idx.lower_bound( limit_order_group_key( group, o.sell_price ) );\n      if( itr == idx.end() || itr->first.group != group\n            || itr->first.min_price.base.asset_id != o.sell_price.base.asset_id\n            || itr->first.min_price.quote.asset_id != o.sell_price.quote.asset_id\n            || itr->second.max_price < o.sell_price )\n      {\n         // can not find corresponding group, should not happen\n         wlog( \"can not find the order group containing order for removing (price dismatch): ${o}\", (\"o\",o) );\n         continue;\n      }\n      else // found\n      {\n         if( itr->second.total_for_sale < o.for_sale )\n            // should not happen\n            wlog( \"can not find the order group containing order for removing (amount dismatch): ${o}\", (\"o\",o) );\n         else if( !remove_empty || itr->second.total_for_sale > o.for_sale )\n            itr->second.total_for_sale -= o.for_sale;\n         else\n            // it's the only order in the group and need to be removed\n            idx.erase( itr );\n      }\n   }\n}\n\n} // end namespace detail\n\n\ngrouped_orders_plugin::grouped_orders_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::grouped_orders_plugin_impl>(*this) )\n{\n   // Nothing else to do\n}\n\ngrouped_orders_plugin::~grouped_orders_plugin() = default;\n\nstd::string grouped_orders_plugin::plugin_name()const\n{\n   return \"grouped_orders\";\n}\n\nvoid grouped_orders_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"tracked-groups\", boost::program_options::value<string>()->default_value(\"[10,100]\"), // 0.1% and 1%\n          \"Group orders by percentage increase on price. Specify a JSON array of numbers here, each number is a group, number 1 means 0.01%. \")\n         ;\n   cfg.add(cli);\n}\n\nvoid grouped_orders_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{ try {\n\n   if( options.count( \"tracked-groups\" ) > 0 )\n   {\n      const std::string& groups = options[\"tracked-groups\"].as<string>();\n      my->_tracked_groups = fc::json::from_string(groups).as<flat_set<uint16_t>>( 2 );\n      my->_tracked_groups.erase( 0 );\n   }\n   else\n      my->_tracked_groups = fc::json::from_string(\"[10,100]\").as<flat_set<uint16_t>>(2);\n\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid grouped_orders_plugin::plugin_startup()\n{\n   auto& groups = *database().add_secondary_index< primary_index<limit_order_index>,\n                                                   detail::limit_order_group_index >( my->_tracked_groups );\n   for( const auto& order : database().get_index_type< limit_order_index >().indices() )\n      groups.object_inserted( order );\n}\n\nconst flat_set<uint16_t>& grouped_orders_plugin::tracked_groups() const\n{\n   return my->_tracked_groups;\n}\n\nconst map< limit_order_group_key, limit_order_group_data >& grouped_orders_plugin::limit_order_groups()\n{\n   const auto& idx = database().get_index_type< limit_order_index >();\n   const auto& pidx = dynamic_cast<const primary_index< limit_order_index >&>(idx);\n   const auto& logidx = pidx.get_secondary_index< detail::limit_order_group_index >();\n   return logidx.get_order_groups();\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/grouped_orders/include/graphene/grouped_orders/grouped_orders_plugin.hpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace grouped_orders {\nusing namespace chain;\n\nstruct limit_order_group_key\n{\n   limit_order_group_key( const uint16_t g, const price& p ) : group(g), min_price(p) {}\n   limit_order_group_key() {}\n\n   uint16_t      group = 0; ///< percentage, 1 means 1 / 10000\n   price         min_price;\n\n   friend bool operator < ( const limit_order_group_key& a, const limit_order_group_key& b )\n   {\n      // price is ordered descendingly, same as limit_order_index\n      return std::tie( a.group, b.min_price ) < std::tie( b.group, a.min_price );\n   }\n   friend bool operator == ( const limit_order_group_key& a, const limit_order_group_key& b )\n   {\n      return std::tie( a.group, a.min_price ) == std::tie( b.group, b.min_price );\n   }\n};\n\nstruct limit_order_group_data\n{\n   limit_order_group_data( const price& p, const share_type s ) : max_price(p), total_for_sale(s) {}\n   limit_order_group_data() {}\n\n   price         max_price;\n   share_type    total_for_sale; ///< asset id is min_price.base.asset_id\n};\n\nnamespace detail\n{\n    class grouped_orders_plugin_impl;\n}\n\n/**\n *  The grouped orders plugin can be configured to track any number of price diff percentages via its configuration.\n *  Every time when there is a change on an order in object database, it will update internal state to reflect the change.\n */\nclass grouped_orders_plugin : public graphene::app::plugin\n{\n   public:\n      explicit grouped_orders_plugin(graphene::app::application& app);\n      ~grouped_orders_plugin() override;\n\n      std::string plugin_name()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(\n         const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n\n      const flat_set<uint16_t>&   tracked_groups()const;\n\n      const map< limit_order_group_key, limit_order_group_data >& limit_order_groups();\n\n   private:\n      std::unique_ptr<detail::grouped_orders_plugin_impl> my;\n};\n\n} } //graphene::grouped_orders\n\nFC_REFLECT( graphene::grouped_orders::limit_order_group_key, (group)(min_price) )\nFC_REFLECT( graphene::grouped_orders::limit_order_group_data, (max_price)(total_for_sale) )\n"
  },
  {
    "path": "libraries/plugins/make_new_plugin.sh",
    "content": "#!/bin/bash\n\n# Find next available space id by checking all current plugins\nnext_space_id () {\n    SPACE_IDS=()\n    for i in * ; do\n        if [ -d \"$i\" ] && [ \"$i\" != \"CMakeFiles\" ]; then\n            cd \"$i/include/graphene/$i\"\n            result=$(grep -rnw '.' -e '#define[[:space:]]*[[:alnum:]]*_SPACE_ID')\n            B=$(echo $result | cut -d ' ' -f 3)\n            if [[ $B =~ [[:digit:]] ]]; then\n                SPACE_IDS+=($B)\n            fi\n            cd \"../../../..\"\n        fi\n    done\n    max=$( printf \"%d\\n\" \"${SPACE_IDS[@]}\" | sort -n | tail -1 )\n    next=$(($max + 1))\n    return $next\n}\n\n## create new plugin\nif [ $# -ne 1 ]; then\n    echo \"Usage: $0 my_new_plugin\"\n    echo \"... where my_new_plugin is the name of the plugin you want to create\"\n    exit 1\nfi\n\npluginName=\"$1\"\n\necho \"Copying template...\"\ncp -r template_plugin \"$pluginName\"\n\necho \"Renaming files/directories...\"\nmv \"$pluginName/include/graphene/template_plugin\" \"$pluginName/include/graphene/$pluginName\"\nfor file in `find \"$pluginName\" -type f -name '*template_plugin*'`; do mv \"$file\" `sed s/template_plugin/\"$pluginName\"/g <<< $file`; done;\necho \"Renaming in files...\"\nfind \"$pluginName\" -type f -exec sed -i \"s/template_plugin/$pluginName/g\" {} \\;\necho \"Assigning next available SPACE_ID...\"\nnext_space_id\nfind \"$pluginName\" -type f -exec sed -i \"s/@SPACE_ID@/$?/g\" {} \\;\n\necho \"Done! $pluginName is ready.\"\necho \"Next steps:\"\necho \"1- Add 'add_subdirectory( $pluginName )' to CmakeLists.txt in this directory.\"\necho \"2- Add 'graphene_$pluginName' to ../../programs/witness_node/CMakeLists.txt with the other plugins.\"\necho \"3- Include plugin header file '#include <graphene/$pluginName/$pluginName.hpp>' to ../../programs/witness_node/main.cpp.\"\necho \"4- Initialize plugin with the others with 'auto ${pluginName}_plug = node->register_plugin<$pluginName::$pluginName>();' in ../../programs/witness_node/main.cpp\"\necho \"5- cmake and make\"\necho \"6- Start plugin with './../programs/witness_node/witness_node --plugins \\\"$pluginName\\\"'. After the seed nodes are added you start to see see a msgs from the plugin 'onBlock' \"\n"
  },
  {
    "path": "libraries/plugins/market_history/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/market_history/*.hpp\")\n\nadd_library( graphene_market_history \n             market_history_plugin.cpp\n           )\n\ntarget_link_libraries( graphene_market_history graphene_app graphene_chain )\ntarget_include_directories( graphene_market_history\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties( market_history_plugin.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_market_history\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/market_history\" )\n\n"
  },
  {
    "path": "libraries/plugins/market_history/include/graphene/market_history/market_history_plugin.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n\n#include <fc/thread/future.hpp>\n#include <fc/uint128.hpp>\n\n#include <boost/multi_index/composite_key.hpp>\n\nnamespace graphene { namespace market_history {\nusing namespace chain;\n\n//\n// Plugins should #define their SPACE_ID's so plugins with\n// conflicting SPACE_ID assignments can be compiled into the\n// same binary (by simply re-assigning some of the conflicting #defined\n// SPACE_ID's in a build script).\n//\n// Assignment of SPACE_ID's cannot be done at run-time because\n// various template automagic depends on them being known at compile\n// time.\n//\n#ifndef MARKET_HISTORY_SPACE_ID\n#define MARKET_HISTORY_SPACE_ID 5\n#endif\n\nenum market_history_object_type\n{\n   order_history_object_type = 0,\n   bucket_object_type = 1,\n   market_ticker_object_type = 2,\n   market_ticker_meta_object_type = 3,\n   // LP = liquidity pool\n   lp_history_object_type = 4,\n   lp_ticker_meta_object_type = 5,\n   lp_ticker_object_type = 6\n};\n\nstruct bucket_key\n{\n   bucket_key( asset_id_type a, asset_id_type b, uint32_t s, fc::time_point_sec o )\n   :base(a),quote(b),seconds(s),open(o){}\n   bucket_key(){}\n\n   asset_id_type      base;\n   asset_id_type      quote;\n   uint32_t           seconds = 0;\n   fc::time_point_sec open;\n\n   friend bool operator < ( const bucket_key& a, const bucket_key& b )\n   {\n      return std::tie( a.base, a.quote, a.seconds, a.open ) < std::tie( b.base, b.quote, b.seconds, b.open );\n   }\n   friend bool operator == ( const bucket_key& a, const bucket_key& b )\n   {\n      return std::tie( a.base, a.quote, b.seconds, a.open ) == std::tie( b.base, b.quote, b.seconds, b.open );\n   }\n};\n\nstruct bucket_object : public abstract_object<bucket_object, MARKET_HISTORY_SPACE_ID, bucket_object_type>\n{\n   price high()const { return asset( high_base, key.base ) / asset( high_quote, key.quote ); }\n   price low()const { return asset( low_base, key.base ) / asset( low_quote, key.quote ); }\n\n   bucket_key          key;\n   share_type          high_base;\n   share_type          high_quote;\n   share_type          low_base;\n   share_type          low_quote;\n   share_type          open_base;\n   share_type          open_quote;\n   share_type          close_base;\n   share_type          close_quote;\n   share_type          base_volume;\n   share_type          quote_volume;\n};\n\nstruct history_key {\n  asset_id_type        base;\n  asset_id_type        quote;\n  int64_t              sequence = 0;\n\n  friend bool operator < ( const history_key& a, const history_key& b ) {\n    return std::tie( a.base, a.quote, a.sequence ) < std::tie( b.base, b.quote, b.sequence );\n  }\n  friend bool operator == ( const history_key& a, const history_key& b ) {\n    return std::tie( a.base, a.quote, a.sequence ) == std::tie( b.base, b.quote, b.sequence );\n  }\n};\nstruct order_history_object : public abstract_object<order_history_object,\n                                        MARKET_HISTORY_SPACE_ID, order_history_object_type>\n{\n   history_key          key;\n   fc::time_point_sec   time;\n   fill_order_operation op;\n};\nstruct order_history_object_key_base_extractor\n{\n   using result_type = asset_id_type;\n   result_type operator()(const order_history_object& o)const { return o.key.base; }\n};\nstruct order_history_object_key_quote_extractor\n{\n   using result_type = asset_id_type;\n   result_type operator()(const order_history_object& o)const { return o.key.quote; }\n};\nstruct order_history_object_key_sequence_extractor\n{\n   using result_type = int64_t;\n   result_type operator()(const order_history_object& o)const { return o.key.sequence; }\n};\n\nstruct market_ticker_object : public abstract_object<market_ticker_object,\n                                        MARKET_HISTORY_SPACE_ID, market_ticker_object_type>\n{\n   asset_id_type       base;\n   asset_id_type       quote;\n   share_type          last_day_base;\n   share_type          last_day_quote;\n   share_type          latest_base;\n   share_type          latest_quote;\n   fc::uint128_t       base_volume;\n   fc::uint128_t       quote_volume;\n};\n\nstruct market_ticker_meta_object : public abstract_object<market_ticker_meta_object,\n                                             MARKET_HISTORY_SPACE_ID, market_ticker_meta_object_type>\n{\n   object_id_type      rolling_min_order_his_id;\n   bool                skip_min_order_his_id = false;\n};\n\nstruct by_key;\nusing bucket_object_multi_index_type = multi_index_container<\n   bucket_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_key>, member< bucket_object, bucket_key, &bucket_object::key > >\n   >\n>;\n\nstruct by_market_time;\nusing order_history_multi_index_type = multi_index_container<\n   order_history_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_key>, member< order_history_object, history_key, &order_history_object::key > >,\n      ordered_unique<\n         tag<by_market_time>,\n         composite_key<\n            order_history_object,\n            order_history_object_key_base_extractor,\n            order_history_object_key_quote_extractor,\n            member<order_history_object, time_point_sec, &order_history_object::time>,\n            order_history_object_key_sequence_extractor\n         >,\n         composite_key_compare<\n            std::less< asset_id_type >,\n            std::less< asset_id_type >,\n            std::greater< time_point_sec >,\n            std::less< int64_t >\n         >\n      >\n   >\n>;\n\nstruct by_market;\nstruct by_volume;\nusing market_ticker_obj_mlti_idx_type = multi_index_container<\n   market_ticker_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_non_unique< tag<by_volume>,\n                          member< market_ticker_object, fc::uint128_t, &market_ticker_object::base_volume > >,\n      ordered_unique<\n         tag<by_market>,\n         composite_key<\n            market_ticker_object,\n            member<market_ticker_object, asset_id_type, &market_ticker_object::base>,\n            member<market_ticker_object, asset_id_type, &market_ticker_object::quote>\n         >\n      >\n   >\n>;\n\nusing bucket_index = generic_index<bucket_object, bucket_object_multi_index_type>;\nusing history_index = generic_index<order_history_object, order_history_multi_index_type>;\nusing market_ticker_index = generic_index<market_ticker_object, market_ticker_obj_mlti_idx_type>;\n\n\n/** Stores operation histories related to liquidity pools */\nstruct liquidity_pool_history_object : public abstract_object<liquidity_pool_history_object,\n                                                 MARKET_HISTORY_SPACE_ID, lp_history_object_type>\n{\n   liquidity_pool_id_type   pool;\n   uint64_t                 sequence = 0;\n   fc::time_point_sec       time;\n   int64_t                  op_type;\n   operation_history_object op;\n};\n\nstruct by_pool_seq;\nstruct by_pool_time;\nstruct by_pool_op_type_seq;\nstruct by_pool_op_type_time;\n\nusing lp_history_multi_index_type = multi_index_container<\n   liquidity_pool_history_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,\n      ordered_unique< tag<by_pool_seq>,\n         composite_key< liquidity_pool_history_object,\n            member<liquidity_pool_history_object, liquidity_pool_id_type, &liquidity_pool_history_object::pool>,\n            member<liquidity_pool_history_object, uint64_t, &liquidity_pool_history_object::sequence>\n         >,\n         composite_key_compare<\n            std::less< liquidity_pool_id_type >,\n            std::greater< uint64_t >\n         >\n      >,\n      ordered_unique< tag<by_pool_time>,\n         composite_key< liquidity_pool_history_object,\n            member<liquidity_pool_history_object, liquidity_pool_id_type, &liquidity_pool_history_object::pool>,\n            member<liquidity_pool_history_object, time_point_sec, &liquidity_pool_history_object::time>,\n            member<liquidity_pool_history_object, uint64_t, &liquidity_pool_history_object::sequence>\n         >,\n         composite_key_compare<\n            std::less< liquidity_pool_id_type >,\n            std::greater< time_point_sec >,\n            std::greater< uint64_t >\n         >\n      >,\n      ordered_unique< tag<by_pool_op_type_seq>,\n         composite_key< liquidity_pool_history_object,\n            member<liquidity_pool_history_object, liquidity_pool_id_type, &liquidity_pool_history_object::pool>,\n            member<liquidity_pool_history_object, int64_t, &liquidity_pool_history_object::op_type>,\n            member<liquidity_pool_history_object, uint64_t, &liquidity_pool_history_object::sequence>\n         >,\n         composite_key_compare<\n            std::less< liquidity_pool_id_type >,\n            std::less< int64_t >,\n            std::greater< uint64_t >\n         >\n      >,\n      ordered_unique< tag<by_pool_op_type_time>,\n         composite_key< liquidity_pool_history_object,\n            member<liquidity_pool_history_object, liquidity_pool_id_type, &liquidity_pool_history_object::pool>,\n            member<liquidity_pool_history_object, int64_t, &liquidity_pool_history_object::op_type>,\n            member<liquidity_pool_history_object, time_point_sec, &liquidity_pool_history_object::time>,\n            member<liquidity_pool_history_object, uint64_t, &liquidity_pool_history_object::sequence>\n         >,\n         composite_key_compare<\n            std::less< liquidity_pool_id_type >,\n            std::less< int64_t >,\n            std::greater< time_point_sec >,\n            std::greater< uint64_t >\n         >\n      >\n   >\n>;\n\nusing liquidity_pool_history_index = generic_index< liquidity_pool_history_object, lp_history_multi_index_type >;\n\n\n/// Stores meta data for liquidity pool tickers\nstruct lp_ticker_meta_object : public abstract_object<lp_ticker_meta_object,\n                                         MARKET_HISTORY_SPACE_ID, lp_ticker_meta_object_type>\n{\n   object_id_type      rolling_min_lp_his_id;\n   bool                skip_min_lp_his_id = false;\n};\n\nusing liquidity_pool_ticker_id_type = object_id<MARKET_HISTORY_SPACE_ID, lp_ticker_object_type>;\n\n/// Stores ticker data for liquidity pools\nstruct liquidity_pool_ticker_object : public abstract_object<liquidity_pool_ticker_object,\n                                                MARKET_HISTORY_SPACE_ID, lp_ticker_object_type>\n{\n   uint32_t            _24h_deposit_count = 0;\n   fc::uint128_t       _24h_deposit_amount_a = 0;\n   fc::uint128_t       _24h_deposit_amount_b = 0;\n   fc::uint128_t       _24h_deposit_share_amount = 0;\n   uint32_t            _24h_withdrawal_count = 0;\n   fc::uint128_t       _24h_withdrawal_amount_a = 0;\n   fc::uint128_t       _24h_withdrawal_amount_b = 0;\n   fc::uint128_t       _24h_withdrawal_share_amount = 0;\n   fc::uint128_t       _24h_withdrawal_fee_a = 0;\n   fc::uint128_t       _24h_withdrawal_fee_b = 0;\n   uint32_t            _24h_exchange_a2b_count = 0;\n   fc::uint128_t       _24h_exchange_a2b_amount_a = 0;\n   fc::uint128_t       _24h_exchange_a2b_amount_b = 0;\n   uint32_t            _24h_exchange_b2a_count = 0;\n   fc::uint128_t       _24h_exchange_b2a_amount_a = 0;\n   fc::uint128_t       _24h_exchange_b2a_amount_b = 0;\n   fc::uint128_t       _24h_exchange_fee_a = 0;\n   fc::uint128_t       _24h_exchange_fee_b = 0;\n   share_type          _24h_balance_delta_a;\n   share_type          _24h_balance_delta_b;\n   uint64_t            total_deposit_count = 0;\n   fc::uint128_t       total_deposit_amount_a = 0;\n   fc::uint128_t       total_deposit_amount_b = 0;\n   fc::uint128_t       total_deposit_share_amount = 0;\n   uint64_t            total_withdrawal_count = 0;\n   fc::uint128_t       total_withdrawal_amount_a = 0;\n   fc::uint128_t       total_withdrawal_amount_b = 0;\n   fc::uint128_t       total_withdrawal_share_amount = 0;\n   fc::uint128_t       total_withdrawal_fee_a = 0;\n   fc::uint128_t       total_withdrawal_fee_b = 0;\n   uint64_t            total_exchange_a2b_count = 0;\n   fc::uint128_t       total_exchange_a2b_amount_a = 0;\n   fc::uint128_t       total_exchange_a2b_amount_b = 0;\n   uint64_t            total_exchange_b2a_count = 0;\n   fc::uint128_t       total_exchange_b2a_amount_a = 0;\n   fc::uint128_t       total_exchange_b2a_amount_b = 0;\n   fc::uint128_t       total_exchange_fee_a = 0;\n   fc::uint128_t       total_exchange_fee_b = 0;\n};\n\nusing lp_ticker_multi_index_type = multi_index_container<\n   liquidity_pool_ticker_object,\n   indexed_by<\n      ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >\n   >\n>;\n\nusing liquidity_pool_ticker_index = generic_index< liquidity_pool_ticker_object, lp_ticker_multi_index_type >;\n\n\nnamespace detail\n{\n    class market_history_plugin_impl;\n}\n\n/**\n *  The market history plugin can be configured to track any number of intervals via its configuration.\n *  Once per block it will scan the virtual operations and look for fill_order_operations and then adjust\n *  the appropriate bucket objects for each fill order.\n */\nclass market_history_plugin : public graphene::app::plugin\n{\n   public:\n      explicit market_history_plugin(graphene::app::application& app);\n      ~market_history_plugin() override;\n\n      std::string plugin_name()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(\n         const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n\n      uint32_t                    max_history()const;\n      const flat_set<uint32_t>&   tracked_buckets()const;\n      uint32_t                    max_order_his_records_per_market()const;\n      uint32_t                    max_order_his_seconds_per_market()const;\n\n   private:\n      std::unique_ptr<detail::market_history_plugin_impl> my;\n};\n\n} } //graphene::market_history\n\n// Other objects aren't mapped here because they are not used\nMAP_OBJECT_ID_TO_TYPE( graphene::market_history::liquidity_pool_ticker_object )\n\nFC_REFLECT( graphene::market_history::history_key, (base)(quote)(sequence) )\nFC_REFLECT_DERIVED( graphene::market_history::order_history_object, (graphene::db::object), (key)(time)(op) )\nFC_REFLECT( graphene::market_history::bucket_key, (base)(quote)(seconds)(open) )\nFC_REFLECT_DERIVED( graphene::market_history::bucket_object, (graphene::db::object),\n                    (key)\n                    (high_base)(high_quote)\n                    (low_base)(low_quote)\n                    (open_base)(open_quote)\n                    (close_base)(close_quote)\n                    (base_volume)(quote_volume) )\nFC_REFLECT_DERIVED( graphene::market_history::market_ticker_object, (graphene::db::object),\n                    (base)(quote)\n                    (last_day_base)(last_day_quote)\n                    (latest_base)(latest_quote)\n                    (base_volume)(quote_volume) )\nFC_REFLECT_DERIVED( graphene::market_history::market_ticker_meta_object, (graphene::db::object),\n                    (rolling_min_order_his_id)(skip_min_order_his_id) )\nFC_REFLECT_DERIVED( graphene::market_history::liquidity_pool_history_object, (graphene::db::object),\n                    (pool)(sequence)(time)(op_type)(op) )\nFC_REFLECT_DERIVED( graphene::market_history::lp_ticker_meta_object, (graphene::db::object),\n                    (rolling_min_lp_his_id)(skip_min_lp_his_id) )\nFC_REFLECT_DERIVED( graphene::market_history::liquidity_pool_ticker_object, (graphene::db::object),\n                    (_24h_deposit_count)\n                    (_24h_deposit_amount_a)\n                    (_24h_deposit_amount_b)\n                    (_24h_deposit_share_amount)\n                    (_24h_withdrawal_count)\n                    (_24h_withdrawal_amount_a)\n                    (_24h_withdrawal_amount_b)\n                    (_24h_withdrawal_share_amount)\n                    (_24h_withdrawal_fee_a)\n                    (_24h_withdrawal_fee_b)\n                    (_24h_exchange_a2b_count)\n                    (_24h_exchange_a2b_amount_a)\n                    (_24h_exchange_a2b_amount_b)\n                    (_24h_exchange_b2a_count)\n                    (_24h_exchange_b2a_amount_a)\n                    (_24h_exchange_b2a_amount_b)\n                    (_24h_exchange_fee_a)\n                    (_24h_exchange_fee_b)\n                    (_24h_balance_delta_a)\n                    (_24h_balance_delta_b)\n                    (total_deposit_count)\n                    (total_deposit_amount_a)\n                    (total_deposit_amount_b)\n                    (total_deposit_share_amount)\n                    (total_withdrawal_count)\n                    (total_withdrawal_amount_a)\n                    (total_withdrawal_amount_b)\n                    (total_withdrawal_share_amount)\n                    (total_withdrawal_fee_a)\n                    (total_withdrawal_fee_b)\n                    (total_exchange_a2b_count)\n                    (total_exchange_a2b_amount_a)\n                    (total_exchange_a2b_amount_b)\n                    (total_exchange_b2a_count)\n                    (total_exchange_b2a_amount_a)\n                    (total_exchange_b2a_amount_b)\n                    (total_exchange_fee_a)\n                    (total_exchange_fee_b)\n                  )\n"
  },
  {
    "path": "libraries/plugins/market_history/market_history_plugin.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/market_history/market_history_plugin.hpp>\n\n#include <graphene/chain/account_evaluator.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/config.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/evaluator.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/transaction_evaluation_state.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/thread/thread.hpp>\n\nnamespace graphene { namespace market_history {\n\nnamespace detail\n{\n\nclass market_history_plugin_impl\n{\n   public:\n      explicit market_history_plugin_impl(market_history_plugin& _plugin)\n      :_self( _plugin ) {}\n\n      /** this method is called as a callback after a block is applied\n       * and will process/index all operations that were applied in the block.\n       */\n      void update_market_histories( const signed_block& b );\n\n      /// process all operations related to liquidity pools\n      void update_liquidity_pool_histories( time_point_sec time, const operation_history_object& oho,\n                                            const lp_ticker_meta_object*& lp_meta );\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n      market_history_plugin&     _self;\n      flat_set<uint32_t>         _tracked_buckets;\n      uint32_t                   _maximum_history_per_bucket_size = 1000;\n      uint32_t                   _max_order_his_records_per_market = 1000;\n      uint32_t                   _max_order_his_seconds_per_market = 259200;\n};\n\n\nstruct operation_process_fill_order\n{\n   market_history_plugin&            _plugin;\n   fc::time_point_sec                _now;\n   const market_ticker_meta_object*& _meta;\n\n   operation_process_fill_order( market_history_plugin& mhp, fc::time_point_sec n, const market_ticker_meta_object*& meta )\n   :_plugin(mhp),_now(n),_meta(meta) {}\n\n   typedef void result_type;\n\n   /** do nothing for other operation types */\n   template<typename T>\n   void operator()( const T& )const{}\n\n   void operator()( const fill_order_operation& o )const\n   {\n      //ilog( \"processing ${o}\", (\"o\",o) );\n      auto& db         = _plugin.database();\n      const auto& order_his_idx = db.get_index_type<history_index>().indices();\n      const auto& history_idx = order_his_idx.get<by_key>();\n      const auto& his_time_idx = order_his_idx.get<by_market_time>();\n\n      // To save new filled order data\n      history_key hkey;\n      hkey.base = o.pays.asset_id;\n      hkey.quote = o.receives.asset_id;\n      if( hkey.base > hkey.quote )\n         std::swap( hkey.base, hkey.quote );\n      hkey.sequence = std::numeric_limits<int64_t>::min();\n\n      auto itr = history_idx.lower_bound( hkey );\n\n      if( itr != history_idx.end() && itr->key.base == hkey.base && itr->key.quote == hkey.quote )\n         hkey.sequence = itr->key.sequence - 1;\n      else\n         hkey.sequence = 0;\n\n      const auto& new_order_his_obj = db.create<order_history_object>( [&]( order_history_object& ho ) {\n         ho.key = hkey;\n         ho.time = _now;\n         ho.op = o;\n      });\n\n      // save a reference to market ticker meta object\n      if( _meta == nullptr )\n      {\n         const auto& meta_idx = db.get_index_type<simple_index<market_ticker_meta_object>>();\n         if( meta_idx.size() == 0 )\n            _meta = &db.create<market_ticker_meta_object>( [&]( market_ticker_meta_object& mtm ) {\n               mtm.rolling_min_order_his_id = new_order_his_obj.id;\n               mtm.skip_min_order_his_id = false;\n            });\n         else\n            _meta = &( *meta_idx.begin() );\n      }\n\n      // To remove old filled order data\n      const auto max_records = _plugin.max_order_his_records_per_market();\n      hkey.sequence += max_records;\n      itr = history_idx.lower_bound( hkey );\n      if( itr != history_idx.end() && itr->key.base == hkey.base && itr->key.quote == hkey.quote )\n      {\n         const auto max_seconds = _plugin.max_order_his_seconds_per_market();\n         fc::time_point_sec min_time;\n         if( min_time + max_seconds < _now )\n            min_time = _now - max_seconds;\n         auto time_itr = his_time_idx.lower_bound( std::make_tuple( hkey.base, hkey.quote, min_time ) );\n         if( time_itr != his_time_idx.end() && time_itr->key.base == hkey.base && time_itr->key.quote == hkey.quote )\n         {\n            if( itr->key.sequence >= time_itr->key.sequence )\n            {\n               while( itr != history_idx.end() && itr->key.base == hkey.base && itr->key.quote == hkey.quote )\n               {\n                  auto old_itr = itr;\n                  ++itr;\n                  db.remove( *old_itr );\n               }\n            }\n            else\n            {\n               while( time_itr != his_time_idx.end() && time_itr->key.base == hkey.base && time_itr->key.quote == hkey.quote )\n               {\n                  auto old_itr = time_itr;\n                  ++time_itr;\n                  db.remove( *old_itr );\n               }\n            }\n         }\n      }\n\n      // To update ticker data and buckets data, only update for maker orders\n      if( !o.is_maker )\n         return;\n\n      bucket_key key;\n      key.base    = o.pays.asset_id;\n      key.quote   = o.receives.asset_id;\n\n      price trade_price = o.pays / o.receives;\n\n      if( key.base > key.quote )\n      {\n         std::swap( key.base, key.quote );\n         trade_price = ~trade_price;\n      }\n\n      price fill_price = o.fill_price;\n      if( fill_price.base.asset_id > fill_price.quote.asset_id )\n         fill_price = ~fill_price;\n\n      // To update ticker data\n      const auto& ticker_idx = db.get_index_type<market_ticker_index>().indices().get<by_market>();\n      auto ticker_itr = ticker_idx.find( std::make_tuple( key.base, key.quote ) );\n      if( ticker_itr == ticker_idx.end() )\n      {\n         db.create<market_ticker_object>( [&]( market_ticker_object& mt ) {\n            mt.base           = key.base;\n            mt.quote          = key.quote;\n            mt.last_day_base  = 0;\n            mt.last_day_quote = 0;\n            mt.latest_base    = fill_price.base.amount;\n            mt.latest_quote   = fill_price.quote.amount;\n            mt.base_volume    = trade_price.base.amount.value;\n            mt.quote_volume   = trade_price.quote.amount.value;\n         });\n      }\n      else\n      {\n         db.modify( *ticker_itr, [&]( market_ticker_object& mt ) {\n            mt.latest_base    = fill_price.base.amount;\n            mt.latest_quote   = fill_price.quote.amount;\n            mt.base_volume    += trade_price.base.amount.value;  // ignore overflow\n            mt.quote_volume   += trade_price.quote.amount.value; // ignore overflow\n         });\n      }\n\n      // To update buckets data\n      const auto max_history = _plugin.max_history();\n      if( max_history == 0 ) return;\n\n      const auto& buckets = _plugin.tracked_buckets();\n      if( buckets.size() == 0 ) return;\n\n      const auto& bucket_idx = db.get_index_type<bucket_index>();\n      for( auto bucket : buckets )\n      {\n          auto bucket_num = _now.sec_since_epoch() / bucket;\n          fc::time_point_sec cutoff;\n          if( bucket_num > max_history )\n             cutoff = cutoff + ( bucket * ( bucket_num - max_history ) );\n\n          key.seconds = bucket;\n          key.open    = fc::time_point_sec() + ( bucket_num * bucket );\n\n          const auto& by_key_idx = bucket_idx.indices().get<by_key>();\n          auto bucket_itr = by_key_idx.find( key );\n          if( bucket_itr == by_key_idx.end() )\n          { // create new bucket\n            /* const auto& obj = */\n            db.create<bucket_object>( [&]( bucket_object& b ){\n                 b.key = key;\n                 b.base_volume = trade_price.base.amount;\n                 b.quote_volume = trade_price.quote.amount;\n                 b.open_base = fill_price.base.amount;\n                 b.open_quote = fill_price.quote.amount;\n                 b.close_base = fill_price.base.amount;\n                 b.close_quote = fill_price.quote.amount;\n                 b.high_base = b.close_base;\n                 b.high_quote = b.close_quote;\n                 b.low_base = b.close_base;\n                 b.low_quote = b.close_quote;\n            });\n            //wlog( \"    creating bucket ${b}\", (\"b\",obj) );\n          }\n          else\n          { // update existing bucket\n             //wlog( \"    before updating bucket ${b}\", (\"b\",*bucket_itr) );\n             db.modify( *bucket_itr, [&]( bucket_object& b ){\n                  try {\n                     b.base_volume += trade_price.base.amount;\n                  } catch( fc::overflow_exception& ) {\n                     b.base_volume = std::numeric_limits<int64_t>::max();\n                  }\n                  try {\n                     b.quote_volume += trade_price.quote.amount;\n                  } catch( fc::overflow_exception& ) {\n                     b.quote_volume = std::numeric_limits<int64_t>::max();\n                  }\n                  b.close_base = fill_price.base.amount;\n                  b.close_quote = fill_price.quote.amount;\n                  if( b.high() < fill_price )\n                  {\n                      b.high_base = b.close_base;\n                      b.high_quote = b.close_quote;\n                  }\n                  if( b.low() > fill_price )\n                  {\n                      b.low_base = b.close_base;\n                      b.low_quote = b.close_quote;\n                  }\n             });\n             //wlog( \"    after bucket bucket ${b}\", (\"b\",*bucket_itr) );\n          }\n\n          {\n             key.open = fc::time_point_sec();\n             bucket_itr = by_key_idx.lower_bound( key );\n\n             while( bucket_itr != by_key_idx.end() &&\n                    bucket_itr->key.base == key.base &&\n                    bucket_itr->key.quote == key.quote &&\n                    bucket_itr->key.seconds == bucket &&\n                    bucket_itr->key.open < cutoff )\n             {\n              //  elog( \"    removing old bucket ${b}\", (\"b\", *bucket_itr) );\n                auto old_bucket_itr = bucket_itr;\n                ++bucket_itr;\n                db.remove( *old_bucket_itr );\n             }\n          }\n      }\n   }\n};\n\nvoid market_history_plugin_impl::update_market_histories( const signed_block& b )\n{\n   graphene::chain::database& db = database();\n\n   const market_ticker_meta_object* _meta = nullptr;\n   const auto& meta_idx = db.get_index_type<simple_index<market_ticker_meta_object>>();\n   if( meta_idx.size() > 0 )\n      _meta = &( *meta_idx.begin() );\n\n   const lp_ticker_meta_object* _lp_meta = nullptr;\n   const auto& lp_meta_idx = db.get_index_type<simple_index<lp_ticker_meta_object>>();\n   if( lp_meta_idx.size() > 0 )\n      _lp_meta = &( *lp_meta_idx.begin() );\n\n   const vector<optional< operation_history_object > >& hist = db.get_applied_operations();\n   for( const optional< operation_history_object >& o_op : hist )\n   {\n      if( o_op.valid() )\n      {\n         // process market history\n         try\n         {\n            o_op->op.visit( operation_process_fill_order( _self, b.timestamp, _meta ) );\n         } FC_CAPTURE_AND_LOG( (o_op) )\n         // process liquidity pool history\n         update_liquidity_pool_histories( b.timestamp, *o_op, _lp_meta );\n      }\n   }\n   // roll out expired data from ticker\n   if( _meta != nullptr )\n   {\n      time_point_sec last_day = b.timestamp - 86400;\n      object_id_type last_min_his_id = _meta->rolling_min_order_his_id;\n      bool skip = _meta->skip_min_order_his_id;\n\n      const auto& ticker_idx = db.get_index_type<market_ticker_index>().indices().get<by_market>();\n      const auto& history_idx = db.get_index_type<history_index>().indices().get<by_id>();\n      auto history_itr = history_idx.lower_bound( _meta->rolling_min_order_his_id );\n      while( history_itr != history_idx.end() && history_itr->time < last_day )\n      {\n         const fill_order_operation& o = history_itr->op;\n         if( skip && history_itr->id == _meta->rolling_min_order_his_id )\n            skip = false;\n         else if( o.is_maker )\n         {\n            bucket_key key;\n            key.base    = o.pays.asset_id;\n            key.quote   = o.receives.asset_id;\n\n            price trade_price = o.pays / o.receives;\n\n            if( key.base > key.quote )\n            {\n               std::swap( key.base, key.quote );\n               trade_price = ~trade_price;\n            }\n\n            price fill_price = o.fill_price;\n            if( fill_price.base.asset_id > fill_price.quote.asset_id )\n               fill_price = ~fill_price;\n\n            auto ticker_itr = ticker_idx.find( std::make_tuple( key.base, key.quote ) );\n            if( ticker_itr != ticker_idx.end() ) // should always be true\n            {\n               db.modify( *ticker_itr, [&]( market_ticker_object& mt ) {\n                  mt.last_day_base  = fill_price.base.amount;\n                  mt.last_day_quote = fill_price.quote.amount;\n                  mt.base_volume    -= trade_price.base.amount.value;  // ignore underflow\n                  mt.quote_volume   -= trade_price.quote.amount.value; // ignore underflow\n               });\n            }\n         }\n         last_min_his_id = history_itr->id;\n         ++history_itr;\n      }\n      // update meta\n      if( history_itr != history_idx.end() ) // if still has some data rolling\n      {\n         if( history_itr->id != _meta->rolling_min_order_his_id ) // if rolled out some\n         {\n            db.modify( *_meta, [&]( market_ticker_meta_object& mtm ) {\n               mtm.rolling_min_order_his_id = history_itr->id;\n               mtm.skip_min_order_his_id = false;\n            });\n         }\n      }\n      else // if all data are rolled out\n      {\n         if( !_meta->skip_min_order_his_id\n             || last_min_his_id != _meta->rolling_min_order_his_id ) // if rolled out some\n         {\n            db.modify( *_meta, [&]( market_ticker_meta_object& mtm ) {\n               mtm.rolling_min_order_his_id = last_min_his_id;\n               mtm.skip_min_order_his_id = true;\n            });\n         }\n      }\n   }\n   // roll out expired data from LP ticker\n   if( _lp_meta != nullptr )\n   {\n      time_point_sec last_day = b.timestamp - 86400;\n      object_id_type last_min_his_id = _lp_meta->rolling_min_lp_his_id;\n      bool skip = _lp_meta->skip_min_lp_his_id;\n\n      const auto& history_idx = db.get_index_type<liquidity_pool_history_index>().indices().get<by_id>();\n      auto history_itr = history_idx.lower_bound( _lp_meta->rolling_min_lp_his_id );\n      while( history_itr != history_idx.end() && history_itr->time < last_day )\n      {\n         if( skip && history_itr->id == _lp_meta->rolling_min_lp_his_id )\n            skip = false;\n         else\n         {\n            liquidity_pool_ticker_id_type ticker_id( history_itr->pool.instance );\n            const liquidity_pool_ticker_object* ticker = db.find( ticker_id );\n            if( ticker != nullptr ) // should always be true\n            {\n               const operation_history_object& oho = history_itr->op;\n               if( oho.op.is_type< liquidity_pool_deposit_operation >() )\n               {\n                  auto& result = oho.result.get< generic_exchange_operation_result >();\n                  db.modify( *ticker, [&result]( liquidity_pool_ticker_object& t ) {\n                     t._24h_deposit_count -= 1;\n                     t._24h_deposit_amount_a -= result.paid.front().amount.value;\n                     t._24h_deposit_amount_b -= result.paid.back().amount.value;\n                     t._24h_deposit_share_amount -= result.received.front().amount.value;\n                     t._24h_balance_delta_a -= result.paid.front().amount.value;\n                     t._24h_balance_delta_b -= result.paid.back().amount.value;\n                  });\n               }\n               else if( oho.op.is_type< liquidity_pool_withdraw_operation >() )\n               {\n                  auto& op = oho.op.get< liquidity_pool_withdraw_operation >();\n                  auto& result = oho.result.get< generic_exchange_operation_result >();\n                  db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) {\n                     t._24h_withdrawal_count -= 1;\n                     t._24h_withdrawal_amount_a -= result.received.front().amount.value;\n                     t._24h_withdrawal_amount_b -= result.received.back().amount.value;\n                     t._24h_withdrawal_share_amount -= op.share_amount.amount.value;\n                     t._24h_withdrawal_fee_a -= result.fees.front().amount.value;\n                     t._24h_withdrawal_fee_b -= result.fees.back().amount.value;\n                     t._24h_balance_delta_a += result.received.front().amount.value;\n                     t._24h_balance_delta_b += result.received.back().amount.value;\n                  });\n               }\n               else if( oho.op.is_type< liquidity_pool_exchange_operation >() )\n               {\n                  auto& op = oho.op.get< liquidity_pool_exchange_operation >();\n                  auto& result = oho.result.get< generic_exchange_operation_result >();\n                  db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) {\n                     auto amount_in = op.amount_to_sell.amount - result.fees.front().amount;\n                     auto amount_out = result.received.front().amount + result.fees.at(1).amount;\n                     if( op.amount_to_sell.asset_id < op.min_to_receive.asset_id ) // pool got a, paid b\n                     {\n                        t._24h_exchange_a2b_count -= 1;\n                        t._24h_exchange_a2b_amount_a -= amount_in.value;\n                        t._24h_exchange_a2b_amount_b -= amount_out.value;\n                        t._24h_exchange_fee_b -= result.fees.back().amount.value;\n                        t._24h_balance_delta_a -= amount_in.value;\n                        t._24h_balance_delta_b += amount_out.value;\n                     }\n                     else // pool got b, paid a\n                     {\n                        t._24h_exchange_b2a_count -= 1;\n                        t._24h_exchange_b2a_amount_a -= amount_out.value;\n                        t._24h_exchange_b2a_amount_b -= amount_in.value;\n                        t._24h_exchange_fee_a -= result.fees.back().amount.value;\n                        t._24h_balance_delta_a += amount_out.value;\n                        t._24h_balance_delta_b -= amount_in.value;\n                     }\n                  });\n               }\n            }\n         }\n         last_min_his_id = history_itr->id;\n         ++history_itr;\n      }\n      // update meta\n      if( history_itr != history_idx.end() ) // if still has some data rolling\n      {\n         if( history_itr->id != _lp_meta->rolling_min_lp_his_id ) // if rolled out some\n         {\n            db.modify( *_lp_meta, [history_itr]( lp_ticker_meta_object& mtm ) {\n               mtm.rolling_min_lp_his_id = history_itr->id;\n               mtm.skip_min_lp_his_id = false;\n            });\n         }\n      }\n      else // if all data are rolled out\n      {\n         if( !_lp_meta->skip_min_lp_his_id\n             || last_min_his_id != _lp_meta->rolling_min_lp_his_id ) // if rolled out some\n         {\n            db.modify( *_lp_meta, [last_min_his_id]( lp_ticker_meta_object& mtm ) {\n               mtm.rolling_min_lp_his_id = last_min_his_id;\n               mtm.skip_min_lp_his_id = true;\n            });\n         }\n      }\n   }\n}\n\nstruct get_liquidity_pool_id_visitor\n{\n   typedef optional<liquidity_pool_id_type> result_type;\n\n   /** do nothing for other operation types */\n   template<typename T>\n   result_type operator()( const T& )const\n   {\n      return {};\n   }\n\n   result_type operator()( const liquidity_pool_delete_operation& o )const\n   {\n      return o.pool;\n   }\n\n   result_type operator()( const liquidity_pool_update_operation& o )const\n   {\n      return o.pool;\n   }\n\n   result_type operator()( const liquidity_pool_deposit_operation& o )const\n   {\n      return o.pool;\n   }\n\n   result_type operator()( const liquidity_pool_withdraw_operation& o )const\n   {\n      return o.pool;\n   }\n\n   result_type operator()( const liquidity_pool_exchange_operation& o )const\n   {\n      return o.pool;\n   }\n\n};\n\nvoid market_history_plugin_impl::update_liquidity_pool_histories(\n      time_point_sec time, const operation_history_object& oho,\n      const lp_ticker_meta_object*& lp_meta )\n{ try {\n\n   optional<liquidity_pool_id_type> pool;\n   uint64_t sequence = 0;\n   if( oho.op.is_type< liquidity_pool_create_operation >() )\n   {\n      pool = *oho.result.get<generic_operation_result>().new_objects.begin();\n      sequence = 1;\n   }\n   else\n   {\n      pool = oho.op.visit( get_liquidity_pool_id_visitor() );\n   }\n\n   if( pool.valid() )\n   {\n      auto& db = database();\n      const auto& his_index = db.get_index_type<liquidity_pool_history_index>().indices();\n      const auto& his_seq_idx = his_index.get<by_pool_seq>();\n      const auto& his_time_idx = his_index.get<by_pool_time>();\n\n      if( sequence == 0 )\n      {\n         auto itr = his_seq_idx.lower_bound( *pool );\n         if( itr != his_seq_idx.end() && itr->pool == *pool )\n            sequence = itr->sequence + 1;\n         else\n            sequence = 2;\n      }\n\n      // To save new data\n      const auto& new_his_obj = db.create<liquidity_pool_history_object>( [&pool,sequence,time,&oho](\n                                      liquidity_pool_history_object& ho ) {\n         ho.pool = *pool;\n         ho.sequence = sequence;\n         ho.time = time;\n         ho.op_type = oho.op.which();\n         ho.op = oho;\n      });\n\n      // save a reference to the ticker meta object\n      if( lp_meta == nullptr )\n      {\n         const auto& lp_meta_idx = db.get_index_type<simple_index<lp_ticker_meta_object>>();\n         if( lp_meta_idx.size() == 0 )\n            lp_meta = &db.create<lp_ticker_meta_object>( [&new_his_obj]( lp_ticker_meta_object& lptm ) {\n               lptm.rolling_min_lp_his_id = new_his_obj.id;\n               lptm.skip_min_lp_his_id = false;\n            });\n         else\n            lp_meta = &( *lp_meta_idx.begin() );\n      }\n\n      // To remove old history data\n      if( sequence > _max_order_his_records_per_market )\n      {\n         const auto min_seq = sequence - _max_order_his_records_per_market;\n         auto itr = his_seq_idx.lower_bound( std::make_tuple( *pool, min_seq ) );\n         if( itr != his_seq_idx.end() && itr->pool == *pool )\n         {\n            fc::time_point_sec min_time;\n            if( min_time + _max_order_his_seconds_per_market < time )\n               min_time = time - _max_order_his_seconds_per_market;\n            auto time_itr = his_time_idx.lower_bound( std::make_tuple( *pool, min_time ) );\n            if( time_itr != his_time_idx.end() && time_itr->pool == *pool )\n            {\n               if( itr->sequence <= time_itr->sequence )\n               {\n                  while( itr != his_seq_idx.end() && itr->pool == *pool )\n                  {\n                     auto old_itr = itr;\n                     ++itr;\n                     db.remove( *old_itr );\n                  }\n               }\n               else\n               {\n                  while( time_itr != his_time_idx.end() && time_itr->pool == *pool )\n                  {\n                     auto old_itr = time_itr;\n                     ++time_itr;\n                     db.remove( *old_itr );\n                  }\n               }\n            }\n         }\n      }\n\n      // To update ticker data\n      if( sequence == 1 ) // create\n      {\n         const liquidity_pool_ticker_object* ticker = nullptr;\n         do {\n            ticker = &db.create<liquidity_pool_ticker_object>( []( liquidity_pool_ticker_object& lpt ) {\n            });\n         } while( ticker->id.instance() < pool->instance );\n      }\n      else\n      {\n         liquidity_pool_ticker_id_type ticker_id( pool->instance );\n         const liquidity_pool_ticker_object* ticker = db.find( ticker_id );\n         if( ticker != nullptr )\n         {\n            if( oho.op.is_type< liquidity_pool_deposit_operation >() )\n            {\n               auto& result = oho.result.get< generic_exchange_operation_result >();\n\n               db.modify( *ticker, [&result]( liquidity_pool_ticker_object& t ) {\n                  t._24h_deposit_count += 1;\n                  t._24h_deposit_amount_a += result.paid.front().amount.value;\n                  t._24h_deposit_amount_b += result.paid.back().amount.value;\n                  t._24h_deposit_share_amount += result.received.front().amount.value;\n                  t._24h_balance_delta_a += result.paid.front().amount.value;\n                  t._24h_balance_delta_b += result.paid.back().amount.value;\n                  t.total_deposit_count += 1;\n                  t.total_deposit_amount_a += result.paid.front().amount.value;\n                  t.total_deposit_amount_b += result.paid.back().amount.value;\n                  t.total_deposit_share_amount += result.received.front().amount.value;\n               });\n\n            }\n            else if( oho.op.is_type< liquidity_pool_withdraw_operation >() )\n            {\n               auto& op = oho.op.get< liquidity_pool_withdraw_operation >();\n               auto& result = oho.result.get< generic_exchange_operation_result >();\n\n               db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) {\n                  t._24h_withdrawal_count += 1;\n                  t._24h_withdrawal_amount_a += result.received.front().amount.value;\n                  t._24h_withdrawal_amount_b += result.received.back().amount.value;\n                  t._24h_withdrawal_share_amount += op.share_amount.amount.value;\n                  t._24h_withdrawal_fee_a += result.fees.front().amount.value;\n                  t._24h_withdrawal_fee_b += result.fees.back().amount.value;\n                  t._24h_balance_delta_a -= result.received.front().amount.value;\n                  t._24h_balance_delta_b -= result.received.back().amount.value;\n                  t.total_withdrawal_count += 1;\n                  t.total_withdrawal_amount_a += result.received.front().amount.value;\n                  t.total_withdrawal_amount_b += result.received.back().amount.value;\n                  t.total_withdrawal_share_amount += op.share_amount.amount.value;\n                  t.total_withdrawal_fee_a += result.fees.front().amount.value;\n                  t.total_withdrawal_fee_b += result.fees.back().amount.value;\n               });\n\n            }\n            else if( oho.op.is_type< liquidity_pool_exchange_operation >() )\n            {\n               auto& op = oho.op.get< liquidity_pool_exchange_operation >();\n               auto& result = oho.result.get< generic_exchange_operation_result >();\n\n               db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) {\n                  auto amount_in = op.amount_to_sell.amount - result.fees.front().amount;\n                  auto amount_out = result.received.front().amount + result.fees.at(1).amount;\n                  if( op.amount_to_sell.asset_id < op.min_to_receive.asset_id ) // pool got a, paid b\n                  {\n                     t._24h_exchange_a2b_count += 1;\n                     t._24h_exchange_a2b_amount_a += amount_in.value;\n                     t._24h_exchange_a2b_amount_b += amount_out.value;\n                     t._24h_exchange_fee_b += result.fees.back().amount.value;\n                     t._24h_balance_delta_a += amount_in.value;\n                     t._24h_balance_delta_b -= amount_out.value;\n                     t.total_exchange_a2b_count += 1;\n                     t.total_exchange_a2b_amount_a += amount_in.value;\n                     t.total_exchange_a2b_amount_b += amount_out.value;\n                     t.total_exchange_fee_b += result.fees.back().amount.value;\n                  }\n                  else // pool got b, paid a\n                  {\n                     t._24h_exchange_b2a_count += 1;\n                     t._24h_exchange_b2a_amount_a += amount_out.value;\n                     t._24h_exchange_b2a_amount_b += amount_in.value;\n                     t._24h_exchange_fee_a += result.fees.back().amount.value;\n                     t._24h_balance_delta_a -= amount_out.value;\n                     t._24h_balance_delta_b += amount_in.value;\n                     t.total_exchange_b2a_count += 1;\n                     t.total_exchange_b2a_amount_a += amount_out.value;\n                     t.total_exchange_b2a_amount_b += amount_in.value;\n                     t.total_exchange_fee_a += result.fees.back().amount.value;\n                  }\n               });\n\n            }\n         }\n      }\n\n\n   }\n\n} FC_CAPTURE_AND_LOG( (time)(oho) ) }\n\n\n} // end namespace detail\n\n\nmarket_history_plugin::market_history_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::market_history_plugin_impl>(*this) )\n{\n   // Nothing else to do\n}\n\nmarket_history_plugin::~market_history_plugin() = default;\n\nstd::string market_history_plugin::plugin_name()const\n{\n   return \"market_history\";\n}\n\nvoid market_history_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"bucket-size\",\n           // 1m, 5m, 15m, 1h, 4h, 1d, 1w\n           boost::program_options::value<string>()->default_value(\"[60,300,900,3600,14400,86400,604800]\"),\n           \"Track market history by grouping orders into buckets of equal size measured \"\n           \"in seconds specified as a JSON array of numbers\")\n         (\"history-per-size\", boost::program_options::value<uint32_t>()->default_value(1500),\n           \"How far back in time to track history for each bucket size, \"\n           \"measured in the number of buckets (default: 1500)\")\n         (\"max-order-his-records-per-market\", boost::program_options::value<uint32_t>()->default_value(1000),\n           \"Will only store this amount of matched orders for each market in order history for querying, \"\n           \"or those meet the other option, which has more data (default: 1000). \"\n           \"This parameter is reused for liquidity pools as maximum operations per pool in history.\")\n         (\"max-order-his-seconds-per-market\", boost::program_options::value<uint32_t>()->default_value(259200),\n           \"Will only store matched orders in last X seconds for each market in order history for querying, \"\n           \"or those meet the other option, which has more data (default: 259200 (3 days)). \"\n           \"This parameter is reused for liquidity pools as operations in last X seconds per pool in history. \"\n           \"Note: this parameter need to be greater than 24 hours to be able to serve market ticker data correctly.\")\n         ;\n   cfg.add(cli);\n}\n\nvoid market_history_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{ try {\n   // connect with group 0 to process before some special steps (e.g. snapshot or next_object_id)\n   database().applied_block.connect( 0, [this]( const signed_block& b){ my->update_market_histories(b); } );\n\n   database().add_index< primary_index< bucket_index  > >();\n   database().add_index< primary_index< history_index  > >();\n   database().add_index< primary_index< market_ticker_index, 8 > >(); // 256 markets per chunk\n   database().add_index< primary_index< simple_index< market_ticker_meta_object > > >();\n\n   database().add_index< primary_index< liquidity_pool_history_index > >();\n   database().add_index< primary_index< simple_index< lp_ticker_meta_object > > >();\n   database().add_index< primary_index< liquidity_pool_ticker_index, 8 > >(); // 256 pools per chunk\n\n   if( options.count( \"bucket-size\" ) > 0 )\n   {\n      const std::string& buckets = options[\"bucket-size\"].as<string>();\n      my->_tracked_buckets = fc::json::from_string(buckets).as<flat_set<uint32_t>>(2);\n      my->_tracked_buckets.erase( 0 );\n   }\n   if( options.count( \"history-per-size\" ) > 0 )\n      my->_maximum_history_per_bucket_size = options[\"history-per-size\"].as<uint32_t>();\n   if( options.count( \"max-order-his-records-per-market\" ) > 0 )\n      my->_max_order_his_records_per_market = options[\"max-order-his-records-per-market\"].as<uint32_t>();\n   if( options.count( \"max-order-his-seconds-per-market\" ) > 0 )\n      my->_max_order_his_seconds_per_market = options[\"max-order-his-seconds-per-market\"].as<uint32_t>();\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid market_history_plugin::plugin_startup()\n{\n}\n\nconst flat_set<uint32_t>& market_history_plugin::tracked_buckets() const\n{\n   return my->_tracked_buckets;\n}\n\nuint32_t market_history_plugin::max_history()const\n{\n   return my->_maximum_history_per_bucket_size;\n}\n\nuint32_t market_history_plugin::max_order_his_records_per_market()const\n{\n   return my->_max_order_his_records_per_market;\n}\n\nuint32_t market_history_plugin::max_order_his_seconds_per_market()const\n{\n   return my->_max_order_his_seconds_per_market;\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/snapshot/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/snapshot/*.hpp\")\n\nadd_library( graphene_snapshot\n             snapshot.cpp\n           )\n\ntarget_link_libraries( graphene_snapshot graphene_app graphene_chain )\ntarget_include_directories( graphene_snapshot\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\ninstall( TARGETS\n   graphene_snapshot\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "libraries/plugins/snapshot/include/graphene/snapshot/snapshot.hpp",
    "content": "/*\n * Copyright (c) 2017 Peter Conrad, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <fc/time.hpp>\n\nnamespace graphene { namespace snapshot_plugin {\n\nclass snapshot_plugin : public graphene::app::plugin {\n   public:\n      using graphene::app::plugin::plugin;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n\n      void plugin_set_program_options(\n         boost::program_options::options_description &command_line_options,\n         boost::program_options::options_description &config_file_options\n      ) override;\n\n      void plugin_initialize( const boost::program_options::variables_map& options ) override;\n\n   private:\n       void check_snapshot( const graphene::chain::signed_block& b);\n\n       uint32_t           snapshot_block = -1, last_block = 0;\n       fc::time_point_sec snapshot_time = fc::time_point_sec::maximum(), last_time = fc::time_point_sec(1);\n       fc::path           dest;\n};\n\n} } //graphene::snapshot_plugin\n"
  },
  {
    "path": "libraries/plugins/snapshot/snapshot.cpp",
    "content": "/*\n * Copyright (c) 2017 Peter Conrad, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/snapshot/snapshot.hpp>\n\n#include <graphene/chain/database.hpp>\n\n#include <fc/io/fstream.hpp>\n\nusing namespace graphene::snapshot_plugin;\nusing std::string;\nusing std::vector;\n\nnamespace bpo = boost::program_options;\n\nstatic const char* OPT_BLOCK_NUM  = \"snapshot-at-block\";\nstatic const char* OPT_BLOCK_TIME = \"snapshot-at-time\";\nstatic const char* OPT_DEST       = \"snapshot-to\";\n\nvoid snapshot_plugin::plugin_set_program_options(\n   boost::program_options::options_description& command_line_options,\n   boost::program_options::options_description& config_file_options)\n{\n   command_line_options.add_options()\n         (OPT_BLOCK_NUM, bpo::value<uint32_t>(), \"Block number after which to do a snapshot\")\n         (OPT_BLOCK_TIME, bpo::value<string>(), \"Block time (ISO format) after which to do a snapshot\")\n         (OPT_DEST, bpo::value<string>(), \"Pathname of JSON file where to store the snapshot\")\n         ;\n   config_file_options.add(command_line_options);\n}\n\nstd::string snapshot_plugin::plugin_name()const\n{\n   return \"snapshot\";\n}\n\nstd::string snapshot_plugin::plugin_description()const\n{\n   return \"Create snapshots at a specified time or block number.\";\n}\n\nvoid snapshot_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{ try {\n   ilog(\"snapshot plugin: plugin_initialize() begin\");\n\n   if( options.count(OPT_BLOCK_NUM) > 0 || options.count(OPT_BLOCK_TIME) > 0 )\n   {\n      FC_ASSERT( options.count(OPT_DEST) > 0,\n                 \"Must specify snapshot-to in addition to snapshot-at-block or snapshot-at-time!\" );\n      dest = options[OPT_DEST].as<std::string>();\n      if( options.count(OPT_BLOCK_NUM) > 0 )\n         snapshot_block = options[OPT_BLOCK_NUM].as<uint32_t>();\n      if( options.count(OPT_BLOCK_TIME) > 0 )\n         snapshot_time = fc::time_point_sec::from_iso_string( options[OPT_BLOCK_TIME].as<std::string>() );\n      // connect with no group specified to process after the ones with a group specified\n      database().applied_block.connect( [&]( const graphene::chain::signed_block& b ) {\n         check_snapshot( b );\n      });\n   }\n   else\n      ilog(\"snapshot plugin is not enabled because neither snapshot-at-block nor snapshot-at-time is specified\");\n\n   ilog(\"snapshot plugin: plugin_initialize() end\");\n} FC_LOG_AND_RETHROW() }\n\nstatic void create_snapshot( const graphene::chain::database& db, const fc::path& dest )\n{\n   ilog(\"snapshot plugin: creating snapshot\");\n   fc::ofstream out;\n   try\n   {\n      out.open( dest );\n   }\n   catch ( fc::exception& e )\n   {\n      wlog( \"Failed to open snapshot destination: ${ex}\", (\"ex\",e) );\n      return;\n   }\n   for( uint32_t space_id = 0; space_id < 256; space_id++ )\n      for( uint32_t type_id = 0; type_id < 256; type_id++ )\n      {\n         try\n         {\n            db.get_index( (uint8_t)space_id, (uint8_t)type_id );\n         }\n         catch (fc::assert_exception& e)\n         {\n            continue;\n         }\n         auto& index = db.get_index( (uint8_t)space_id, (uint8_t)type_id );\n         index.inspect_all_objects( [&out]( const graphene::db::object& o ) {\n            out << fc::json::to_string( o.to_variant() ) << '\\n';\n         });\n      }\n   out.close();\n   ilog(\"snapshot plugin: created snapshot\");\n}\n\nvoid snapshot_plugin::check_snapshot( const graphene::chain::signed_block& b )\n{ try {\n    uint32_t current_block = b.block_num();\n    if( (last_block < snapshot_block && snapshot_block <= current_block)\n           || (last_time < snapshot_time && snapshot_time <= b.timestamp) )\n       create_snapshot( database(), dest );\n    last_block = current_block;\n    last_time = b.timestamp;\n} FC_LOG_AND_RETHROW() }\n"
  },
  {
    "path": "libraries/plugins/template_plugin/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/template_plugin/*.hpp\")\n\nadd_library( graphene_template_plugin\n        template_plugin.cpp\n           )\n\ntarget_link_libraries( graphene_template_plugin graphene_app graphene_chain )\ntarget_include_directories( graphene_template_plugin\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif(MSVC)\n  set_source_files_properties(template_plugin.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_template_plugin\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\nINSTALL( FILES ${HEADERS} DESTINATION \"include/graphene/template_plugin\" )\n"
  },
  {
    "path": "libraries/plugins/template_plugin/include/graphene/template_plugin/template_plugin.hpp",
    "content": "/*\n * Copyright (c) 2018 template_plugin and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n\nnamespace graphene { namespace template_plugin {\nusing namespace chain;\n\n//\n// Plugins should #define their SPACE_ID's so plugins with\n// conflicting SPACE_ID assignments can be compiled into the\n// same binary (by simply re-assigning some of the conflicting #defined\n// SPACE_ID's in a build script).\n//\n// Assignment of SPACE_ID's cannot be done at run-time because\n// various template automagic depends on them being known at compile\n// time.\n//\n#ifndef template_plugin_SPACE_ID\n#define template_plugin_SPACE_ID @SPACE_ID@\n#endif\n\n\nnamespace detail\n{\n    class template_plugin_impl;\n}\n\nclass template_plugin : public graphene::app::plugin\n{\n   public:\n      explicit template_plugin(graphene::app::application& app);\n      ~template_plugin() override;\n\n      std::string plugin_name()const override;\n      std::string plugin_description()const override;\n      void plugin_set_program_options(\n         boost::program_options::options_description& cli,\n         boost::program_options::options_description& cfg) override;\n      void plugin_initialize(const boost::program_options::variables_map& options) override;\n      void plugin_startup() override;\n      void plugin_shutdown() override;\n\n   private:\n      void cleanup();\n      std::unique_ptr<detail::template_plugin_impl> my;\n};\n\n} } //graphene::template\n"
  },
  {
    "path": "libraries/plugins/template_plugin/template_plugin.cpp",
    "content": "/*\n * Copyright (c) 2018 template_plugin and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/template_plugin/template_plugin.hpp>\n\nnamespace graphene { namespace template_plugin {\n\nnamespace detail\n{\n\nclass template_plugin_impl\n{\n   public:\n      explicit template_plugin_impl( template_plugin& _plugin );\n      virtual ~template_plugin_impl();\n\n      void on_block( const signed_block& b );\n\n      graphene::chain::database& database()\n      {\n         return _self.database();\n      }\n\n      friend class graphene::template_plugin::template_plugin;\n\n   private:\n      template_plugin& _self;\n\n      std::string _plugin_option = \"\";\n\n};\n\nvoid template_plugin_impl::on_block( const signed_block& b )\n{\n   wdump((b.block_num()));\n}\n\ntemplate_plugin_impl::template_plugin_impl( template_plugin& _plugin ) :\n   _self( _plugin )\n{\n   // Put other code here\n}\n\ntemplate_plugin_impl::~template_plugin_impl()\n{\n   // Put the real code here. If none, remove the destructor.\n}\n\n} // end namespace detail\n\ntemplate_plugin::template_plugin(graphene::app::application& app) :\n   plugin(app),\n   my( std::make_unique<detail::template_plugin_impl>(*this) )\n{\n   // Add needed code here\n}\n\ntemplate_plugin::~template_plugin()\n{\n   cleanup();\n}\n\nstd::string template_plugin::plugin_name()const\n{\n   return \"template_plugin\";\n}\nstd::string template_plugin::plugin_description()const\n{\n   return \"template_plugin description\";\n}\n\nvoid template_plugin::plugin_set_program_options(\n   boost::program_options::options_description& cli,\n   boost::program_options::options_description& cfg\n   )\n{\n   cli.add_options()\n         (\"template_plugin_option\", boost::program_options::value<std::string>(), \"template_plugin option\")\n         ;\n   cfg.add(cli);\n}\n\nvoid template_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{\n   // connect with group 0 by default to process before some special steps (e.g. snapshot or next_object_id)\n   database().applied_block.connect( 0, [this]( const signed_block& b) {\n      my->on_block(b);\n   } );\n\n   if (options.count(\"template_plugin\") > 0) {\n      my->_plugin_option = options[\"template_plugin\"].as<std::string>();\n   }\n}\n\nvoid template_plugin::plugin_startup()\n{\n   ilog(\"template_plugin: plugin_startup() begin\");\n}\n\nvoid template_plugin::plugin_shutdown()\n{\n   ilog(\"template_plugin: plugin_shutdown() begin\");\n   cleanup();\n}\n\nvoid template_plugin::cleanup()\n{\n   // Add cleanup code here\n}\n\n} }\n"
  },
  {
    "path": "libraries/plugins/witness/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/witness/*.hpp\")\n\nadd_library( graphene_witness \n             witness.cpp\n           )\n\ntarget_link_libraries( graphene_witness graphene_app graphene_chain )\ntarget_include_directories( graphene_witness\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif (MSVC)\n    set_target_properties( graphene_witness PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_witness\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "libraries/plugins/witness/include/graphene/witness/witness.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/plugin.hpp>\n#include <graphene/chain/database.hpp>\n\n#include <fc/thread/future.hpp>\n\nnamespace graphene { namespace witness_plugin {\n\nnamespace block_production_condition\n{\n   enum block_production_condition_enum\n   {\n      produced = 0,\n      not_synced = 1,\n      not_my_turn = 2,\n      not_time_yet = 3,\n      no_private_key = 4,\n      low_participation = 5,\n      lag = 6,\n      exception_producing_block = 7,\n      shutdown = 8,\n      no_network = 9\n   };\n}\n\nclass witness_plugin : public graphene::app::plugin {\npublic:\n   using graphene::app::plugin::plugin;\n   ~witness_plugin() override { cleanup(); }\n\n   std::string plugin_name()const override;\n\n   void plugin_set_program_options(\n      boost::program_options::options_description &command_line_options,\n      boost::program_options::options_description &config_file_options\n      ) override;\n\n   void set_block_production(bool allow) { _production_enabled = allow; }\n   void stop_block_production();\n\n   void plugin_initialize( const boost::program_options::variables_map& options ) override;\n   void plugin_startup() override;\n   void plugin_shutdown() override { cleanup(); }\n\n   inline const fc::flat_map< chain::witness_id_type, fc::optional<chain::public_key_type> >& get_witness_key_cache()\n   { return _witness_key_cache; }\n\nprivate:\n   void cleanup() { stop_block_production(); }\n\n   void schedule_production_loop();\n   block_production_condition::block_production_condition_enum block_production_loop();\n   block_production_condition::block_production_condition_enum maybe_produce_block( fc::limited_mutable_variant_object& capture );\n   void add_private_key(const std::string& key_id_to_wif_pair_string);\n\n   /// Fetch signing keys of all witnesses in the cache from object database and update the cache accordingly\n   void refresh_witness_key_cache();\n\n   boost::program_options::variables_map _options;\n   bool _production_enabled = false;\n   bool _shutting_down = false;\n   uint32_t _required_witness_participation = 33 * GRAPHENE_1_PERCENT;\n   uint32_t _production_skip_flags = graphene::chain::database::skip_nothing;\n\n   std::map<chain::public_key_type, fc::ecc::private_key, chain::pubkey_comparator> _private_keys;\n   std::set<chain::witness_id_type> _witnesses;\n   fc::future<void> _block_production_task;\n\n   /// For tracking signing keys of specified witnesses, only update when applied a block\n   fc::flat_map< chain::witness_id_type, fc::optional<chain::public_key_type> > _witness_key_cache;\n\n};\n\n} } //graphene::witness_plugin\n"
  },
  {
    "path": "libraries/plugins/witness/witness.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/witness/witness.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <graphene/utilities/key_conversion.hpp>\n\n#include <fc/thread/thread.hpp>\n#include <fc/io/fstream.hpp>\n\n#include <boost/filesystem/path.hpp>\n\n#include <iostream>\n\nusing namespace graphene::witness_plugin;\nusing std::string;\nusing std::vector;\n\nnamespace bpo = boost::program_options;\n\nvoid new_chain_banner( const graphene::chain::database& db )\n{\n   ilog(\"\\n\"\n      \"********************************\\n\"\n      \"*                              *\\n\"\n      \"*   ------- NEW CHAIN ------   *\\n\"\n      \"*   - Welcome to BitShares! -  *\\n\"\n      \"*   ------------------------   *\\n\"\n      \"*                              *\\n\"\n      \"********************************\\n\"\n      \"\\n\");\n   if( db.get_slot_at_time( fc::time_point::now() ) > 200 )\n   {\n      wlog(\"Your genesis seems to have an old timestamp\");\n      wlog(\"Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\");\n   }\n}\n\nvoid witness_plugin::plugin_set_program_options(\n   boost::program_options::options_description& command_line_options,\n   boost::program_options::options_description& config_file_options)\n{\n   auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string(\"nathan\")));\n   string witness_id_example = fc::json::to_string(chain::witness_id_type(5));\n   command_line_options.add_options()\n         (\"enable-stale-production\", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}),\n               \"Enable block production, even if the chain is stale.\")\n         (\"required-participation\", bpo::value<uint32_t>()->default_value(33),\n               \"Percent of witnesses (0-100) that must be participating in order to produce blocks\")\n         (\"witness-id,w\", bpo::value<vector<string>>()->composing()->multitoken(),\n               (\"ID of witness controlled by this node (e.g. \" + witness_id_example +\n               \", quotes are required, may specify multiple times)\").c_str())\n         (\"private-key\", bpo::value<vector<string>>()->composing()->multitoken()->\n          DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()),\n                graphene::utilities::key_to_wif(default_priv_key))),\n                \"Tuple of [PublicKey, WIF private key] (may specify multiple times)\")\n         (\"private-key-file\", bpo::value<vector<boost::filesystem::path>>()->composing()->multitoken(),\n          \"Path to a file containing tuples of [PublicKey, WIF private key].\"\n          \" The file has to contain exactly one tuple (i.e. private - public key pair) per line.\"\n          \" This option may be specified multiple times, thus multiple files can be provided.\")\n         ;\n   config_file_options.add(command_line_options);\n}\n\nstd::string witness_plugin::plugin_name()const\n{\n   return \"witness\";\n}\n\nvoid witness_plugin::add_private_key(const std::string& key_id_to_wif_pair_string)\n{\n   auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::public_key_type, std::string>>\n         (key_id_to_wif_pair_string, 5);\n   fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);\n   if (!private_key)\n   {\n      // the key isn't in WIF format; see if they are still passing the old native private key format.  This is\n      // just here to ease the transition, can be removed soon\n      try\n      {\n         private_key = fc::variant(key_id_to_wif_pair.second, 2).as<fc::ecc::private_key>(1);\n      }\n      catch (const fc::exception&)\n      {\n         FC_THROW(\"Invalid WIF-format private key ${key_string}\", (\"key_string\", key_id_to_wif_pair.second));\n      }\n   }\n\n   if (_private_keys.find(key_id_to_wif_pair.first) == _private_keys.end())\n   {\n      ilog(\"Public Key: ${public}\", (\"public\", key_id_to_wif_pair.first));\n      _private_keys[key_id_to_wif_pair.first] = *private_key;\n   }\n}\n\nvoid witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)\n{ try {\n   ilog(\"witness plugin:  plugin_initialize() begin\");\n   _options = &options;\n   LOAD_VALUE_SET(options, \"witness-id\", _witnesses, chain::witness_id_type);\n\n   if( options.count(\"private-key\") > 0 )\n   {\n      const std::vector<std::string> key_id_to_wif_pair_strings = options[\"private-key\"].as<std::vector<std::string>>();\n      for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)\n      {\n         add_private_key(key_id_to_wif_pair_string);\n      }\n   }\n   if (options.count(\"private-key-file\") > 0)\n   {\n      const std::vector<boost::filesystem::path> key_id_to_wif_pair_files =\n            options[\"private-key-file\"].as<std::vector<boost::filesystem::path>>();\n      for (const boost::filesystem::path& key_id_to_wif_pair_file : key_id_to_wif_pair_files)\n      {\n         if (fc::exists(key_id_to_wif_pair_file))\n         {\n            std::string file_content;\n            fc::read_file_contents(key_id_to_wif_pair_file, file_content);\n            std::istringstream file_content_as_stream(file_content);\n\n            std::string line; // key_id_to_wif_pair_string\n            while (std::getline(file_content_as_stream, line))\n            {\n               add_private_key(line);\n            }\n         }\n         else\n         {\n            FC_THROW(\"Failed to load private key file from ${path}\", (\"path\", key_id_to_wif_pair_file.string()));\n         }\n      }\n   }\n   if(options.count(\"required-participation\") > 0)\n   {\n       auto required_participation = options[\"required-participation\"].as<uint32_t>();\n       FC_ASSERT(required_participation <= 100);\n       _required_witness_participation = options[\"required-participation\"].as<uint32_t>()*GRAPHENE_1_PERCENT;\n       if(required_participation < 10)\n           wlog(\"witness plugin: Warning - Low required participation of ${rp}% found\", (\"rp\", required_participation));\n       else if(required_participation > 90)\n           wlog(\"witness plugin: Warning - High required participation of ${rp}% found\", (\"rp\", required_participation));\n   }\n   ilog(\"witness plugin:  plugin_initialize() end\");\n} FC_LOG_AND_RETHROW() }\n\nvoid witness_plugin::plugin_startup()\n{ try {\n   ilog(\"witness plugin:  plugin_startup() begin\");\n   chain::database& d = database();\n   if( !_witnesses.empty() )\n   {\n      ilog(\"Launching block production for ${n} witnesses.\", (\"n\", _witnesses.size()));\n      app().set_block_production(true);\n      if( _production_enabled )\n      {\n         if( d.head_block_num() == 0 )\n            new_chain_banner(d);\n         _production_skip_flags |= graphene::chain::database::skip_undo_history_check;\n      }\n      refresh_witness_key_cache();\n      d.applied_block.connect( [this]( const chain::signed_block& b )\n      {\n         refresh_witness_key_cache();\n      });\n      schedule_production_loop();\n   }\n   else\n   {\n      ilog(\"No witness configured.\");\n   }\n   ilog(\"witness plugin:  plugin_startup() end\");\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid witness_plugin::stop_block_production()\n{\n   _shutting_down = true;\n   \n   try {\n      if( _block_production_task.valid() )\n         _block_production_task.cancel_and_wait(__FUNCTION__);\n   } catch(fc::canceled_exception&) {\n      //Expected exception. Move along.\n   } catch(fc::exception& e) {\n      edump((e.to_detail_string()));\n   }\n}\n\nvoid witness_plugin::refresh_witness_key_cache()\n{\n   const auto& db = database();\n   for( const chain::witness_id_type& wit_id : _witnesses )\n   {\n      const chain::witness_object* wit_obj = db.find( wit_id );\n      if( wit_obj )\n         _witness_key_cache[wit_id] = wit_obj->signing_key;\n      else\n         _witness_key_cache[wit_id] = fc::optional<chain::public_key_type>();\n   }\n}\n\nvoid witness_plugin::schedule_production_loop()\n{\n   if (_shutting_down) return;\n\n   //Schedule for the next second's tick regardless of chain state\n   // If we would wait less than 50ms, wait for the whole second.\n   fc::time_point now = fc::time_point::now();\n   int64_t time_to_next_second = 1000000 - (now.time_since_epoch().count() % 1000000);\n   if( time_to_next_second < 50000 )      // we must sleep for at least 50ms\n       time_to_next_second += 1000000;\n\n   fc::time_point next_wakeup( now + fc::microseconds( time_to_next_second ) );\n\n   _block_production_task = fc::schedule([this]{block_production_loop();},\n                                         next_wakeup, \"Witness Block Production\");\n}\n\nblock_production_condition::block_production_condition_enum witness_plugin::block_production_loop()\n{\n   block_production_condition::block_production_condition_enum result;\n   fc::limited_mutable_variant_object capture( GRAPHENE_MAX_NESTED_OBJECTS );\n\n   if (_shutting_down) \n   {\n      result = block_production_condition::shutdown;\n   }\n   else\n   {\n      try\n      {\n         result = maybe_produce_block(capture);\n      }\n      catch( const fc::canceled_exception& )\n      {\n         //We're trying to exit. Go ahead and let this one out.\n         throw;\n      }\n      catch( const fc::exception& e )\n      {\n         elog(\"Got exception while generating block:\\n${e}\", (\"e\", e.to_detail_string()));\n         result = block_production_condition::exception_producing_block;\n      }\n   }\n\n   switch( result )\n   {\n      case block_production_condition::produced:\n         ilog(\"Generated block #${n} with ${x} transaction(s) and timestamp ${t} at time ${c}\", (capture));\n         break;\n      case block_production_condition::not_synced:\n         ilog(\"Not producing block because production is disabled until we receive a recent block \"\n              \"(see: --enable-stale-production)\");\n         break;\n      case block_production_condition::not_my_turn:\n         break;\n      case block_production_condition::not_time_yet:\n         break;\n      case block_production_condition::no_private_key:\n         ilog(\"Not producing block because I don't have the private key for ${scheduled_key}\", (capture) );\n         break;\n      case block_production_condition::low_participation:\n         elog(\"Not producing block because node appears to be on a minority fork with only ${pct}% witness participation\",\n               (capture) );\n         break;\n      case block_production_condition::lag:\n         elog(\"Not producing block because node didn't wake up within 2500ms of the slot time.\");\n         break;\n      case block_production_condition::exception_producing_block:\n         elog( \"exception producing block\" );\n         break;\n      case block_production_condition::shutdown:\n         ilog( \"shutdown producing block\" );\n         return result;\n      case block_production_condition::no_network:\n         ilog( \"not connected to P2P network\" );\n         return result;\n      default:\n         elog( \"unknown condition ${result} while producing block\", (\"result\", (unsigned char)result) );\n         break;\n   }\n\n   schedule_production_loop();\n   return result;\n}\n\nblock_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block(\n      fc::limited_mutable_variant_object& capture )\n{\n   chain::database& db = database();\n   fc::time_point now_fine = fc::time_point::now();\n   fc::time_point_sec now = now_fine + fc::microseconds( 500000 );\n\n   // If the next block production opportunity is in the present or future, we're synced.\n   if( !_production_enabled )\n   {\n      if( db.get_slot_time(1) >= now )\n         _production_enabled = true;\n      else\n         return block_production_condition::not_synced;\n   }\n\n   // is anyone scheduled to produce now or one second in the future?\n   uint32_t slot = db.get_slot_at_time( now );\n   if( slot == 0 )\n   {\n      capture(\"next_time\", db.get_slot_time(1));\n      return block_production_condition::not_time_yet;\n   }\n\n   //\n   // this assert should not fail, because now <= db.head_block_time()\n   // should have resulted in slot == 0.\n   //\n   // if this assert triggers, there is a serious bug in get_slot_at_time()\n   // which would result in allowing a later block to have a timestamp\n   // less than or equal to the previous block\n   //\n   assert( now > db.head_block_time() );\n\n   graphene::chain::witness_id_type scheduled_witness = db.get_scheduled_witness( slot );\n   // we must control the witness scheduled to produce the next block.\n   if( _witnesses.find( scheduled_witness ) == _witnesses.end() )\n   {\n      capture(\"scheduled_witness\", scheduled_witness);\n      return block_production_condition::not_my_turn;\n   }\n\n   fc::time_point_sec scheduled_time = db.get_slot_time( slot );\n   graphene::chain::public_key_type scheduled_key = *_witness_key_cache[scheduled_witness]; // should be valid\n   auto private_key_itr = _private_keys.find( scheduled_key );\n\n   if( private_key_itr == _private_keys.end() )\n   {\n      capture(\"scheduled_key\", scheduled_key);\n      return block_production_condition::no_private_key;\n   }\n\n   uint32_t prate = db.witness_participation_rate();\n   if( prate < _required_witness_participation )\n   {\n      capture(\"pct\", uint32_t(100*uint64_t(prate) / GRAPHENE_1_PERCENT));\n      return block_production_condition::low_participation;\n   }\n\n   if( llabs((scheduled_time - now).count()) > fc::milliseconds( 2500 ).count() )\n   {\n      capture(\"scheduled_time\", scheduled_time)(\"now\", now);\n      return block_production_condition::lag;\n   }\n\n   if( p2p_node() == nullptr )\n      return block_production_condition::no_network;\n\n   auto block = db.generate_block(\n      scheduled_time,\n      scheduled_witness,\n      private_key_itr->second,\n      _production_skip_flags\n      );\n   capture(\"n\", block.block_num())(\"t\", block.timestamp)(\"c\", now)(\"x\", block.transactions.size());\n   fc::async( [this,block](){ p2p_node()->broadcast(net::block_message(block)); } );\n\n   return block_production_condition::produced;\n}\n"
  },
  {
    "path": "libraries/protocol/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/protocol/*.hpp\")\n\nlist(APPEND SOURCES account.cpp\n                    assert.cpp\n                    asset_ops.cpp\n                    block.cpp\n                    confidential.cpp\n                    chain_parameters.cpp\n                    fee_schedule.cpp\n                    fee_schedule_calc.cpp\n                    fee_schedule_serialization_pack.cpp\n                    fee_schedule_serialization_variant.cpp\n                    fee_schedule_set_fee.cpp\n                    memo.cpp\n                    proposal.cpp\n                    transfer.cpp\n                    vote.cpp\n                    witness.cpp\n                    address.cpp\n                    asset.cpp\n                    authority.cpp\n                    special_authority.cpp\n                    restriction.cpp\n                    custom_authority.cpp\n                    committee_member.cpp\n                    custom.cpp\n                    market.cpp\n                    credit_offer.cpp\n                    liquidity_pool.cpp\n                    samet_fund.cpp\n                    ticket.cpp\n                    operations.cpp\n                    pts_address.cpp\n                    small_ops.cpp\n                    transaction.cpp\n                    types.cpp\n                    withdraw_permission.cpp\n                    worker.cpp\n                    htlc.cpp)\n\n\nlist(APPEND CUSTOM_AUTHS_FILES\n     custom_authorities/create_predicate_fwd_1.cpp\n     custom_authorities/create_predicate_fwd_2.cpp\n     custom_authorities/create_predicate_fwd_3.cpp\n     custom_authorities/restriction_predicate.cpp\n     custom_authorities/list_1.cpp\n     custom_authorities/list_2.cpp\n     custom_authorities/list_3.cpp\n     custom_authorities/list_5.cpp\n     custom_authorities/list_6.cpp\n     custom_authorities/list_9.cpp\n     custom_authorities/list_10.cpp\n     custom_authorities/list_11.cpp)\n\nfile(GLOB CUSTOM_AUTHS_HEADERS \"custom_authorities/*.hxx\")\n\nadd_library( graphene_protocol_custom_auths ${CUSTOM_AUTHS_FILES} ${CUSTOM_AUTHS_HEADERS} )\ntarget_link_libraries( graphene_protocol_custom_auths fc )\ntarget_include_directories( graphene_protocol_custom_auths PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nif( MSVC )\n   set_source_files_properties( ${CUSTOM_AUTHS_FILES} ${SOURCES} PROPERTIES COMPILE_FLAGS /bigobj )\nelse( MSVC )\n   if( MINGW )\n      set_source_files_properties( ${CUSTOM_AUTHS_FILES} ${SOURCES} PROPERTIES COMPILE_FLAGS -Wa,-mbig-obj )\n   endif( MINGW )\nendif( MSVC )\n\nadd_library( graphene_protocol ${SOURCES} ${HEADERS} )\ntarget_link_libraries( graphene_protocol fc graphene_protocol_custom_auths )\ntarget_include_directories( graphene_protocol PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\ninstall( TARGETS\n   graphene_protocol\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\ninstall( FILES ${HEADERS} DESTINATION \"include/graphene/protocol\" )\n"
  },
  {
    "path": "libraries/protocol/account.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/account.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\n/**\n * Names must comply with the following grammar (RFC 1035):\n * @code\n * <domain> ::= <subdomain> | \" \"\n * <subdomain> ::= <label> | <subdomain> \".\" <label>\n * <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]\n * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>\n * <let-dig-hyp> ::= <let-dig> | \"-\"\n * <let-dig> ::= <letter> | <digit>\n * @endcode\n *\n * Which is equivalent to the following:\n *\n * @code\n * <domain> ::= <subdomain> | \" \"\n * <subdomain> ::= <label> (\".\" <label>)*\n * <label> ::= <letter> [ [ <let-dig-hyp>+ ] <let-dig> ]\n * <let-dig-hyp> ::= <let-dig> | \"-\"\n * <let-dig> ::= <letter> | <digit>\n * @endcode\n *\n * I.e. a valid name consists of a dot-separated sequence\n * of one or more labels consisting of the following rules:\n *\n * - Each label is three characters or more\n * - Each label begins with a letter\n * - Each label ends with a letter or digit\n * - Each label contains only letters, digits or hyphens\n *\n * In addition we require the following:\n *\n * - All letters are lowercase\n * - Length is between (inclusive) GRAPHENE_MIN_ACCOUNT_NAME_LENGTH and GRAPHENE_MAX_ACCOUNT_NAME_LENGTH\n */\nbool is_valid_name( const string& name )\n{ try {\n   const size_t len = name.size();\n\n   if( len < GRAPHENE_MIN_ACCOUNT_NAME_LENGTH )\n   {\n      return false;\n   }\n\n   if( len > GRAPHENE_MAX_ACCOUNT_NAME_LENGTH )\n   {\n      return false;\n   }\n\n   size_t begin = 0;\n   while( true )\n   {\n      size_t end = name.find_first_of( '.', begin );\n      if( end == std::string::npos )\n         end = len;\n      if( (end - begin) < GRAPHENE_MIN_ACCOUNT_NAME_LENGTH )\n      {\n         return false;\n      }\n      switch( name[begin] )\n      {\n         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':\n         case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p':\n         case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x':\n         case 'y': case 'z':\n            break;\n         default:\n            return false;\n      }\n      switch( name[end-1] )\n      {\n         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':\n         case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p':\n         case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x':\n         case 'y': case 'z':\n         case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':\n         case '8': case '9':\n            break;\n         default:\n            return false;\n      }\n      for( size_t i=begin+1; i<end-1; i++ )\n      {\n         switch( name[i] )\n         {\n            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':\n            case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p':\n            case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x':\n            case 'y': case 'z':\n            case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7':\n            case '8': case '9':\n            case '-':\n               break;\n            default:\n               return false;\n         }\n      }\n      if( end == len )\n         break;\n      begin = end+1;\n   }\n   return true;\n} FC_CAPTURE_AND_RETHROW( (name) ) }\n\nbool is_cheap_name( const string& n )\n{\n   bool v = false;\n   for( auto c : n )\n   {\n      if( c >= '0' && c <= '9' ) return true;\n      if( c == '.' || c == '-' || c == '/' ) return true;\n      switch( c )\n      {\n         case 'a':\n         case 'e':\n         case 'i':\n         case 'o':\n         case 'u':\n         case 'y':\n            v = true;\n      }\n   }\n   if( !v )\n      return true;\n   return false;\n}\n\nvoid account_options::validate() const\n{\n   auto needed_witnesses = num_witness;\n   auto needed_committee = num_committee;\n\n   for( vote_id_type id : votes )\n   {\n      if( id.type() == vote_id_type::witness && needed_witnesses > 0 )\n         --needed_witnesses;\n      else if ( id.type() == vote_id_type::committee && needed_committee > 0 )\n         --needed_committee;\n   }\n\n   FC_ASSERT( needed_witnesses == 0 && needed_committee == 0,\n              \"May not specify fewer witnesses or committee members than the number voted for.\");\n}\n\nshare_type account_create_operation::calculate_fee( const fee_params_t& k )const\n{\n   auto core_fee_required = k.basic_fee;\n\n   if( !is_cheap_name(name) )\n      core_fee_required = k.premium_fee;\n\n   // Authorities and vote lists can be arbitrarily large, so charge a data fee for big ones\n   auto data_fee =  calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); \n   core_fee_required += data_fee;\n\n   return core_fee_required;\n}\n\nvoid account_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( is_valid_name( name ) );\n   FC_ASSERT( referrer_percent <= GRAPHENE_100_PERCENT );\n   FC_ASSERT( owner.num_auths() != 0 );\n   FC_ASSERT( owner.address_auths.size() == 0 );\n   FC_ASSERT( active.num_auths() != 0 );\n   FC_ASSERT( active.address_auths.size() == 0 );\n   FC_ASSERT( !owner.is_impossible(), \"cannot create an account with an impossible owner authority threshold\" );\n   FC_ASSERT( !active.is_impossible(), \"cannot create an account with an impossible active authority threshold\" );\n   options.validate();\n   if( extensions.value.owner_special_authority.valid() )\n      validate_special_authority( *extensions.value.owner_special_authority );\n   if( extensions.value.active_special_authority.valid() )\n      validate_special_authority( *extensions.value.active_special_authority );\n   if( extensions.value.buyback_options.valid() )\n   {\n      FC_ASSERT( !(extensions.value.owner_special_authority.valid()) );\n      FC_ASSERT( !(extensions.value.active_special_authority.valid()) );\n      FC_ASSERT( owner == authority::null_authority() );\n      FC_ASSERT( active == authority::null_authority() );\n      size_t n_markets = extensions.value.buyback_options->markets.size();\n      FC_ASSERT( n_markets > 0 );\n      for( const asset_id_type& m : extensions.value.buyback_options->markets )\n      {\n         FC_ASSERT( m != extensions.value.buyback_options->asset_to_buy );\n      }\n   }\n}\n\nshare_type account_update_operation::calculate_fee( const fee_params_t& k )const\n{\n   auto core_fee_required = k.fee;  \n   if( new_options )\n      core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );\n   return core_fee_required;\n}\n\nvoid account_update_operation::validate()const\n{\n   FC_ASSERT( account != GRAPHENE_TEMP_ACCOUNT );\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( account != account_id_type() );\n\n   bool has_action = (\n         owner.valid()\n      || active.valid()\n      || new_options.valid()\n      || extensions.value.owner_special_authority.valid()\n      || extensions.value.active_special_authority.valid()\n      );\n\n   FC_ASSERT( has_action );\n\n   if( owner )\n   {\n      FC_ASSERT( owner->num_auths() != 0 );\n      FC_ASSERT( owner->address_auths.size() == 0 );\n      FC_ASSERT( !owner->is_impossible(), \"cannot update an account with an impossible owner authority threshold\" );\n   }\n   if( active )\n   {\n      FC_ASSERT( active->num_auths() != 0 );\n      FC_ASSERT( active->address_auths.size() == 0 );\n      FC_ASSERT( !active->is_impossible(), \"cannot update an account with an impossible active authority threshold\" );\n   }\n\n   if( new_options )\n      new_options->validate();\n   if( extensions.value.owner_special_authority.valid() )\n      validate_special_authority( *extensions.value.owner_special_authority );\n   if( extensions.value.active_special_authority.valid() )\n      validate_special_authority( *extensions.value.active_special_authority );\n}\n\nshare_type account_upgrade_operation::calculate_fee(const fee_params_t& k) const\n{\n   if( upgrade_to_lifetime_member )\n      return k.membership_lifetime_fee;\n   return k.membership_annual_fee;\n}\n\nvoid account_upgrade_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n}\n\nvoid account_transfer_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_options )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation )\n"
  },
  {
    "path": "libraries/protocol/address.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/address.hpp>\n#include <graphene/protocol/pts_address.hpp>\n#include <fc/crypto/base58.hpp>\n#include <algorithm>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\n   address::address( const std::string& base58str )\n   {\n      std::string prefix( GRAPHENE_ADDRESS_PREFIX );\n      FC_ASSERT( is_valid( base58str, prefix ), \"${str}\", (\"str\",base58str) );\n\n      std::vector<char> v = fc::from_base58( base58str.substr( prefix.size() ) );\n      memcpy( (char*)addr._hash, v.data(), std::min<size_t>( v.size()-4, sizeof( addr ) ) );\n   }\n\n   bool address::is_valid( const std::string& base58str, const std::string& prefix )\n   {\n      const size_t prefix_len = prefix.size();\n      if( base58str.size() <= prefix_len )\n          return false;\n      if( base58str.substr( 0, prefix_len ) != prefix )\n          return false;\n      std::vector<char> v;\n      try\n      {\n         v = fc::from_base58( base58str.substr( prefix_len ) );\n      }\n      catch( const fc::parse_error_exception& e )\n      {\n        return false;\n      }\n\n      if( v.size() != sizeof( fc::ripemd160 ) + 4 )\n          return false;\n\n      const fc::ripemd160 checksum = fc::ripemd160::hash( v.data(), v.size() - 4 );\n      if( memcmp( v.data() + 20, (char*)checksum._hash, 4 ) != 0 )\n          return false;\n\n      return true;\n   }\n\n   address::address( const fc::ecc::public_key& pub )\n   {\n       auto dat = pub.serialize();\n       addr = fc::ripemd160::hash( fc::sha512::hash( (char*) dat.data(), dat.size() ) );\n   }\n\n   address::address( const pts_address& ptsaddr )\n   {\n       addr = fc::ripemd160::hash( (char*)&ptsaddr, sizeof( ptsaddr ) );\n   }\n\n   address::address( const fc::ecc::public_key_data& pub )\n   {\n       addr = fc::ripemd160::hash( fc::sha512::hash( (char*) pub.data(), pub.size() ) );\n   }\n\n   address::address( const graphene::protocol::public_key_type& pub )\n   {\n       addr = fc::ripemd160::hash( fc::sha512::hash( (char*) pub.key_data.data(), pub.key_data.size() ) );\n   }\n\n   address::operator std::string()const\n   {\n        char bin_addr[24];\n        static_assert( sizeof(bin_addr) >= sizeof(addr) + 4, \"address size mismatch\" );\n        memcpy( bin_addr, addr.data(), sizeof(addr) );\n        auto checksum = fc::ripemd160::hash( addr.data(), sizeof(addr) );\n        memcpy( bin_addr + sizeof(addr), (char*)&checksum._hash[0], 4 );\n        return GRAPHENE_ADDRESS_PREFIX + fc::to_base58( bin_addr, sizeof(bin_addr) );\n   }\n\n} } // namespace graphene::protocol\n\nnamespace fc\n{\n    void to_variant( const graphene::protocol::address& var,  variant& vo, uint32_t max_depth )\n    {\n        vo = std::string(var);\n    }\n    void from_variant( const variant& var,  graphene::protocol::address& vo, uint32_t max_depth )\n    {\n        vo = graphene::protocol::address( var.as_string() );\n    }\n}\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::address )\n"
  },
  {
    "path": "libraries/protocol/assert.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/account.hpp>\n#include <graphene/protocol/asset_ops.hpp>\n#include <graphene/protocol/assert.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nbool account_name_eq_lit_predicate::validate()const\n{\n   return is_valid_name( name );\n}\n\nbool asset_symbol_eq_lit_predicate::validate()const\n{\n   return is_valid_symbol( symbol );\n}\n\nstruct predicate_validator\n{\n   typedef void result_type;\n\n   template<typename T>\n   void operator()( const T& p )const\n   {\n      p.validate();\n   }\n};\n\nvoid assert_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   for( const auto& item : predicates )\n      item.visit( predicate_validator() );\n}\n\n/**\n * The fee for assert operations is proportional to their size,\n * but cheaper than a data fee because they require no storage\n */\nshare_type  assert_operation::calculate_fee(const fee_params_t& k)const\n{\n   return k.fee * predicates.size();\n}\n\n} }  // namespace graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation )\n"
  },
  {
    "path": "libraries/protocol/asset.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/asset.hpp>\n#include <boost/rational.hpp>\n#include <boost/multiprecision/cpp_int.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace protocol {\n      using fc::uint128_t;\n\n      bool operator == ( const price& a, const price& b )\n      {\n         if( std::tie( a.base.asset_id, a.quote.asset_id ) != std::tie( b.base.asset_id, b.quote.asset_id ) )\n            return false;\n\n         const auto amult = uint128_t( b.quote.amount.value ) * a.base.amount.value;\n         const auto bmult = uint128_t( a.quote.amount.value ) * b.base.amount.value;\n\n         return amult == bmult;\n      }\n\n      bool operator < ( const price& a, const price& b )\n      {\n         if( a.base.asset_id < b.base.asset_id ) return true;\n         if( a.base.asset_id > b.base.asset_id ) return false;\n         if( a.quote.asset_id < b.quote.asset_id ) return true;\n         if( a.quote.asset_id > b.quote.asset_id ) return false;\n\n         const auto amult = uint128_t( b.quote.amount.value ) * a.base.amount.value;\n         const auto bmult = uint128_t( a.quote.amount.value ) * b.base.amount.value;\n\n         return amult < bmult;\n      }\n\n      asset operator * ( const asset& a, const price& b )\n      {\n         if( a.asset_id == b.base.asset_id )\n         {\n            FC_ASSERT( b.base.amount.value > 0 );\n            uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value)/b.base.amount.value;\n            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );\n            return asset( static_cast<int64_t>(result), b.quote.asset_id );\n         }\n         else if( a.asset_id == b.quote.asset_id )\n         {\n            FC_ASSERT( b.quote.amount.value > 0 );\n            uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value)/b.quote.amount.value;\n            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );\n            return asset( static_cast<int64_t>(result), b.base.asset_id );\n         }\n         FC_THROW_EXCEPTION( fc::assert_exception, \"invalid asset * price\", (\"asset\",a)(\"price\",b) );\n      }\n\n      asset asset::multiply_and_round_up( const price& b )const\n      {\n         const asset& a = *this;\n         if( a.asset_id == b.base.asset_id )\n         {\n            FC_ASSERT( b.base.amount.value > 0 );\n            uint128_t result = ( ( ( uint128_t(a.amount.value) * b.quote.amount.value ) + b.base.amount.value ) - 1 )\n                               / b.base.amount.value;\n            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );\n            return asset( static_cast<int64_t>(result), b.quote.asset_id );\n         }\n         else if( a.asset_id == b.quote.asset_id )\n         {\n            FC_ASSERT( b.quote.amount.value > 0 );\n            uint128_t result = ( ( ( uint128_t(a.amount.value) * b.base.amount.value ) + b.quote.amount.value ) - 1 )\n                               / b.quote.amount.value;\n            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );\n            return asset( static_cast<int64_t>(result), b.base.asset_id );\n         }\n         FC_THROW_EXCEPTION( fc::assert_exception,\n                             \"invalid asset::multiply_and_round_up(price)\", (\"asset\",a)(\"price\",b) );\n      }\n\n      price operator / ( const asset& base, const asset& quote )\n      { try {\n         FC_ASSERT( base.asset_id != quote.asset_id );\n         return price{base,quote};\n      } FC_CAPTURE_AND_RETHROW( (base)(quote) ) }\n\n      price price::max( asset_id_type base, asset_id_type quote )\n      { return asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), base ) / asset( share_type(1), quote); }\n\n      price price::min( asset_id_type base, asset_id_type quote )\n      { return asset( share_type(1), base ) / asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), quote); }\n\n      price operator *  ( const price& p, const ratio_type& r )\n      { try {\n         p.validate();\n\n         FC_ASSERT( r.numerator() > 0 && r.denominator() > 0 );\n\n         if( r.numerator() == r.denominator() ) return p;\n\n         boost::rational<uint128_t> p128( p.base.amount.value, p.quote.amount.value );\n         boost::rational<uint128_t> r128( r.numerator(), r.denominator() );\n         auto cp = p128 * r128;\n         auto ocp = cp;\n\n         bool shrinked = false;\n         bool using_max = false;\n         static const uint128_t max( GRAPHENE_MAX_SHARE_SUPPLY );\n         while( cp.numerator() > max || cp.denominator() > max )\n         {\n            if( 1 == cp.numerator() )\n            {\n               cp = boost::rational<uint128_t>( 1, max );\n               using_max = true;\n               break;\n            }\n            else if( 1 == cp.denominator() )\n            {\n               cp = boost::rational<uint128_t>( max, 1 );\n               using_max = true;\n               break;\n            }\n            else\n            {\n               cp = boost::rational<uint128_t>( cp.numerator() >> 1, cp.denominator() >> 1 );\n               shrinked = true;\n            }\n         }\n         if( shrinked ) // maybe not accurate enough due to rounding, do additional checks here\n         {\n            uint128_t num = ocp.numerator();\n            uint128_t den = ocp.denominator();\n            FC_ASSERT( num > 0 && den > 0, \"Internal error\" );\n            if( num > den )\n            {\n               num /= den;\n               num = std::min( num, max );\n               den = 1;\n            }\n            else\n            {\n               den /= num;\n               den = std::min( den, max );\n               num = 1;\n            }\n            boost::rational<uint128_t> ncp( num, den );\n            if( num == max || den == max ) // it's on the edge, we know it's accurate enough\n               cp = ncp;\n            else\n            {\n               // from the accurate ocp, now we have ncp and cp. use the one which is closer to ocp.\n               // TODO improve performance\n               auto diff1 = (ncp >= ocp) ? (ncp - ocp) : (ocp - ncp);\n               auto diff2 = (cp >= ocp) ? (cp - ocp) : (ocp - cp);\n               if( diff1 < diff2 ) cp = ncp;\n            }\n         }\n\n         price np = asset( static_cast<int64_t>(cp.numerator()), p.base.asset_id )\n                  / asset( static_cast<int64_t>(cp.denominator()), p.quote.asset_id );\n\n         if( shrinked || using_max )\n         {\n            bool flipped = ( r.numerator() > r.denominator() ) ? ( np < p ) : ( np > p );\n            if( flipped )\n               // even with an accurate result, if p is out of valid range, return it\n               np = p;\n         }\n\n         np.validate();\n         return np;\n      } FC_CAPTURE_AND_RETHROW( (p)(r.numerator())(r.denominator()) ) }\n\n      price operator /  ( const price& p, const ratio_type& r )\n      { try {\n         return p * ratio_type( r.denominator(), r.numerator() );\n      } FC_CAPTURE_AND_RETHROW( (p)(r.numerator())(r.denominator()) ) }\n\n      /**\n       *  The black swan price is defined as debt/collateral, we want to perform a margin call\n       *  before debt == collateral.   Given a debt/collateral ratio of 1 USD / CORE and\n       *  a maintenance collateral requirement of 2x we can define the call price to be\n       *  2 USD / CORE.\n       *\n       *  This method divides the collateral by the maintenance collateral ratio to derive\n       *  a call price for the given black swan ratio.\n       *\n       *  There exists some cases where the debt and collateral values are so small that\n       *  dividing by the collateral ratio will result in a 0 price or really poor\n       *  rounding errors.   No matter what the collateral part of the price ratio can\n       *  never go to 0 and the debt can never go more than GRAPHENE_MAX_SHARE_SUPPLY\n       *\n       *  CR * DEBT/COLLAT or DEBT/(COLLAT/CR)\n       *\n       *  Note: this function is only used before core-1270 hard fork.\n       */\n      price price::call_price( const asset& debt, const asset& collateral, uint16_t collateral_ratio)\n      { try {\n         boost::rational<uint128_t> swan(debt.amount.value,collateral.amount.value);\n         boost::rational<uint128_t> ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );\n         auto cp = swan * ratio;\n\n         while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY )\n            cp = boost::rational<uint128_t>( (cp.numerator() >> 1)+1, (cp.denominator() >> 1)+1 );\n\n         return  (  asset( static_cast<int64_t>(cp.denominator()), collateral.asset_id )\n                  / asset( static_cast<int64_t>(cp.numerator()), debt.asset_id ) );\n      } FC_CAPTURE_AND_RETHROW( (debt)(collateral)(collateral_ratio) ) }\n\n      bool price::is_null() const\n      {\n         // Effectively same as \"return *this == price();\" but perhaps faster\n         return ( base.asset_id == asset_id_type() && quote.asset_id == asset_id_type() );\n      }\n\n      void price::validate( bool check_upper_bound /* = false */ )const\n      { try {\n         FC_ASSERT( base.amount.value > 0, \"Base amount should be positive\" );\n         FC_ASSERT( quote.amount.value > 0, \"Quote amount should be positive\" );\n         FC_ASSERT( base.asset_id != quote.asset_id, \"Base asset ID and quote asset ID should be different\" );\n         if( check_upper_bound )\n         {\n            FC_ASSERT( base.amount.value <= GRAPHENE_MAX_SHARE_SUPPLY,\n                       \"Base amount should not be greater than ${max}\",\n                       (\"max\", GRAPHENE_MAX_SHARE_SUPPLY) );\n            FC_ASSERT( quote.amount.value <= GRAPHENE_MAX_SHARE_SUPPLY,\n                       \"Quote amount should not be greater than ${max}\",\n                       (\"max\", GRAPHENE_MAX_SHARE_SUPPLY) );\n         }\n      } FC_CAPTURE_AND_RETHROW( (base)(quote) ) }\n\n      void price_feed::validate() const\n      { try {\n         if( !settlement_price.is_null() )\n            settlement_price.validate();\n         FC_ASSERT( maximum_short_squeeze_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );\n         FC_ASSERT( maximum_short_squeeze_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n         FC_ASSERT( maintenance_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );\n         FC_ASSERT( maintenance_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n         // Note: there was code here calling `max_short_squeeze_price();` before core-1270 hard fork,\n         //       in order to make sure that it doesn't overflow,\n         //       but the code doesn't actually check overflow, and it won't overflow, so the code is removed.\n\n         // Note: not checking `maintenance_collateral_ratio >= maximum_short_squeeze_ratio` since launch\n      } FC_CAPTURE_AND_RETHROW( (*this) ) }\n\n      bool price_feed::is_for( asset_id_type asset_id ) const\n      {\n         try\n         {\n            if( !settlement_price.is_null() )\n               return (settlement_price.base.asset_id == asset_id);\n            if( !core_exchange_rate.is_null() )\n               return (core_exchange_rate.base.asset_id == asset_id);\n            // (null, null) is valid for any feed\n            return true;\n         }\n         FC_CAPTURE_AND_RETHROW( (*this) )\n      }\n\n      // This function is kept here due to potential different behavior in edge cases.\n      // TODO check after core-1270 hard fork to see if we can safely remove it\n      price price_feed::max_short_squeeze_price_before_hf_1270()const\n      {\n         // settlement price is in debt/collateral\n         boost::rational<uint128_t> sp( settlement_price.base.amount.value, settlement_price.quote.amount.value );\n         boost::rational<uint128_t> ratio( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio );\n         auto cp = sp * ratio;\n\n         while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY )\n            cp = boost::rational<uint128_t>( (cp.numerator() >> 1)+(cp.numerator()&1U),\n                                            (cp.denominator() >> 1)+(cp.denominator()&1U) );\n\n         return (  asset( static_cast<int64_t>(cp.numerator()), settlement_price.base.asset_id )\n                 / asset( static_cast<int64_t>(cp.denominator()), settlement_price.quote.asset_id ) );\n      }\n\n\n      // Documentation in header.\n      // Calculation:  MSSP = settlement_price / MSSR\n      price price_feed::max_short_squeeze_price()const\n      {\n         // settlement price is in debt/collateral\n         return settlement_price * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio );\n      }\n\n      // Documentation in header.\n      // Calculation:  MCOP = settlement_price / (MSSR - MCFR); result is in debt/collateral\n      price price_feed::margin_call_order_price(const fc::optional<uint16_t>& maybe_mcfr)const\n      {\n         return settlement_price / margin_call_order_ratio( maybe_mcfr );\n      }\n\n      // Calculation:  MCOR = MSSR - MCFR, floor at 1.00\n      uint16_t price_feed::get_margin_call_price_numerator(const fc::optional<uint16_t>& maybe_mcfr)const\n      {\n         const uint16_t mcfr = maybe_mcfr.valid() ? *maybe_mcfr : 0;\n         uint16_t numerator = (mcfr < maximum_short_squeeze_ratio) ?\n            (maximum_short_squeeze_ratio - mcfr) : GRAPHENE_COLLATERAL_RATIO_DENOM; // won't underflow\n         if (numerator < GRAPHENE_COLLATERAL_RATIO_DENOM)\n            numerator = GRAPHENE_COLLATERAL_RATIO_DENOM; // floor at 1.00\n         return numerator;\n      }\n\n      // Documentation in header.\n      // Calculation:  MCOR = MSSR - MCFR\n      ratio_type price_feed::margin_call_order_ratio(const fc::optional<uint16_t>& maybe_mcfr)const\n      {\n         auto numerator = get_margin_call_price_numerator( maybe_mcfr );\n         return ratio_type( numerator, GRAPHENE_COLLATERAL_RATIO_DENOM );\n      }\n\n      // Reason for this function is explained in header.\n      // Calculation: (MSSR - MCFR) / MSSR\n      ratio_type price_feed::margin_call_pays_ratio(const fc::optional<uint16_t>& maybe_mcfr)const\n      {\n         auto numerator = get_margin_call_price_numerator( maybe_mcfr );\n         return ratio_type( numerator, maximum_short_squeeze_ratio );\n         // Note: This ratio, if it multiplied margin_call_order_price, would yield the\n         // max_short_squeeze_price, apart perhaps for truncation (rounding) error.\n      }\n\n      price price_feed::maintenance_collateralization()const\n      {\n         if( settlement_price.is_null() )\n            return price();\n         return ~settlement_price * ratio_type( maintenance_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );\n      }\n\n// compile-time table of powers of 10 using template metaprogramming\n\ntemplate< size_t N >\nstruct p10\n{\n   static constexpr int64_t v = 10 * p10<N-1>::v;\n};\n\ntemplate<>\nstruct p10<0>\n{\n   static constexpr int64_t v = 1;\n};\n\nshare_type asset::scaled_precision( uint8_t precision )\n{\n   FC_ASSERT( precision < 19 );\n   static constexpr std::array<int64_t, 19> scaled_precision_lut =\n   {\n      p10<  0 >::v, p10<  1 >::v, p10<  2 >::v, p10<  3 >::v,\n      p10<  4 >::v, p10<  5 >::v, p10<  6 >::v, p10<  7 >::v,\n      p10<  8 >::v, p10<  9 >::v, p10< 10 >::v, p10< 11 >::v,\n      p10< 12 >::v, p10< 13 >::v, p10< 14 >::v, p10< 15 >::v,\n      p10< 16 >::v, p10< 17 >::v, p10< 18 >::v\n   };\n\n   return scaled_precision_lut[ precision ];\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::price )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::price_feed )\n"
  },
  {
    "path": "libraries/protocol/asset_ops.cpp",
    "content": "/*\n * Copyright (c) 2015-2018 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/asset_ops.hpp>\n\n#include <fc/io/raw.hpp>\n\n#include <locale>\n\nnamespace graphene { namespace protocol {\n\n/**\n *  Valid symbols can contain [A-Z0-9], and '.'\n *  They must start with [A, Z]\n *  They must end with [A, Z] before HF_620 or [A-Z0-9] after it\n *  They can contain a maximum of one '.'\n */\nbool is_valid_symbol( const string& symbol )\n{\n    static const std::locale& loc = std::locale::classic();\n    if( symbol.size() < GRAPHENE_MIN_ASSET_SYMBOL_LENGTH )\n        return false;\n\n    if( symbol.substr(0,3) == \"BIT\" )\n        return false;\n\n    if( symbol.size() > GRAPHENE_MAX_ASSET_SYMBOL_LENGTH )\n        return false;\n\n    if( !isalpha( symbol.front(), loc ) )\n        return false;\n\n    if( !isalnum( symbol.back(), loc ) )\n        return false;\n\n    bool dot_already_present = false;\n    for( const auto c : symbol )\n    {\n        if( (isalpha( c, loc ) && isupper( c, loc )) || isdigit( c, loc ) )\n            continue;\n\n        if( c == '.' )\n        {\n            if( dot_already_present )\n                return false;\n\n            dot_already_present = true;\n            continue;\n        }\n\n        return false;\n    }\n\n    return true;\n}\n\nshare_type asset_issue_operation::calculate_fee(const fee_params_t& k)const\n{\n   return k.fee + calculate_data_fee( fc::raw::pack_size(memo), k.price_per_kbyte );\n}\n\nshare_type asset_create_operation::calculate_fee( const asset_create_operation::fee_params_t& param,\n                                                  const optional<uint64_t>& sub_asset_creation_fee )const\n{\n   share_type core_fee_required = param.long_symbol;\n\n   if( sub_asset_creation_fee.valid() && symbol.find('.') != std::string::npos )\n   {\n      core_fee_required = *sub_asset_creation_fee;\n   }\n   else\n   {\n      switch( symbol.size() )\n      {\n      case 3: core_fee_required = param.symbol3;\n          break;\n      case 4: core_fee_required = param.symbol4;\n          break;\n      default:\n          break;\n      }\n   }\n\n   // common_options contains several lists and a string. Charge fees for its size\n   core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), param.price_per_kbyte );\n\n   return core_fee_required;\n}\n\nvoid  asset_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( is_valid_symbol(symbol) );\n   common_options.validate();\n   // TODO fix the missing check for witness_fed_asset and committee_fed_asset with a hard fork\n   if( 0 != ( common_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK\n              & (uint16_t)( ~(witness_fed_asset|committee_fed_asset) ) ) )\n      FC_ASSERT( bitasset_opts.valid() );\n   if( is_prediction_market )\n   {\n      FC_ASSERT( bitasset_opts.valid(), \"Cannot have a User-Issued Asset implement a prediction market.\" );\n      FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) );\n      FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bsrm_update) );\n      FC_ASSERT( !bitasset_opts->extensions.value.black_swan_response_method.valid(),\n                 \"Can not set black_swan_response_method for Prediction Markets\" );\n   }\n   if( bitasset_opts ) bitasset_opts->validate();\n\n   asset dummy = asset(1) * common_options.core_exchange_rate;\n   FC_ASSERT(dummy.asset_id == asset_id_type(1));\n   FC_ASSERT(precision <= 12);\n}\n\nvoid asset_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   if( new_issuer )\n      FC_ASSERT(issuer != *new_issuer);\n   new_options.validate();\n\n   asset dummy = asset(1, asset_to_update) * new_options.core_exchange_rate;\n   FC_ASSERT(dummy.asset_id == asset_id_type());\n\n   if( extensions.value.new_precision.valid() )\n      FC_ASSERT( *extensions.value.new_precision <= 12 );\n\n   if( extensions.value.skip_core_exchange_rate.valid() )\n   {\n      FC_ASSERT( *extensions.value.skip_core_exchange_rate == true,\n                 \"If skip_core_exchange_rate is specified, it can only be true\" );\n   }\n}\n\nvoid asset_update_issuer_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( issuer != new_issuer );\n}\n\nshare_type asset_update_operation::calculate_fee(const asset_update_operation::fee_params_t& k)const\n{\n   return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );\n}\n\n\nvoid asset_publish_feed_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   feed.validate();\n\n   // maybe some of these could be moved to feed.validate()\n   if( !feed.core_exchange_rate.is_null() )\n   {\n      feed.core_exchange_rate.validate();\n   }\n   if( (!feed.settlement_price.is_null()) && (!feed.core_exchange_rate.is_null()) )\n   {\n      FC_ASSERT( feed.settlement_price.base.asset_id == feed.core_exchange_rate.base.asset_id );\n   }\n\n   FC_ASSERT( !feed.settlement_price.is_null() );\n   FC_ASSERT( !feed.core_exchange_rate.is_null() );\n   FC_ASSERT( feed.is_for( asset_id ) );\n\n   if( extensions.value.initial_collateral_ratio.valid() )\n   {\n      FC_ASSERT( *extensions.value.initial_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );\n      FC_ASSERT( *extensions.value.initial_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n   }\n}\n\nvoid asset_reserve_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( amount_to_reserve.amount.value <= GRAPHENE_MAX_SHARE_SUPPLY );\n   FC_ASSERT( amount_to_reserve.amount.value > 0 );\n}\n\nvoid asset_issue_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( asset_to_issue.amount.value <= GRAPHENE_MAX_SHARE_SUPPLY );\n   FC_ASSERT( asset_to_issue.amount.value > 0 );\n   FC_ASSERT( asset_to_issue.asset_id != asset_id_type(0) );\n}\n\nvoid asset_fund_fee_pool_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( fee.asset_id == asset_id_type() );\n   FC_ASSERT( amount > 0 );\n}\n\nvoid asset_settle_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( amount.amount >= 0 );\n}\n\nvoid asset_update_bitasset_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   new_options.validate();\n}\n\nvoid asset_update_feed_producers_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n}\n\nvoid asset_global_settle_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( asset_to_settle == settle_price.base.asset_id );\n}\n\nvoid bitasset_options::validate() const\n{\n   FC_ASSERT(minimum_feeds > 0);\n   FC_ASSERT(force_settlement_offset_percent <= GRAPHENE_100_PERCENT);\n   FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT);\n\n   if( extensions.value.margin_call_fee_ratio.valid() )\n      FC_ASSERT( *extensions.value.margin_call_fee_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n\n   if( extensions.value.initial_collateral_ratio.valid() )\n   {\n      FC_ASSERT( *extensions.value.initial_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );\n      FC_ASSERT( *extensions.value.initial_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n   }\n   if( extensions.value.maintenance_collateral_ratio.valid() )\n   {\n      FC_ASSERT( *extensions.value.maintenance_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );\n      FC_ASSERT( *extensions.value.maintenance_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n   }\n   if( extensions.value.maximum_short_squeeze_ratio.valid() )\n   {\n      FC_ASSERT( *extensions.value.maximum_short_squeeze_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );\n      FC_ASSERT( *extensions.value.maximum_short_squeeze_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );\n   }\n\n   if( extensions.value.force_settle_fee_percent.valid() )\n      FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT );\n\n   if( extensions.value.black_swan_response_method.valid() )\n   {\n      auto bsrm_count = static_cast<uint8_t>( black_swan_response_type::BSRM_TYPE_COUNT );\n      FC_ASSERT( *extensions.value.black_swan_response_method < bsrm_count,\n                 \"black_swan_response_method should be less than ${c}\", (\"c\",bsrm_count) );\n   }\n}\n\nvoid asset_options::validate()const\n{\n   FC_ASSERT( max_supply > 0 );\n   FC_ASSERT( max_supply <= GRAPHENE_MAX_SHARE_SUPPLY );\n   // The non-negative maker fee must be less than or equal to 100%\n   FC_ASSERT( market_fee_percent <= GRAPHENE_100_PERCENT );\n\n   // The non-negative taker fee must be less than or equal to 100%\n   if( extensions.value.taker_fee_percent.valid() )\n      FC_ASSERT( *extensions.value.taker_fee_percent <= GRAPHENE_100_PERCENT );\n\n   FC_ASSERT( max_market_fee >= 0 && max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY );\n   // There must be no high bits in permissions whose meaning is not known.\n   FC_ASSERT( 0 == (issuer_permissions & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)) );\n   // The permission-only bits can not be set in flag\n   FC_ASSERT( 0 == (flags & global_settle),\n              \"Can not set global_settle flag, it is for issuer permission only\" );\n\n   // the witness_fed and committee_fed flags cannot be set simultaneously\n   FC_ASSERT( (flags & (witness_fed_asset | committee_fed_asset)) != (witness_fed_asset | committee_fed_asset) );\n   core_exchange_rate.validate();\n   FC_ASSERT( core_exchange_rate.base.asset_id.instance.value == 0 ||\n              core_exchange_rate.quote.asset_id.instance.value == 0 );\n\n   if(!whitelist_authorities.empty() || !blacklist_authorities.empty())\n      FC_ASSERT( 0 != (flags & white_list) );\n   for( auto item : whitelist_markets )\n   {\n      FC_ASSERT( blacklist_markets.find(item) == blacklist_markets.end() );\n   }\n   for( auto item : blacklist_markets )\n   {\n      FC_ASSERT( whitelist_markets.find(item) == whitelist_markets.end() );\n   }\n   if( extensions.value.reward_percent.valid() )\n      FC_ASSERT( *extensions.value.reward_percent <= GRAPHENE_100_PERCENT );\n}\n\n// Note: this function is only called after the BSIP 48/75 hardfork\nvoid asset_options::validate_flags( bool is_market_issued, bool allow_disable_collateral_bid )const\n{\n   FC_ASSERT( 0 == (flags & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)),\n              \"Can not set an unknown bit in flags\" );\n   if( !allow_disable_collateral_bid ) // before core-2281 hf, can not set the disable_collateral_bidding bit\n      FC_ASSERT( 0 == (flags & disable_collateral_bidding),\n                 \"Can not set the 'disable_collateral_bidding' bit in flags between the core-2281 hardfork \"\n                 \"and the BSIP_48_75 hardfork\" );\n   // Note: global_settle is checked in validate(), so do not check again here\n   FC_ASSERT( 0 == (flags & disable_mcr_update),\n              \"Can not set disable_mcr_update flag, it is for issuer permission only\" );\n   FC_ASSERT( 0 == (flags & disable_icr_update),\n              \"Can not set disable_icr_update flag, it is for issuer permission only\" );\n   FC_ASSERT( 0 == (flags & disable_mssr_update),\n              \"Can not set disable_mssr_update flag, it is for issuer permission only\" );\n   FC_ASSERT( 0 == (flags & disable_bsrm_update),\n              \"Can not set disable_bsrm_update flag, it is for issuer permission only\" );\n   if( !is_market_issued )\n   {\n      FC_ASSERT( 0 == (flags & (uint16_t)(~UIA_ASSET_ISSUER_PERMISSION_MASK)),\n                 \"Can not set a flag for bitassets only to UIA\" );\n   }\n}\n\nuint16_t asset_options::get_enabled_issuer_permissions_mask() const\n{\n   return ( (issuer_permissions & ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK)\n          | ((uint16_t)(~issuer_permissions) & ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK) );\n}\n\nvoid asset_claim_fees_operation::validate()const {\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( amount_to_claim.amount > 0 );\n   if( extensions.value.claim_from_asset_id.valid() )\n     FC_ASSERT( *extensions.value.claim_from_asset_id != amount_to_claim.asset_id );\n}\n\nvoid asset_claim_pool_operation::validate()const {\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( fee.asset_id != asset_id);\n   FC_ASSERT( amount_to_claim.amount > 0 );\n   FC_ASSERT( amount_to_claim.asset_id == asset_id_type());\n}\n\n} } // namespace graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_options )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options::ext )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::additional_asset_options )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::ext )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::ext )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed_producers_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_params_t )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_cancel_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed_producers_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation )\n"
  },
  {
    "path": "libraries/protocol/authority.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/authority.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid add_authority_accounts(\n   flat_set<account_id_type>& result,\n   const authority& a\n   )\n{\n   for( auto& item : a.account_auths )\n      result.insert( item.first );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::authority )\n"
  },
  {
    "path": "libraries/protocol/block.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <boost/endian/conversion.hpp>\n#include <graphene/protocol/block.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <fc/io/raw.hpp>\n#include <algorithm>\n\nnamespace graphene { namespace protocol {\n   digest_type block_header::digest()const\n   {\n      return digest_type::hash(*this);\n   }\n\n   uint32_t block_header::num_from_id(const block_id_type& id)\n   {\n      return boost::endian::endian_reverse(id._hash[0].value());\n   }\n\n   const block_id_type& signed_block_header::id()const\n   {\n      if( 0 == _block_id._hash[0].value() )\n      {\n         auto tmp = fc::sha224::hash( *this );\n         tmp._hash[0] = boost::endian::endian_reverse(block_num()); // store the block num in the ID, 160 bits is plenty for the hash\n         static_assert( sizeof(tmp._hash[0]) == 4, \"should be 4 bytes\" );\n         memcpy(_block_id._hash, tmp._hash, std::min(sizeof(_block_id), sizeof(tmp)));\n      }\n      return _block_id;\n   }\n\n   const fc::ecc::public_key& signed_block_header::signee()const\n   {\n      if( !_signee.valid() )\n         _signee = fc::ecc::public_key( witness_signature, digest(), true/*enforce canonical*/ );\n      return _signee;\n   }\n\n   void signed_block_header::sign( const fc::ecc::private_key& signer )\n   {\n      witness_signature = signer.sign_compact( digest() );\n   }\n\n   bool signed_block_header::validate_signee( const fc::ecc::public_key& expected_signee )const\n   {\n      return signee() == expected_signee;\n   }\n\n   const checksum_type& signed_block::calculate_merkle_root()const\n   {\n      static const checksum_type empty_checksum;\n      if( transactions.size() == 0 ) \n         return empty_checksum;\n\n      if( 0 == _calculated_merkle_root._hash[0].value() )\n      {\n         vector<digest_type> ids;\n         ids.resize( transactions.size() );\n         for( uint32_t i = 0; i < transactions.size(); ++i )\n            ids[i] = transactions[i].merkle_digest();\n\n         vector<digest_type>::size_type current_number_of_hashes = ids.size();\n         while( current_number_of_hashes > 1 )\n         {\n            // hash ID's in pairs\n            uint32_t i_max = current_number_of_hashes - (current_number_of_hashes&1);\n            uint32_t k = 0;\n\n            for( uint32_t i = 0; i < i_max; i += 2 )\n               ids[k++] = digest_type::hash( std::make_pair( ids[i], ids[i+1] ) );\n\n            if( current_number_of_hashes&1 )\n               ids[k++] = ids[i_max];\n            current_number_of_hashes = k;\n         }\n         _calculated_merkle_root = checksum_type::hash( ids[0] );\n      }\n      return _calculated_merkle_root;\n   }\n} }\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::block_header)\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::signed_block_header)\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::signed_block)\n"
  },
  {
    "path": "libraries/protocol/chain_parameters.cpp",
    "content": "#include <graphene/protocol/chain_parameters.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n   chain_parameters::chain_parameters() {\n       current_fees = std::make_shared<fee_schedule>();\n   }\n\n   // copy constructor\n   chain_parameters::chain_parameters(const chain_parameters& other)\n   {\n      current_fees = std::make_shared<fee_schedule>(*other.current_fees);\n      safe_copy(*this, other);\n   }\n\n   // copy assignment\n   chain_parameters& chain_parameters::operator=(const chain_parameters& other)\n   {\n      if (&other != this)\n      {\n         current_fees = std::make_shared<fee_schedule>(*other.current_fees);\n         safe_copy(*this, other);\n      }\n      return *this;\n   }\n\n   // copies the easy stuff\n   void chain_parameters::safe_copy(chain_parameters& to, const chain_parameters& from)\n   {\n      to.block_interval = from.block_interval;\n      to.maintenance_interval = from.maintenance_interval;\n      to.maintenance_skip_slots = from.maintenance_skip_slots;\n      to.committee_proposal_review_period = from.committee_proposal_review_period;\n      to.maximum_transaction_size = from.maximum_transaction_size;\n      to.maximum_block_size = from.maximum_block_size;\n      to.maximum_time_until_expiration = from.maximum_time_until_expiration;\n      to.maximum_proposal_lifetime = from.maximum_proposal_lifetime;\n      to.maximum_asset_whitelist_authorities = from.maximum_asset_whitelist_authorities;\n      to.maximum_asset_feed_publishers = from.maximum_asset_feed_publishers;\n      to.maximum_witness_count = from.maximum_witness_count;\n      to.maximum_committee_count = from.maximum_committee_count;\n      to.maximum_authority_membership = from.maximum_authority_membership;\n      to.reserve_percent_of_fee = from.reserve_percent_of_fee;\n      to.network_percent_of_fee = from.network_percent_of_fee;\n      to.lifetime_referrer_percent_of_fee = from.lifetime_referrer_percent_of_fee;\n      to.cashback_vesting_period_seconds = from.cashback_vesting_period_seconds;\n      to.cashback_vesting_threshold = from.cashback_vesting_threshold;\n      to.count_non_member_votes = from.count_non_member_votes;\n      to.allow_non_member_whitelists = from.allow_non_member_whitelists;\n      to.witness_pay_per_block = from.witness_pay_per_block;\n      to.witness_pay_vesting_seconds = from.witness_pay_vesting_seconds;\n      to.worker_budget_per_day = from.worker_budget_per_day;\n      to.max_predicate_opcode = from.max_predicate_opcode;\n      to.fee_liquidation_threshold = from.fee_liquidation_threshold;\n      to.accounts_per_fee_scale = from.accounts_per_fee_scale;\n      to.account_fee_scale_bitshifts = from.account_fee_scale_bitshifts;\n      to.max_authority_depth = from.max_authority_depth;\n      to.extensions = from.extensions;\n   }\n\n   // move constructor\n   chain_parameters::chain_parameters(chain_parameters&& other)\n   {\n      current_fees = std::move(other.current_fees);\n      safe_copy(*this, other);\n   }\n\n   // move assignment\n   chain_parameters& chain_parameters::operator=(chain_parameters&& other)\n   {\n      if (&other != this)\n      {\n         current_fees = std::move(other.current_fees);\n         safe_copy(*this, other);\n      }\n      return *this;\n   }\n\n   void chain_parameters::validate()const\n   {\n      get_current_fees().validate();\n      FC_ASSERT( reserve_percent_of_fee <= GRAPHENE_100_PERCENT );\n      FC_ASSERT( network_percent_of_fee <= GRAPHENE_100_PERCENT );\n      FC_ASSERT( lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT );\n      FC_ASSERT( network_percent_of_fee + lifetime_referrer_percent_of_fee <= GRAPHENE_100_PERCENT );\n\n      FC_ASSERT( block_interval >= GRAPHENE_MIN_BLOCK_INTERVAL );\n      FC_ASSERT( block_interval <= GRAPHENE_MAX_BLOCK_INTERVAL );\n      FC_ASSERT( block_interval > 0 );\n      FC_ASSERT( maintenance_interval > block_interval,\n                 \"Maintenance interval must be longer than block interval\" );\n      FC_ASSERT( maintenance_interval % block_interval == 0,\n                 \"Maintenance interval must be a multiple of block interval\" );\n      FC_ASSERT( maximum_transaction_size >= GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT,\n                 \"Transaction size limit is too low\" );\n      FC_ASSERT( maximum_block_size >= GRAPHENE_MIN_BLOCK_SIZE_LIMIT,\n                 \"Block size limit is too low\" );\n      FC_ASSERT( maximum_time_until_expiration > block_interval,\n                 \"Maximum transaction expiration time must be greater than a block interval\" );\n      FC_ASSERT( maximum_proposal_lifetime - committee_proposal_review_period > block_interval,\n                 \"Committee proposal review period must be less than the maximum proposal lifetime\" );\n      if( extensions.value.market_fee_network_percent.valid() )\n      {\n         FC_ASSERT( *extensions.value.market_fee_network_percent <= 3000, // GRAPHENE_100_PERCENT is 10000\n                    \"The market_fee_network_percent parameter can not exceed 30%\" );\n      }\n      if( extensions.value.maker_fee_discount_percent.valid() )\n      {\n         FC_ASSERT( *extensions.value.maker_fee_discount_percent <= GRAPHENE_100_PERCENT,\n                    \"The maker_fee_discount_percent parameter can not exceed 100%\" );\n      }\n   }\n\n   uint16_t chain_parameters::get_market_fee_network_percent() const\n   {\n      return extensions.value.market_fee_network_percent.valid() ?\n                *extensions.value.market_fee_network_percent : 0;\n   }\n\n   uint16_t chain_parameters::get_maker_fee_discount_percent() const\n   {\n      return extensions.value.maker_fee_discount_percent.valid() ?\n                *extensions.value.maker_fee_discount_percent : 0;\n   }\n\n}}\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::chain_parameters )\n"
  },
  {
    "path": "libraries/protocol/committee_member.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/committee_member.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid committee_member_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT(url.size() < GRAPHENE_MAX_URL_LENGTH );\n}\n\nvoid committee_member_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   if( new_url.valid() )\n      FC_ASSERT(new_url->size() < GRAPHENE_MAX_URL_LENGTH );\n}\n\nvoid committee_member_update_global_parameters_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   new_parameters.validate();\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION(\n   graphene::protocol::committee_member_update_global_parameters_operation::fee_params_t )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_global_parameters_operation )\n"
  },
  {
    "path": "libraries/protocol/confidential.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/confidential.hpp>\n\n#include <fc/crypto/base58.hpp>\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid transfer_to_blind_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( amount.amount > 0 );\n\n   vector<commitment_type> in;\n   vector<commitment_type> out(outputs.size());\n   int64_t                 net_public = amount.amount.value;\n   for( uint32_t i = 0; i < out.size(); ++i )\n   {\n      out[i] = outputs[i].commitment;\n      /// require all outputs to be sorted prevents duplicates AND prevents implementations\n      /// from accidentally leaking information by how they arrange commitments.\n      if( i > 0 ) FC_ASSERT( out[i-1] < out[i], \"all outputs must be sorted by commitment id\" );\n      FC_ASSERT( !outputs[i].owner.is_impossible() );\n   }\n   FC_ASSERT( out.size(), \"there must be at least one output\" );\n\n   auto public_c = fc::ecc::blind(blinding_factor,net_public);\n\n   FC_ASSERT( fc::ecc::verify_sum( {public_c}, out, 0 ), \"\", (\"net_public\",net_public) );\n\n   if( outputs.size() > 1 )\n   {\n      for( auto out : outputs )\n      {\n         auto info = fc::ecc::range_get_info( out.range_proof );\n         FC_ASSERT( info.max_value <= GRAPHENE_MAX_SHARE_SUPPLY );\n      }\n   }\n}\n\nshare_type transfer_to_blind_operation::calculate_fee( const fee_params_t& k )const\n{\n    return k.fee + outputs.size() * k.price_per_output;\n}\n\n\nvoid transfer_from_blind_operation::validate()const\n{\n   FC_ASSERT( amount.amount > 0 );\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( inputs.size() > 0 );\n   FC_ASSERT( amount.asset_id == fee.asset_id );\n\n\n   vector<commitment_type> in(inputs.size());\n   vector<commitment_type> out;\n   int64_t                 net_public = fee.amount.value + amount.amount.value;\n   out.push_back( fc::ecc::blind( blinding_factor, net_public ) );\n   for( uint32_t i = 0; i < in.size(); ++i )\n   {\n      in[i] = inputs[i].commitment;\n      /// by requiring all inputs to be sorted we also prevent duplicate commitments on the input\n      if( i > 0 ) FC_ASSERT( in[i-1] < in[i], \"all inputs must be sorted by commitment id\" );\n   }\n   FC_ASSERT( in.size(), \"there must be at least one input\" );\n   FC_ASSERT( fc::ecc::verify_sum( in, out, 0 ) );\n}\n\n\n/**\n *  If fee_payer = temp_account_id, then the fee is paid by the surplus balance of inputs-outputs and\n *  100% of the fee goes to the network.\n */\naccount_id_type blind_transfer_operation::fee_payer()const\n{\n   return GRAPHENE_TEMP_ACCOUNT;\n}\n\n\n/**\n *  This method can be computationally intensive because it verifies that input commitments - output commitments add up to 0\n */\nvoid blind_transfer_operation::validate()const\n{ try {\n   vector<commitment_type> in(inputs.size());\n   vector<commitment_type> out(outputs.size());\n   int64_t                 net_public = fee.amount.value;//from_amount.value - to_amount.value;\n   for( uint32_t i = 0; i < in.size(); ++i )\n   {\n      in[i] = inputs[i].commitment;\n      /// by requiring all inputs to be sorted we also prevent duplicate commitments on the input\n      if( i > 0 ) FC_ASSERT( in[i-1] < in[i] );\n   }\n   for( uint32_t i = 0; i < out.size(); ++i )\n   {\n      out[i] = outputs[i].commitment;\n      if( i > 0 ) FC_ASSERT( out[i-1] < out[i] );\n      FC_ASSERT( !outputs[i].owner.is_impossible() );\n   }\n   FC_ASSERT( in.size(), \"there must be at least one input\" );\n   FC_ASSERT( fc::ecc::verify_sum( in, out, net_public ), \"\", (\"net_public\", net_public) );\n\n   if( outputs.size() > 1 )\n   {\n      for( auto out : outputs )\n      {\n         auto info = fc::ecc::range_get_info( out.range_proof );\n         FC_ASSERT( info.max_value <= GRAPHENE_MAX_SHARE_SUPPLY );\n      }\n   }\n   FC_ASSERT( fc::ecc::verify_sum( in, out, net_public ), \"\", (\"net_public\", net_public) );\n} FC_CAPTURE_AND_RETHROW( (*this) ) }\n\nshare_type blind_transfer_operation::calculate_fee( const fee_params_t& k )const\n{\n    return k.fee + outputs.size() * k.price_per_output;\n}\n\n/**\n *  Packs *this then encodes as base58 encoded string.\n */\nstealth_confirmation::operator string()const\n{\n   return fc::to_base58( fc::raw::pack( *this ) );\n}\n/**\n * Unpacks from a base58 string\n */\nstealth_confirmation::stealth_confirmation( const std::string& base58 )\n{\n   *this = fc::raw::unpack<stealth_confirmation>( fc::from_base58( base58 ) );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation )\n"
  },
  {
    "path": "libraries/protocol/credit_offer.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/credit_offer.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nstatic void validate_acceptable_collateral( const flat_map<asset_id_type, price>& acceptable_collateral,\n                                            const asset_id_type* p_asset_type = nullptr )\n{\n   FC_ASSERT( !acceptable_collateral.empty(), \"Acceptable collateral list should not be empty\" );\n\n   asset_id_type asset_type = ( p_asset_type != nullptr ) ? *p_asset_type\n                              : acceptable_collateral.begin()->second.base.asset_id;\n\n   for( const auto& collateral : acceptable_collateral )\n   {\n      const auto& collateral_asset_type = collateral.first;\n      const auto& collateral_price = collateral.second;\n      FC_ASSERT( collateral_price.base.asset_id == asset_type,\n                 \"Base asset ID in price of acceptable collateral should be same as offer asset type\" );\n      FC_ASSERT( collateral_price.quote.asset_id == collateral_asset_type,\n                 \"Quote asset ID in price of acceptable collateral should be same as collateral asset type\" );\n      collateral_price.validate( true );\n   }\n}\n\nstatic void validate_acceptable_borrowers( const flat_map<account_id_type, share_type>& acceptable_borrowers )\n{\n   for( const auto& borrower : acceptable_borrowers )\n   {\n      const auto& max_borrow_amount = borrower.second.value;\n      FC_ASSERT( max_borrow_amount >= 0,\n                 \"Maximum amount to borrow for acceptable borrowers should not be negative\" );\n      FC_ASSERT( max_borrow_amount <= GRAPHENE_MAX_SHARE_SUPPLY,\n                 \"Maximum amount to borrow for acceptable borrowers should not be greater than ${max}\",\n                 (\"max\", GRAPHENE_MAX_SHARE_SUPPLY) );\n   }\n}\n\nvoid credit_offer_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( balance > 0, \"Balance should be positive\" );\n   FC_ASSERT( max_duration_seconds <= GRAPHENE_MAX_CREDIT_DEAL_SECS,\n              \"Maximum duration should not be greater than ${d} days\",\n              (\"d\", GRAPHENE_MAX_CREDIT_DEAL_DAYS) );\n   FC_ASSERT( min_deal_amount >= 0, \"Minimum deal amount should not be negative\" );\n   FC_ASSERT( min_deal_amount <= GRAPHENE_MAX_SHARE_SUPPLY,\n              \"Minimum deal amount should not be greater than ${max}\",\n              (\"max\", GRAPHENE_MAX_SHARE_SUPPLY) );\n\n   validate_acceptable_collateral( acceptable_collateral, &asset_type );\n   validate_acceptable_borrowers( acceptable_borrowers );\n}\n\nshare_type credit_offer_create_operation::calculate_fee( const fee_params_t& schedule )const\n{\n   share_type core_fee_required = schedule.fee;\n   core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte );\n   return core_fee_required;\n}\n\nvoid credit_offer_delete_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n}\n\nvoid credit_offer_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   bool updating_something = false;\n\n   if( delta_amount.valid() )\n   {\n      updating_something = true;\n      FC_ASSERT( delta_amount->amount != 0, \"Delta amount should not be zero\" );\n   }\n   if( fee_rate.valid() )\n      updating_something = true;\n   if( max_duration_seconds.valid() )\n   {\n      updating_something = true;\n      FC_ASSERT( *max_duration_seconds <= GRAPHENE_MAX_CREDIT_DEAL_SECS,\n                 \"Maximum duration should not be greater than ${d} days\",\n                 (\"d\", GRAPHENE_MAX_CREDIT_DEAL_DAYS) );\n   }\n   if( min_deal_amount.valid() )\n   {\n      updating_something = true;\n      FC_ASSERT( *min_deal_amount >= 0, \"Minimum deal amount should not be negative\" );\n      FC_ASSERT( *min_deal_amount <= GRAPHENE_MAX_SHARE_SUPPLY,\n                 \"Minimum deal amount should not be greater than ${max}\",\n                 (\"max\", GRAPHENE_MAX_SHARE_SUPPLY) );\n   }\n   if( enabled.valid() )\n      updating_something = true;\n   if( auto_disable_time.valid() )\n      updating_something = true;\n   if( acceptable_collateral.valid() )\n   {\n      updating_something = true;\n      validate_acceptable_collateral( *acceptable_collateral ); // Note: check base asset ID in evaluator\n   }\n   if( acceptable_borrowers.valid() )\n   {\n      updating_something = true;\n      validate_acceptable_borrowers( *acceptable_borrowers );\n   }\n\n   FC_ASSERT( updating_something,\n              \"Should change something - at least one of the optional data fields should be present\" );\n}\n\nshare_type credit_offer_update_operation::calculate_fee( const fee_params_t& schedule )const\n{\n   share_type core_fee_required = schedule.fee;\n   core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte );\n   return core_fee_required;\n}\n\nvoid credit_offer_accept_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( borrow_amount.amount > 0, \"Amount to borrow should be positive\" );\n   FC_ASSERT( collateral.amount > 0, \"Collateral amount should be positive\" );\n   if( extensions.value.auto_repay.valid() )\n   {\n      constexpr auto cdar_count = static_cast<uint8_t>( credit_deal_auto_repayment_type::CDAR_TYPE_COUNT );\n      FC_ASSERT( *extensions.value.auto_repay < cdar_count,\n                 \"auto_repay should be less than ${c}\", (\"c\",cdar_count) );\n   }\n}\n\nvoid credit_deal_repay_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( repay_amount.amount > 0, \"Amount to repay should be positive\" );\n   FC_ASSERT( credit_fee.amount >= 0, \"Credit fee should not be negative\" );\n   FC_ASSERT( repay_amount.asset_id == credit_fee.asset_id,\n             \"Asset type of repay amount and credit fee should be the same\" );\n}\n\nvoid credit_deal_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n\n   constexpr auto cdar_count = static_cast<uint8_t>( credit_deal_auto_repayment_type::CDAR_TYPE_COUNT );\n   FC_ASSERT( auto_repay < cdar_count,\n              \"auto_repay should be less than ${c}\", (\"c\",cdar_count) );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation::fee_params_t )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::ext )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_expired_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation )\n"
  },
  {
    "path": "libraries/protocol/custom.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/custom.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid custom_operation::validate()const\n{\n   FC_ASSERT( fee.amount > 0 );\n}\nshare_type custom_operation::calculate_fee(const fee_params_t& k)const\n{\n   return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );\n}\n\n} }\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation )\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/BOOST_LICENSE_1_0.txt",
    "content": "Boost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/create_predicate_fwd.hxx",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* This file contains forward declarations for externalized specializations of the create_predicate_function\n * template. Generate using the following shell code, and paste below.\n\nFWD_FIELD_TYPES=\"share_type asset_id_type flat_set<asset_id_type> asset price\"\nFWD_FIELD_TYPES=\"$FWD_FIELD_TYPES string std::vector<char> time_point_sec\"\nFWD_FIELD_TYPES=\"$FWD_FIELD_TYPES account_id_type flat_set<account_id_type> public_key_type authority\"\nFWD_FIELD_TYPES=\"$FWD_FIELD_TYPES optional<authority>\"\nFWD_FIELD_TYPES=\"$FWD_FIELD_TYPES bool uint8_t uint16_t uint32_t unsigned_int extensions_type\"\n\nfor T in $FWD_FIELD_TYPES; do\n    echo \"extern template\"\n    echo \"object_restriction_predicate<$T> create_predicate_function( \"\n    echo \"    restriction_function func, restriction_argument arg );\"\ndone\n * ---------------- CUT ---------------- */\n\nextern template\nobject_restriction_predicate<share_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<asset_id_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<flat_set<asset_id_type>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<asset> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<price> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<string> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<std::vector<char>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<time_point_sec> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<account_id_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<flat_set<account_id_type>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<public_key_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<authority> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<optional<authority>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<bool> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<uint8_t> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<uint16_t> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<uint32_t> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<unsigned_int> create_predicate_function(\n    restriction_function func, restriction_argument arg );\nextern template\nobject_restriction_predicate<extensions_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/create_predicate_fwd_1.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n\n/* This file contains explicit specializations of the create_predicate_function template.\n * Generate using the following shell code, and paste below.\n\nFWD_FIELD_TYPES=\"share_type asset_id_type flat_set<asset_id_type> asset price\"\nFWD_FIELD_TYPES=\"$FWD_FIELD_TYPES string std::vector<char> time_point_sec\"\n\nfor T in $FWD_FIELD_TYPES; do\n    echo \"template\"\n    echo \"object_restriction_predicate<$T> create_predicate_function( \"\n    echo \"    restriction_function func, restriction_argument arg );\"\ndone\n */\n\nnamespace graphene { namespace protocol {\n\ntemplate\nobject_restriction_predicate<share_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<asset_id_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<flat_set<asset_id_type>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<asset> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<price> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<string> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<std::vector<char>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<time_point_sec> create_predicate_function(\n    restriction_function func, restriction_argument arg );\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/create_predicate_fwd_2.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n\n/* This file contains explicit specializations of the create_predicate_function template.\n * Generate using the following shell code, and paste below.\n\nFWD_FIELD_TYPES=\"account_id_type flat_set<account_id_type> public_key_type authority optional<authority>\"\n\nfor T in $FWD_FIELD_TYPES; do\n    echo \"template\"\n    echo \"object_restriction_predicate<$T> create_predicate_function( \"\n    echo \"    restriction_function func, restriction_argument arg );\"\ndone\n */\n\nnamespace graphene { namespace protocol {\n\ntemplate\nobject_restriction_predicate<account_id_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<flat_set<account_id_type>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<public_key_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<authority> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<optional<authority>> create_predicate_function(\n    restriction_function func, restriction_argument arg );\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/create_predicate_fwd_3.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n\n/* This file contains explicit specializations of the create_predicate_function template.\n * Generate using the following shell code, and paste below.\n\nFWD_FIELD_TYPES=\"bool uint8_t uint16_t uint32_t unsigned_int extensions_type\"\n\nfor T in $FWD_FIELD_TYPES; do\n    echo \"template\"\n    echo \"object_restriction_predicate<$T> create_predicate_function( \"\n    echo \"    restriction_function func, restriction_argument arg );\"\ndone\n */\n\nnamespace graphene { namespace protocol {\n\ntemplate\nobject_restriction_predicate<bool> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<uint8_t> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<uint16_t> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<uint32_t> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<unsigned_int> create_predicate_function(\n    restriction_function func, restriction_argument arg );\ntemplate\nobject_restriction_predicate<extensions_type> create_predicate_function(\n    restriction_function func, restriction_argument arg );\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_1.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_1(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_1::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_10.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_10(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_10::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_11.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_11(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_11::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_2.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\n\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_2(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_2::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_3.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\n\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_3(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_3::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_5.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\n\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_5(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_5::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_6.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\n\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_6(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_6::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/list_9.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\n\nusing result_type = object_restriction_predicate<operation>;\n\nresult_type get_restriction_pred_list_9(size_t idx, vector<restriction> rs) {\n   return typelist::runtime::dispatch(operation_list_9::list(), idx, [&rs] (auto t) {\n      using Op = typename decltype(t)::type;\n      result_type to_return = [p=restrictions_to_predicate<Op>(std::move(rs), true)] (const operation& op) {\n         FC_ASSERT(op.which() == operation::tag<Op>::value,\n                   \"Supplied operation is incorrect type for restriction predicate\");\n         return p(op.get<Op>());\n      };\n      return to_return;\n   });\n}\n} }\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/restriction_predicate.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include \"restriction_predicate.hxx\"\n#include \"sliced_lists.hxx\"\n\nnamespace graphene { namespace protocol {\n\nrestriction_predicate_function get_restriction_predicate(vector<restriction> rs, operation::tag_type op_type) {\n   auto f = typelist::runtime::dispatch(operation::list(), op_type, [&rs](auto t) -> restriction_predicate_function {\n      using Op = typename decltype(t)::type;\n      if (typelist::contains<operation_list_1::list, Op>())\n         return get_restriction_pred_list_1(typelist::index_of<operation_list_1::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_2::list, Op>())\n         return get_restriction_pred_list_2(typelist::index_of<operation_list_2::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_3::list, Op>())\n         return get_restriction_pred_list_3(typelist::index_of<operation_list_3::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_5::list, Op>())\n         return get_restriction_pred_list_5(typelist::index_of<operation_list_5::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_6::list, Op>())\n         return get_restriction_pred_list_6(typelist::index_of<operation_list_6::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_9::list, Op>())\n         return get_restriction_pred_list_9(typelist::index_of<operation_list_9::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_10::list, Op>())\n         return get_restriction_pred_list_10(typelist::index_of<operation_list_10::list, Op>(), std::move(rs));\n      if (typelist::contains<operation_list_11::list, Op>())\n         return get_restriction_pred_list_11(typelist::index_of<operation_list_11::list, Op>(), std::move(rs));\n      if (typelist::contains<unsupported_operations_list::list, Op>())\n         FC_THROW_EXCEPTION( fc::assert_exception, \"Unsupported operation detected!\" );\n\n      // Compile time check that we'll never get to the exception below\n      static_assert(typelist::contains<typelist::concat<operation_list_1::list, operation_list_2::list,\n                                                        operation_list_3::list,\n                                                        operation_list_5::list, operation_list_6::list,\n                                                        operation_list_9::list, operation_list_10::list,\n                                                        operation_list_11::list,\n                                                        unsupported_operations_list::list>,\n                                       Op>(), \"\");\n      FC_THROW_EXCEPTION(fc::assert_exception,\n                         \"LOGIC ERROR: Operation type not handled by custom authorities implementation. \"\n                         \"Please report this error.\");\n   });\n\n   // Wrap function in a layer that, if the function returns an error, reverses the order of the rejection path. This\n   // is because the order the path is created in, from the top of the call stack to the bottom, is counterintuitive.\n   return [f=std::move(f)](const operation& op) { return f(op).reverse_path(); };\n}\n\npredicate_result& predicate_result::reverse_path() {\n   if (success)\n      return *this;\n   const auto reverse_subpaths = [](rejection_indicator& indicator) {\n      if (indicator.is_type<vector<predicate_result>>()) {\n         auto& results = indicator.get<vector<predicate_result>>();\n         for (predicate_result& result : results)\n            result.reverse_path();\n      }\n   };\n   std::reverse(rejection_path.begin(), rejection_path.end());\n   std::for_each(rejection_path.begin(), rejection_path.end(), reverse_subpaths);\n   return *this;\n}\n\n// These are some compile-time tests of the metafunctions and predicate type analysis. They are turned off to make\n// building faster; they only need to be enabled when making changes in restriction_predicate.hxx\n#if false\nstatic_assert(!is_container<int>, \"\");\nstatic_assert(is_container<vector<int>>, \"\");\nstatic_assert(is_container<flat_set<int>>, \"\");\nstatic_assert(is_container<string>, \"\");\nstatic_assert(is_flat_set<flat_set<int>>, \"\");\nstatic_assert(!is_flat_set<vector<int>>, \"\");\n\nstatic_assert(predicate_eq<int, int64_t>()(10, 20) == false, \"\");\nstatic_assert(predicate_eq<int, int64_t>()(10, 5) == false, \"\");\nstatic_assert(predicate_eq<int, int64_t>()(10, 10) == true, \"\");\n\nstatic_assert(predicate_eq<void_t, void_t>::valid == false, \"\");\nstatic_assert(predicate_eq<int, void_t>::valid == false, \"\");\nstatic_assert(predicate_eq<void_t, int64_t>::valid == false, \"\");\nstatic_assert(predicate_eq<int, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<long, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<vector<bool>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<flat_set<char>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<short, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<bool, int64_t>::valid == false, \"\");\nstatic_assert(predicate_eq<int, bool>::valid == false, \"\");\nstatic_assert(predicate_eq<fc::optional<int>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<fc::optional<long>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_eq<fc::optional<long>, void_t>::valid == true, \"\");\nstatic_assert(predicate_eq<flat_set<bool>, flat_set<bool>>::valid == true, \"\");\nstatic_assert(predicate_eq<flat_set<bool>, string>::valid == false, \"\");\nstatic_assert(predicate_eq<string, string>::valid == true, \"\");\nstatic_assert(predicate_ne<int, void_t>::valid == false, \"\");\nstatic_assert(predicate_ne<void_t, int64_t>::valid == false, \"\");\nstatic_assert(predicate_ne<int, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<long, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<vector<bool>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<flat_set<char>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<short, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<bool, int64_t>::valid == false, \"\");\nstatic_assert(predicate_ne<int, bool>::valid == false, \"\");\nstatic_assert(predicate_ne<fc::optional<int>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<fc::optional<long>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_ne<fc::optional<long>, void_t>::valid == true, \"\");\nstatic_assert(predicate_ne<string, string>::valid == true, \"\");\n\nstatic_assert(predicate_compare<int, int64_t>()(20, 10) == 1, \"\");\nstatic_assert(predicate_compare<int, int64_t>()(5, 10) == -1, \"\");\nstatic_assert(predicate_compare<int, int64_t>()(10, 10) == 0, \"\");\nstatic_assert(predicate_lt<int, int64_t>()(20, 10) == false, \"\");\nstatic_assert(predicate_lt<int, int64_t>()(5, 10) == true, \"\");\nstatic_assert(predicate_lt<int, int64_t>()(10, 10) == false, \"\");\nstatic_assert(predicate_le<int, int64_t>()(20, 10) == false, \"\");\nstatic_assert(predicate_le<int, int64_t>()(5, 10) == true, \"\");\nstatic_assert(predicate_le<int, int64_t>()(10, 10) == true, \"\");\nstatic_assert(predicate_gt<int, int64_t>()(20, 10) == true, \"\");\nstatic_assert(predicate_gt<int, int64_t>()(5, 10) == false, \"\");\nstatic_assert(predicate_gt<int, int64_t>()(10, 10) == false, \"\");\nstatic_assert(predicate_ge<int, int64_t>()(20, 10) == true, \"\");\nstatic_assert(predicate_ge<int, int64_t>()(5, 10) == false, \"\");\nstatic_assert(predicate_ge<int, int64_t>()(10, 10) == true, \"\");\n\nstatic_assert(predicate_compare<int, int64_t>::valid == true, \"\");\nstatic_assert(predicate_compare<short, int64_t>::valid == true, \"\");\nstatic_assert(predicate_compare<string, string>::valid == true, \"\");\nstatic_assert(predicate_compare<vector<int>, int64_t>::valid == false, \"\");\nstatic_assert(predicate_compare<fc::optional<int>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_compare<fc::optional<short>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_compare<fc::optional<string>, string>::valid == true, \"\");\nstatic_assert(predicate_lt<int, int64_t>::valid == true, \"\");\nstatic_assert(predicate_lt<short, int64_t>::valid == true, \"\");\nstatic_assert(predicate_lt<string, string>::valid == true, \"\");\nstatic_assert(predicate_lt<vector<int>, int64_t>::valid == false, \"\");\nstatic_assert(predicate_lt<fc::optional<int>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_lt<fc::optional<short>, int64_t>::valid == true, \"\");\nstatic_assert(predicate_lt<fc::optional<string>, string>::valid == true, \"\");\n\nstatic_assert(predicate_in<string, string>::valid == false, \"\");\nstatic_assert(predicate_in<int, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_in<string, flat_set<string>>::valid == true, \"\");\nstatic_assert(predicate_in<flat_set<string>, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_in<fc::optional<string>, flat_set<string>>::valid == true, \"\");\nstatic_assert(predicate_not_in<string, string>::valid == false, \"\");\nstatic_assert(predicate_not_in<int, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_not_in<string, flat_set<string>>::valid == true, \"\");\nstatic_assert(predicate_not_in<flat_set<string>, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_not_in<fc::optional<string>, flat_set<string>>::valid == true, \"\");\n\nstatic_assert(predicate_has_all<string, string>::valid == false, \"\");\nstatic_assert(predicate_has_all<int, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_has_all<string, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_has_all<flat_set<string>, flat_set<string>>::valid == true, \"\");\nstatic_assert(predicate_has_all<fc::optional<string>, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_has_all<fc::optional<flat_set<string>>, flat_set<string>>::valid == true, \"\");\nstatic_assert(predicate_has_none<string, string>::valid == false, \"\");\nstatic_assert(predicate_has_none<int, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_has_none<string, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_has_none<flat_set<string>, flat_set<string>>::valid == true, \"\");\nstatic_assert(predicate_has_none<fc::optional<string>, flat_set<string>>::valid == false, \"\");\nstatic_assert(predicate_has_none<fc::optional<flat_set<string>>, flat_set<string>>::valid == true, \"\");\n\n#endif\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/restriction_predicate.hxx",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include <fc/exception/exception.hpp>\n\n#include \"safe_compare.hpp\"\n\nnamespace graphene { namespace protocol {\nnamespace typelist = fc::typelist;\nusing std::declval;\nusing std::size_t;\nusing restriction_function = restriction::function_type;\nusing restriction_argument = restriction::argument_type;\n\n// Make our own std::void_t since the real one isn't available in C++14\ntemplate<typename...> using make_void = void;\n\n// Metafunction to check if type is some instantiation of fc::safe\ntemplate<typename> constexpr static bool is_safe = false;\ntemplate<typename I> constexpr static bool is_safe<fc::safe<I>> = true;\n\n// Metafunction to check if type is a flat_set of any element type\ntemplate<typename> struct is_flat_set_impl : std::false_type {};\ntemplate<typename T> struct is_flat_set_impl<flat_set<T>> : std::true_type {};\ntemplate<typename T> constexpr static bool is_flat_set = is_flat_set_impl<T>::value;\n\n// We use our own is_integral which does not consider bools integral (to disallow comparison between bool and ints)\ntemplate<typename T> constexpr static bool is_integral = !std::is_same<T, bool>::value &&\n                                                         !std::is_same<T, safe<bool>>::value &&\n                                                         (is_safe<T> || std::is_integral<T>::value);\n\n// Metafunction to check if two types are comparable, which means not void_t, and either the same or both integral\ntemplate<typename T, typename U>\nconstexpr static bool comparable_types = !std::is_same<T, void_t>::value &&\n                                         (std::is_same<T, U>::value || (is_integral<T> && is_integral<U>));\n\n// Metafunction to check if type is a container\ntemplate<typename, typename = void>\nstruct is_container_impl : std::false_type {};\ntemplate<typename T>\nstruct is_container_impl<T, make_void<typename T::value_type, decltype(declval<T>().size())>> : std::true_type {};\ntemplate<typename T> constexpr static bool is_container = is_container_impl<T>::value;\n\n// Type alias for a predicate on a particular field type\ntemplate<typename Field>\nusing object_restriction_predicate = std::function<predicate_result(const Field&)>;\n\n// Get the actual number when type might be a safe<I>\ntemplate<typename I, typename=std::enable_if_t<std::is_integral<I>::value>>\nconst auto& to_num(const I& i) { return i; }\ntemplate<typename I>\nconst auto& to_num(const fc::safe<I>& i) { return i.value; }\ninline auto to_num(const fc::time_point_sec& t) { return t.sec_since_epoch(); }\n\nnamespace safenum = boost::safe_numerics::safe_compare;\n\n/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// *** Restriction Predicate Logic ***\n//\n// This file implements the core logic of Custom Active Authorities. A CAA is an authority which is permitted by an\n// account to execute a particular authority on that account's behalf, with some restrictions on the content of that\n// operation. This file implements the logic to validate those restrictions, and create a predicate function which\n// takes a particular operation and determines whether it complies with the restrictions or not.\n//\n// The restrictions are a recursive structure, which applies to a particular operation struct, but may recurse to\n// specify restrictions on fields or subfields of that struct. This file explores the restriction structure in tandem\n// with the operation struct to verify that all of the restrictions are valid and to produce a predicate function.\n// Note that this file operates primarily on restriction data, but only operation *types*, meaning the actual\n// operation value does not appear until the predicate returned by this file is run.\n//\n// As a result, this file is very template heavy, and does a good deal of type manipulation. Its contents are\n// organized as a series of layers, which recursively examine the restrictions and types they apply to, and finally,\n// once all the types have been resolved, a predicate function is created which evaluates the restrictions on an\n// operation.\n//\n// To give an overview of the logic, the layers stack up like so, from beginning (bottom of file) to end:\n//  - restrictions_to_predicate<Object>() -- takes a vector<restriction> and creates a predicate for each of them,\n//    but returns a single predicate that returns true only if all sub-predicates return true\n//    - create_field_predicate<Object>() -- Resolves which field of Object the restriction is referencing by indexing\n//      into the object's reflected fields with the predicate's member_index\n//    - create_logical_or_predicate<Object>() -- If the predicate is a logical OR function, the predicate does not\n//      specify a field to examine; rather, the predicates in its branches do. Thus this function recurses into\n//      restrictions_to_predicate for each branch of the OR, and combines the resulting predicates in a predicate\n//      which returns true if any branch of the OR passes\n//  - create_predicate_function<Field>() -- switches on restriction type to determine which predicate template to use\n//    going forward\n//    - make_predicate<Predicate, Field, ArgVariant> -- Determines what type the restriction argument is and creates\n//      a predicate functor for that type\n//    - attribute_assertion<Field> -- If the restriction is an attribute assertion, instead of using make_predicate\n//      to create a predicate function, we first recurse into restrictions_to_predicate with Field as the Object\n//    - variant_assertion<Field> -- If the restriction is a variant assertion, instead of using make_predicate, we\n//      recurse into restrictions_to_predicate with the variant value as the Object\n//  - embed_argument<Field, Predicate, Argument>() -- Embeds the argument into the predicate if it is a valid type\n//    for the predicate, and throws otherwise.\n//  - predicate_xyz<Argument> -- These are functors implementing the various predicate function types\n/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// These typelists contain the argument types legal for various function types:\n\n// Valid for magnitude comparisons and equality comparisons\nusing comparable_types_list = typelist::list<int64_t, string, time_point_sec, account_id_type, asset_id_type,\n                                             force_settlement_id_type, committee_member_id_type, witness_id_type,\n                                             limit_order_id_type, call_order_id_type, custom_id_type,\n                                             proposal_id_type, withdraw_permission_id_type,\n                                             vesting_balance_id_type, worker_id_type, balance_id_type>;\n// Valid for list functions (in, not_in, has_all, has_none)\nstruct make_flat_set { template<typename T> struct transform { using type = flat_set<T>; }; };\nusing list_types_list = typelist::transform<typelist::concat<typelist::list<bool, public_key_type, fc::sha256>,\n                                                             comparable_types_list>,\n                                            make_flat_set>;\n// Valid for equality comparisons but not necessarily magnitude comparisons\nusing equality_types_list = typename typelist::concat<typelist::list<void_t, bool, public_key_type, fc::sha256>,\n                                                      comparable_types_list, list_types_list>;\n// Valid for attritube assertions\nusing attr_types_list = typelist::list<vector<restriction>>;\n// Valid for logical or assertions\nusing or_types_list = typelist::list<vector<vector<restriction>>>;\n\n//////////////////////////////////////////////// PREDICATE FUNCTORS ////////////////////////////////////////////////\n// An invalid predicate which throws upon construction. Inherited by other predicates when arg types are incompatible\ntemplate<typename A, typename B>\nstruct predicate_invalid {\n   constexpr static bool valid = false;\n   predicate_invalid() { FC_THROW_EXCEPTION(fc::assert_exception, \"Invalid types for predicate\"); }\n   bool operator()(const A&, const B&) const { return false; }\n};\n// Equality comparison\ntemplate<typename A, typename B, typename = void> struct predicate_eq : predicate_invalid<A, B> {};\ntemplate<typename Field, typename Argument>\nstruct predicate_eq<Field, Argument, std::enable_if_t<std::is_same<Field, Argument>::value>> {\n   // Simple comparison, same type\n   constexpr static bool valid = true;\n   constexpr bool operator()(const Field& f, const Argument& a) const { return f == a; }\n};\ntemplate<typename Field, typename Argument>\nstruct predicate_eq<Field, Argument, std::enable_if_t<is_integral<Field> && is_integral<Argument> &&\n                                                      !std::is_same<Field, Argument>::value>> {\n   // Simple comparison, integral types\n   constexpr static bool valid = true;\n   constexpr bool operator()(const Field& f, const Argument& a) const { return safenum::equal(to_num(f), to_num(a)); }\n};\ntemplate<typename Field, typename Argument>\nstruct predicate_eq<Field, Argument, std::enable_if_t<is_container<Field> && is_integral<Argument>>> {\n   // Compare container size against int\n   constexpr static bool valid = true;\n   bool operator()(const Field& f, const Argument& a) const { return safenum::equal(f.size(), to_num(a)); }\n};\ntemplate<typename Field, typename Argument>\nstruct predicate_eq<fc::optional<Field>, Argument, std::enable_if_t<comparable_types<Field, Argument>>>\n   : predicate_eq<Field, Argument> {\n   // Compare optional value against comparable type\n   using base = predicate_eq<Field, Argument>;\n   bool operator()(const fc::optional<Field>& f, const Argument& a) const {\n      if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n      return (*this)(*f, a);\n   }\n};\ntemplate<typename Field>\nstruct predicate_eq<fc::optional<Field>, void_t, void> {\n   // Compare optional value against void_t (checks that optional is null)\n   constexpr static bool valid = true;\n   bool operator()(const fc::optional<Field>& f, const void_t&) const { return !f.valid(); }\n};\n// Not-equal is just an equality comparison wrapped in a negator\ntemplate<typename Field, typename Argument> struct predicate_ne : predicate_eq<Field, Argument> {\n   using equal = predicate_eq<Field, Argument>;\n   bool operator()(const Field& f, const Argument& a) const { return !equal::operator()(f, a); }\n};\n\n// Shared implementation for all inequality comparisons\ntemplate<typename A, typename B, typename = void> struct predicate_compare : predicate_invalid<A, B> {};\ntemplate<typename Field, typename Argument>\nstruct predicate_compare<Field, Argument, std::enable_if_t<std::is_same<Field, Argument>::value>> {\n   // Simple comparison, same types\n   constexpr static bool valid = true;\n   constexpr int8_t operator()(const Field& f, const Argument& a) const {\n      return f<a? -1 : (f>a? 1 : 0);\n   }\n};\ntemplate<typename Field, typename Argument>\nstruct predicate_compare<Field, Argument, std::enable_if_t<is_integral<Field> && is_integral<Argument> &&\n                                                           !std::is_same<Field, Argument>::value>> {\n   // Simple comparison, integral types\n   constexpr static bool valid = true;\n   constexpr int8_t operator()(const Field& f, const Argument& a) const {\n      auto nf = to_num(f);\n      auto na = to_num(a);\n      return safenum::less_than(nf, na)? -1 : (safenum::greater_than(nf, na)? 1 : 0);\n   }\n};\ntemplate<typename Field, typename Argument>\nstruct predicate_compare<fc::optional<Field>, Argument, void> : predicate_compare<Field, Argument> {\n   // Compare optional value against comparable type\n   constexpr static bool valid = true;\n   constexpr int8_t operator()(const fc::optional<Field>& f, const Argument& a) const {\n      if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n       return (*this)(*f, a);\n   }\n};\n// The actual inequality predicates\ntemplate<typename Field, typename Argument> struct predicate_lt : predicate_compare<Field, Argument> {\n   using base = predicate_compare<Field, Argument>;\n   constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) < 0; }\n};\ntemplate<typename Field, typename Argument> struct predicate_le : predicate_compare<Field, Argument> {\n   using base = predicate_compare<Field, Argument>;\n   constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) <= 0; }\n};\ntemplate<typename Field, typename Argument> struct predicate_gt : predicate_compare<Field, Argument> {\n   using base = predicate_compare<Field, Argument>;\n   constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) > 0; }\n};\ntemplate<typename Field, typename Argument> struct predicate_ge : predicate_compare<Field, Argument> {\n   using base = predicate_compare<Field, Argument>;\n   constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) >= 0; }\n};\n\n// Field-in-list predicate\ntemplate<typename F, typename C, typename = void> struct predicate_in : predicate_invalid<F, C> {};\ntemplate<typename Field, typename Element>\nstruct predicate_in<Field, flat_set<Element>, std::enable_if_t<comparable_types<Field, Element> && !is_safe<Field>>> {\n   // Simple inclusion check\n   constexpr static bool valid = true;\n   bool operator()(const Field& f, const flat_set<Element>& c) const { return c.count(f) != 0; }\n};\ntemplate<typename Field, typename Element>\nstruct predicate_in<fc::safe<Field>, flat_set<Element>, std::enable_if_t<comparable_types<Field, Element>>> {\n   // Check for safe value\n   constexpr static bool valid = true;\n   bool operator()(const fc::safe<Field>& f, const flat_set<Element>& c) const { return c.count(f.value) != 0; }\n};\ntemplate<typename Field, typename Element>\nstruct predicate_in<fc::optional<Field>, flat_set<Element>, std::enable_if_t<comparable_types<Field, Element>>> {\n   // Check for optional value\n   constexpr static bool valid = true;\n   bool operator()(const fc::optional<Field>& f, const flat_set<Element>& c) const {\n      if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n       return c.count(*f) != 0;\n   }\n};\ntemplate<typename Container, typename Element>\nstruct predicate_in<Container, flat_set<Element>,\n                    std::enable_if_t<is_container<Container> &&\n                                     comparable_types<typename Container::value_type, Element>>> {\n   // Check all values in container are in argument\n   constexpr static bool valid = true;\n   // Unsorted container\n   template<typename C = Container, std::enable_if_t<!is_flat_set<C>, bool> = true>\n   bool operator()(const Container& c, const flat_set<Element>& a) const {\n      return std::all_of(c.begin(), c.end(), [&a](const auto& ce) { return a.count(ce) > 0; });\n   }\n   // Sorted container\n   template<typename C = Container, std::enable_if_t<is_flat_set<C>, bool> = true>\n   bool operator()(const Container& c, const flat_set<Element>& a) const {\n      return std::includes(a.begin(), a.end(), c.begin(), c.end());\n   }\n};\n// Field-not-in-list is just field-in-list wrapped in a negator\ntemplate<typename Field, typename Container, typename=void> struct predicate_not_in : predicate_in<Field, Container> {\n   using base = predicate_in<Field, Container>;\n   bool operator()(const Field& f, const Container& c) const { return !base::operator()(f, c); }\n};\n// Container-field-not-in-list is not a simple negation of predicate_in, specialize here\ntemplate<typename Container, typename Element>\nstruct predicate_not_in<Container, flat_set<Element>,\n                        std::enable_if_t<is_container<Container> &&\n                                         comparable_types<typename Container::value_type, Element>>> {\n   constexpr static bool valid = true;\n   // Unsorted container\n   template<typename C = Container, std::enable_if_t<!is_flat_set<C>, bool> = true>\n   bool operator()(const Container& c, const flat_set<Element>& a) const {\n      return std::none_of(c.begin(), c.end(), [&a](const auto& ce) { return a.count(ce) > 0; });\n   }\n   // Sorted container\n   template<typename C = Container, std::enable_if_t<is_flat_set<C>, bool> = true>\n   bool operator()(const Container& c, const flat_set<Element>& a) const {\n      flat_set<typename Container::value_type> intersection;\n      std::set_intersection(c.begin(), c.end(), a.begin(), a.end(),\n                            std::inserter(intersection, intersection.begin()));\n      return intersection.empty();\n   }\n};\n\n// List-contains-list predicate\ntemplate<typename C1, typename C2, typename = void> struct predicate_has_all : predicate_invalid<C1, C2> {};\ntemplate<typename FieldElement, typename ArgumentElement>\nstruct predicate_has_all<flat_set<FieldElement>, flat_set<ArgumentElement>,\n                         std::enable_if_t<comparable_types<FieldElement, ArgumentElement>>> {\n   // Field is already flat_set\n   constexpr static bool valid = true;\n   bool operator()(const flat_set<FieldElement>& f, const flat_set<ArgumentElement>& a) const {\n      if (f.size() < a.size()) return false;\n      return std::includes(f.begin(), f.end(), a.begin(), a.end());\n   }\n};\ntemplate<typename FieldContainer, typename ArgumentElement>\nstruct predicate_has_all<FieldContainer, flat_set<ArgumentElement>,\n                         std::enable_if_t<is_container<FieldContainer> && !is_flat_set<FieldContainer> &&\n                                          comparable_types<typename FieldContainer::value_type, ArgumentElement>>> {\n   // Field is other container; convert to flat_set\n   constexpr static bool valid = true;\n   bool operator()(const FieldContainer& f, const flat_set<ArgumentElement>& a) const {\n      if (f.size() < a.size()) return false;\n      std::set<typename FieldContainer::value_type> fs(f.begin(), f.end());\n      return std::includes(fs.begin(), fs.end(), a.begin(), a.end());\n   }\n};\ntemplate<typename OptionalType, typename Argument>\nstruct predicate_has_all<fc::optional<OptionalType>, Argument, void> : predicate_has_all<OptionalType, Argument> {\n   // Field is optional container\n   bool operator()(const fc::optional<OptionalType>& f, const Argument& a) const {\n      if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n      return (*this)(*f, a);\n   }\n};\n\n// List contains none of list predicate\ntemplate<typename C1, typename C2, typename = void> struct predicate_has_none : predicate_invalid<C1, C2> {};\ntemplate<typename FieldElement, typename ArgumentElement>\nstruct predicate_has_none<flat_set<FieldElement>, flat_set<ArgumentElement>,\n                          std::enable_if_t<comparable_types<FieldElement, ArgumentElement>>> {\n   // Field is already flat_set\n   constexpr static bool valid = true;\n   bool operator()(const flat_set<FieldElement>& f, const flat_set<ArgumentElement>& a) const {\n      flat_set<FieldElement> intersection;\n      std::set_intersection(f.begin(), f.end(), a.begin(), a.end(),\n                            std::inserter(intersection, intersection.begin()));\n      return intersection.empty();\n   }\n};\ntemplate<typename FieldContainer, typename ArgumentElement>\nstruct predicate_has_none<FieldContainer, flat_set<ArgumentElement>,\n                          std::enable_if_t<is_container<FieldContainer> && !is_flat_set<FieldContainer> &&\n                                           comparable_types<typename FieldContainer::value_type, ArgumentElement>>> {\n   // Field is other container\n   constexpr static bool valid = true;\n   bool operator()(const FieldContainer& f, const flat_set<ArgumentElement>& a) const {\n      return !std::any_of(f.begin(), f.end(), [&a](const auto& fe) { return a.count(fe) > 0; });\n   }\n};\ntemplate<typename OptionalType, typename Argument>\nstruct predicate_has_none<fc::optional<OptionalType>, Argument, void> : predicate_has_all<OptionalType, Argument> {\n   // Field is optional container\n   bool operator()(const fc::optional<OptionalType>& f, const Argument& a) const {\n      if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n      return (*this)(*f, a);\n   }\n};\n////////////////////////////////////////////// END PREDICATE FUNCTORS //////////////////////////////////////////////\n\n// Forward declaration of restrictions_to_predicate, because attribute assertions and logical ORs recurse into it\ntemplate<typename Object> object_restriction_predicate<Object> restrictions_to_predicate(vector<restriction>, bool);\n\ntemplate<typename Field>\nstruct attribute_assertion {\n   static object_restriction_predicate<Field> create(vector<restriction>&& rs) {\n      return restrictions_to_predicate<Field>(std::move(rs), false);\n   }\n};\ntemplate<typename Field>\nstruct attribute_assertion<fc::optional<Field>> {\n   static object_restriction_predicate<fc::optional<Field>> create(vector<restriction>&& rs) {\n      return [p=restrictions_to_predicate<Field>(std::move(rs), false)](const fc::optional<Field>& f) {\n         if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n         return p(*f);\n      };\n   }\n};\ntemplate<typename Extension>\nstruct attribute_assertion<extension<Extension>> {\n   static object_restriction_predicate<extension<Extension>> create(vector<restriction>&& rs) {\n      return [p=restrictions_to_predicate<Extension>(std::move(rs), false)](const extension<Extension>& x) {\n         return p(x.value);\n      };\n   }\n};\n\ntemplate<typename Variant>\nstruct variant_assertion {\n   static object_restriction_predicate<Variant> create(restriction::variant_assert_argument_type&&) {\n      FC_THROW_EXCEPTION(fc::assert_exception, \"Invalid variant assertion on non-variant field\",\n                         (\"Field\", fc::get_typename<Variant>::name()));\n   }\n};\ntemplate<typename... Types>\nstruct variant_assertion<static_variant<Types...>> {\n   using Variant = static_variant<Types...>;\n\n   template<typename Value>\n   static auto make_predicate(vector<restriction>&& rs) {\n      return [p=restrictions_to_predicate<Value>(std::move(rs), true)](const Variant& v) {\n         if (v.which() == Variant::template tag<Value>::value)\n            return p(v.template get<Value>());\n         return predicate_result::Rejection(predicate_result::incorrect_variant_type);\n      };\n   }\n   static object_restriction_predicate<Variant> create(restriction::variant_assert_argument_type&& arg) {\n      return typelist::runtime::dispatch(typelist::list<Types...>(), arg.first,\n                                         [&arg](auto t) -> object_restriction_predicate<Variant> {\n         using Value = typename decltype(t)::type;\n         return variant_assertion::make_predicate<Value>(std::move(arg.second));\n      });\n   }\n};\ntemplate<typename... Types>\nstruct variant_assertion<fc::optional<static_variant<Types...>>> {\n   using Variant = static_variant<Types...>;\n   using Optional = fc::optional<Variant>;\n   static object_restriction_predicate<Optional> create(restriction::variant_assert_argument_type&& arg) {\n      return typelist::runtime::dispatch(typelist::list<Types...>(), arg.first,\n                                         [&arg](auto t) -> object_restriction_predicate<Optional> {\n         using Value = typename decltype(t)::type;\n         auto pred = variant_assertion<Variant>::template make_predicate<Value>(std::move(arg.second));\n         return [p=std::move(pred)](const Optional& opt) {\n            if (!opt.valid()) return predicate_result::Rejection(predicate_result::null_optional);\n            return p(*opt);\n         };\n      });\n   }\n};\n\n// Embed the argument into the predicate functor\ntemplate<typename F, typename P, typename A, typename = std::enable_if_t<P::valid>>\nobject_restriction_predicate<F> embed_argument(P p, A a, short) {\n   return [p=std::move(p), a=std::move(a)](const F& f) {\n      if (p(f, a)) return predicate_result::Success();\n      return predicate_result::Rejection(predicate_result::predicate_was_false);\n   };\n}\ntemplate<typename F, typename P, typename A>\nobject_restriction_predicate<F> embed_argument(P, A, long) {\n   FC_THROW_EXCEPTION(fc::assert_exception, \"Invalid types for predicate\");\n}\n\n// Resolve the argument type and make a predicate for it\ntemplate<template<typename...> class Predicate, typename Field, typename ArgVariant>\nobject_restriction_predicate<Field> make_predicate(ArgVariant arg) {\n   return typelist::runtime::dispatch(typename ArgVariant::list(), arg.which(),\n                                      [&arg](auto t) mutable -> object_restriction_predicate<Field> {\n      using Arg = typename decltype(t)::type;\n      return embed_argument<Field>(Predicate<Field, Arg>(), std::move(arg.template get<Arg>()), short());\n   });\n}\n\ntemplate<typename Field>\nobject_restriction_predicate<Field> create_predicate_function(restriction_function func, restriction_argument arg) {\n   try {\n      switch(func) {\n      case restriction::func_eq:\n         return make_predicate<predicate_eq, Field>(static_variant<equality_types_list>::import_from(std::move(arg)));\n      case restriction::func_ne:\n         return make_predicate<predicate_ne, Field>(static_variant<equality_types_list>::import_from(std::move(arg)));\n      case restriction::func_lt:\n         return make_predicate<predicate_lt, Field>(static_variant<comparable_types_list>\n                                                    ::import_from(std::move(arg)));\n      case restriction::func_le:\n         return make_predicate<predicate_le, Field>(static_variant<comparable_types_list>\n                                                    ::import_from(std::move(arg)));\n      case restriction::func_gt:\n         return make_predicate<predicate_gt, Field>(static_variant<comparable_types_list>\n                                                    ::import_from(std::move(arg)));\n      case restriction::func_ge:\n         return make_predicate<predicate_ge, Field>(static_variant<comparable_types_list>\n                                                    ::import_from(std::move(arg)));\n      case restriction::func_in:\n         return make_predicate<predicate_in, Field>(static_variant<list_types_list>::import_from(std::move(arg)));\n      case restriction::func_not_in:\n         return make_predicate<predicate_not_in, Field>(static_variant<list_types_list>\n                                                        ::import_from(std::move(arg)));\n      case restriction::func_has_all:\n         return make_predicate<predicate_has_all, Field>(static_variant<list_types_list>\n                                                         ::import_from(std::move(arg)));\n      case restriction::func_has_none:\n         return make_predicate<predicate_has_none, Field>(static_variant<list_types_list>\n                                                          ::import_from(std::move(arg)));\n      case restriction::func_attr:\n         FC_ASSERT(arg.which() == restriction_argument::tag<vector<restriction>>::value,\n                   \"Argument type for attribute assertion must be restriction list\");\n         return attribute_assertion<Field>::create(std::move(arg.get<vector<restriction>>()));\n      case restriction::func_variant_assert:\n         FC_ASSERT(arg.which() == restriction_argument::tag<restriction::variant_assert_argument_type>::value,\n                   \"Argument type for attribute assertion must be pair of variant tag and restriction list\");\n         return variant_assertion<Field>::create(std::move(arg.get<restriction::variant_assert_argument_type>()));\n      default:\n          FC_THROW_EXCEPTION(fc::assert_exception, \"Invalid function type on restriction\");\n      }\n   } FC_CAPTURE_AND_RETHROW( (fc::get_typename<Field>::name())(func)(arg) )\n}\n\n#include \"create_predicate_fwd.hxx\"\n\n/**\n * @brief Create a predicate asserting on the field of the object a restriction is referencing\n *\n * @tparam Object The type the restriction restricts\n *\n * A restriction specifies requirements about a field of an object. This struct shifts the focus from the object type\n * the restriction references to the particular field type, creates a predicate on that field, and wraps that\n * predicate to accept the object type and invoke the inner predicate on the specified field.\n */\ntemplate<typename Object,\n         typename = std::enable_if_t<typelist::length<typename fc::reflector<Object>::native_members>() != 0>>\nobject_restriction_predicate<Object> create_field_predicate(restriction&& r, short) {\n   using member_list = typename fc::reflector<Object>::native_members;\n   FC_ASSERT( r.member_index < static_cast<uint64_t>(typelist::length<member_list>()),\n              \"Invalid member index ${I} for object ${O}\",\n              (\"I\", r.member_index)(\"O\", fc::get_typename<Object>::name()) );\n   auto predicator = [f=r.restriction_type, a=std::move(r.argument)](auto t) -> object_restriction_predicate<Object> {\n      using FieldReflection = typename decltype(t)::type;\n      using Field = typename FieldReflection::type;\n      auto p = create_predicate_function<Field>(static_cast<restriction_function>(f), std::move(a));\n      return [p=std::move(p)](const Object& o) { return p(FieldReflection::get(o)); };\n   };\n   return typelist::runtime::dispatch(member_list(), static_cast<size_t>(r.member_index.value), predicator);\n}\ntemplate<typename Object>\nobject_restriction_predicate<Object> create_field_predicate(restriction&&, long) {\n   FC_THROW_EXCEPTION(fc::assert_exception, \"Invalid restriction references member of non-object type: ${O}\",\n                      (\"O\", fc::get_typename<Object>::name()));\n}\n\ntemplate<typename Object>\nobject_restriction_predicate<Object> create_logical_or_predicate(vector<vector<restriction>> rs) {\n   FC_ASSERT(rs.size() > 1, \"Logical OR must have at least two branches\");\n   auto to_predicate = std::bind(restrictions_to_predicate<Object>, std::placeholders::_1, false);\n\n   vector<object_restriction_predicate<Object>> predicates;\n   std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()),\n                  std::back_inserter(predicates), to_predicate);\n\n   return [predicates=std::move(predicates)](const Object& obj) {\n      vector<predicate_result> rejections;\n      bool success = std::any_of(predicates.begin(), predicates.end(),\n                                 [o=std::cref(obj), &rejections](const auto& p) {\n         auto result = p(o);\n         if (!result) rejections.push_back(std::move(result));\n         return !!result;\n      });\n      if (success) return predicate_result::Success();\n      return predicate_result::Rejection(std::move(rejections));\n   };\n}\n\ntemplate<typename Object>\nobject_restriction_predicate<Object> restrictions_to_predicate(vector<restriction> rs, bool allow_empty) {\n   if (!allow_empty)\n      FC_ASSERT(!rs.empty(), \"Empty attribute assertions and logical OR branches are not permitted\");\n\n   vector<object_restriction_predicate<Object>> predicates;\n   std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()),\n                  std::back_inserter(predicates), [](restriction&& r) {\n      if (r.restriction_type.value == restriction::func_logical_or) {\n          FC_ASSERT(r.argument.which() == restriction_argument::tag<vector<vector<restriction>>>::value,\n                    \"Restriction argument for logical OR function type must be list of restriction lists.\");\n          return create_logical_or_predicate<Object>(std::move(r.argument.get<vector<vector<restriction>>>()));\n      }\n      return create_field_predicate<Object>(std::move(r), short());\n   });\n\n   return [predicates=std::move(predicates)](const Object& obj) {\n      for (size_t i = 0; i < predicates.size(); ++i) {\n         auto result = predicates[i](obj);\n         if (!result) {\n            result.rejection_path.push_back(i);\n            return result;\n         }\n      }\n      return predicate_result::Success();\n   };\n}\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/safe_compare.hpp",
    "content": "#ifndef BOOST_NUMERIC_SAFE_COMPARE_HPP\n#define BOOST_NUMERIC_SAFE_COMPARE_HPP\n\n// MS compatible compilers support #pragma once\n#if defined(_MSC_VER) && (_MSC_VER >= 1020)\n# pragma once\n#endif\n\n//  Copyright (c) 2012 Robert Ramey\n//\n// Distributed under the Boost Software License, Version 1.0. (See\n// accompanying file BOOST_LICENSE_1_0.txt or copy at\n// http://www.boost.org/LICENSE_1_0.txt)\n\n#include <type_traits>\n#include <limits>\n\nnamespace boost {\nnamespace safe_numerics {\nnamespace safe_compare {\n\n////////////////////////////////////////////////////\n// safe comparison on primitive integral types\nnamespace safe_compare_detail {\n    template<typename T>\n    using make_unsigned = typename std::conditional<\n        std::is_signed<T>::value,\n        std::make_unsigned<T>,\n        T\n    >::type;\n\n    // both arguments unsigned or signed\n    template<bool TS, bool US>\n    struct less_than {\n        template<class T, class U>\n        constexpr static bool invoke(const T & t, const U & u){\n            return t < u;\n        }\n    };\n\n    // T unsigned, U signed\n    template<>\n    struct less_than<false, true> {\n        template<class T, class U>\n        constexpr static bool invoke(const T & t, const U & u){\n            return\n                (u < 0) ?\n                    false\n                :\n                    less_than<false, false>::invoke(\n                        t,\n                        static_cast<const typename make_unsigned<U>::type &>(u)\n                    )\n                ;\n        }\n    };\n    // T signed, U unsigned\n    template<>\n    struct less_than<true, false> {\n        template<class T, class U>\n        constexpr static bool invoke(const T & t, const U & u){\n            return\n                (t < 0) ?\n                    true\n                :\n                    less_than<false, false>::invoke(\n                        static_cast<const typename make_unsigned<T>::type &>(t),\n                        u\n                    )\n                ;\n        }\n    };\n} // safe_compare_detail\n\ntemplate<class T, class U>\ntypename std::enable_if<\n    std::is_integral<T>::value && std::is_integral<U>::value,\n    bool\n>::type\nconstexpr less_than(const T & lhs, const U & rhs) {\n    return safe_compare_detail::less_than<\n        std::is_signed<T>::value,\n        std::is_signed<U>::value\n    >::template invoke(lhs, rhs);\n}\n\ntemplate<class T, class U>\ntypename std::enable_if<\n    std::is_floating_point<T>::value && std::is_floating_point<U>::value,\n    bool\n>::type\nconstexpr less_than(const T & lhs, const U & rhs) {\n    return lhs < rhs;\n}\n\ntemplate<class T, class U>\nconstexpr bool greater_than(const T & lhs, const U & rhs) {\n    return less_than(rhs, lhs);\n}\n\ntemplate<class T, class U>\nconstexpr bool less_than_equal(const T & lhs, const U & rhs) {\n    return ! greater_than(lhs, rhs);\n}\n\ntemplate<class T, class U>\nconstexpr bool greater_than_equal(const T & lhs, const U & rhs) {\n    return ! less_than(lhs, rhs);\n}\n\nnamespace safe_compare_detail {\n    // both arguments unsigned or signed\n    template<bool TS, bool US>\n    struct equal {\n        template<class T, class U>\n        constexpr static bool invoke(const T & t, const U & u){\n            return t == u;\n        }\n    };\n\n    // T unsigned, U signed\n    template<>\n    struct equal<false, true> {\n        template<class T, class U>\n        constexpr static bool invoke(const T & t, const U & u){\n            return\n                (u < 0) ?\n                    false\n                :\n                    equal<false, false>::invoke(\n                        t,\n                        static_cast<const typename make_unsigned<U>::type &>(u)\n                    )\n                ;\n        }\n    };\n    // T signed, U unsigned\n    template<>\n    struct equal<true, false> {\n        template<class T, class U>\n        constexpr static bool invoke(const T & t, const U & u){\n            return\n                (t < 0) ?\n                    false\n                :\n                    equal<false, false>::invoke(\n                        static_cast<const typename make_unsigned<T>::type &>(t),\n                        u\n                    )\n                ;\n        }\n    };\n} // safe_compare_detail\n\ntemplate<class T, class U>\ntypename std::enable_if<\n    std::is_integral<T>::value && std::is_integral<U>::value,\n    bool\n>::type\nconstexpr equal(const T & lhs, const U & rhs) {\n    return safe_compare_detail::equal<\n        std::numeric_limits<T>::is_signed,\n        std::numeric_limits<U>::is_signed\n    >::template invoke(lhs, rhs);\n}\n\ntemplate<class T, class U>\ntypename std::enable_if<\n    std::is_floating_point<T>::value && std::is_floating_point<U>::value,\n    bool\n>::type\nconstexpr equal(const T & lhs, const U & rhs) {\n    return lhs == rhs;\n}\n\ntemplate<class T, class U>\nconstexpr bool not_equal(const T & lhs, const U & rhs) {\n    return ! equal(lhs, rhs);\n}\n\n} // safe_compare\n} // safe_numerics\n} // boost\n\n#endif // BOOST_NUMERIC_SAFE_COMPARE_HPP\n"
  },
  {
    "path": "libraries/protocol/custom_authorities/sliced_lists.hxx",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/operations.hpp>\n\n#include <fc/reflect/typelist.hpp>\n\nnamespace graphene { namespace protocol {\nnamespace typelist = fc::typelist;\n\n// To make the build gentler on RAM, break the operation list into several pieces to build over several files\nusing operation_list_1 = static_variant<typelist::builder<>\n                                                ::add<transfer_operation>            // 0\n                                                ::add<limit_order_create_operation>  // 1\n                                                ::add<limit_order_cancel_operation>  // 2\n                                                ::add<call_order_update_operation>   // 3\n                                                ::finalize>;\nusing operation_list_2 = static_variant<typelist::builder<>\n                                                ::add<account_create_operation>      // 5\n                                                ::add<account_update_operation>      // 6\n                                                ::finalize>;\nusing operation_list_3 = static_variant<typelist::builder<>\n                                                ::add<asset_create_operation>        // 10\n                                                ::finalize>;\nusing operation_list_5 = static_variant<typelist::builder<>\n                                                ::add<asset_update_feed_producers_operation> // 13\n                                                ::add<asset_issue_operation>                 // 14\n                                                ::add<asset_reserve_operation>               // 15\n                                                ::finalize>;\nusing operation_list_6 = static_variant<typelist::builder<>\n                                                ::add<asset_publish_feed_operation>          // 19\n                                                ::add<witness_update_operation>              // 21\n                                                ::finalize>;\nusing operation_list_9 = static_variant<typelist::builder<>\n                                                ::add<vesting_balance_create_operation>      // 32\n                                                ::add<vesting_balance_withdraw_operation>    // 33\n                                                ::finalize>;\nusing operation_list_10 = static_variant<typelist::builder<>\n                                                ::add<override_transfer_operation>   // 38\n                                                ::finalize>;\nusing operation_list_11 = static_variant<typelist::builder<>\n                                                ::add<htlc_create_operation>         // 49\n                                                ::add<htlc_redeem_operation>         // 50\n                                                ::add<htlc_extend_operation>         // 52\n                                                ::finalize>;\n// Note: Since BSIP-40 is not to be enabled on the BitShares Mainnet any time soon,\n//       by now, the list of supported operations ends here.\n//       These operations are used in unit tests.\n//       As of writing, transfer_operation and limit_order_create_operation appeared on the public testnet\n//       between block #31808000 and #31811000.\n//       Other operations are added to the unsupported_operations_list with a comment \"Unsupported\".\n//       The operations in the unsupported_operations_list with other comments are virtual operations or\n//       unimplemented, so should be kept there anyway.\n//       This is to reduce the compilation time and the size of binaries.\n// TODO support more operations when we decide to continue BSIP-40 development.\nusing unsupported_operations_list = static_variant<typelist::builder<>\n                                                ::add<fill_order_operation>            // 4  // VIRTUAL\n                                                ::add<account_whitelist_operation>     // 7  // Unsupported\n                                                ::add<account_upgrade_operation>       // 8  // Unsupported\n                                                ::add<account_transfer_operation>      // 9  // Unimplemented\n                                                ::add<asset_update_operation>          // 11 // Unsupported\n                                                ::add<asset_update_bitasset_operation> // 12 // Unsupported\n                                                ::add<asset_fund_fee_pool_operation>   // 16 // Unsupported\n                                                ::add<asset_settle_operation>          // 17 // Unsupported\n                                                ::add<asset_global_settle_operation>   // 18 // Unsupported\n                                                ::add<witness_create_operation>        // 20 // Unsupported\n                                                // [22, 32) // Unsupported\n                                                ::add_list<typelist::slice<operation::list,\n                                                      typelist::index_of< operation::list,\n                                                                          proposal_create_operation >(),\n                                                      typelist::index_of< operation::list,\n                                                                          vesting_balance_create_operation >() >>\n                                                ::add<worker_create_operation>       // 34 // Unsupported\n                                                ::add<custom_operation>              // 35 // Unsupported\n                                                ::add<assert_operation>              // 36 // Unsupported\n                                                ::add<balance_claim_operation>       // 37 // Unsupported\n                                                ::add<transfer_to_blind_operation>   // 39 // Unsupported\n                                                ::add<blind_transfer_operation>      // 40 // Unsupported\n                                                ::add<transfer_from_blind_operation> // 41 // Unsupported\n                                                ::add<asset_settle_cancel_operation> // 42 // VIRTUAL\n                                                ::add<asset_claim_fees_operation>    // 43 // Unsupported\n                                                ::add<fba_distribute_operation>      // 44 // VIRTUAL\n                                                ::add<bid_collateral_operation>      // 45 // Unsupported\n                                                ::add<execute_bid_operation>         // 46 // VIRTUAL\n                                                ::add<asset_claim_pool_operation>    // 47 // Unsupported\n                                                ::add<asset_update_issuer_operation> // 48 // Unsupported\n                                                ::add<htlc_redeemed_operation>       // 51 // VIRTUAL\n                                                ::add<htlc_refund_operation>         // 53 // VIRTUAL\n                                                // New operations are added here // Unsupported\n                                                ::add_list<typelist::slice<operation::list,\n                                                      typelist::index_of< operation::list,\n                                                                          custom_authority_create_operation >() >>\n                                                ::finalize>;\n\nobject_restriction_predicate<operation> get_restriction_pred_list_1(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_2(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_3(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_5(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_6(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_9(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_10(size_t idx, vector<restriction> rs);\nobject_restriction_predicate<operation> get_restriction_pred_list_11(size_t idx, vector<restriction> rs);\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/custom_authority.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/custom_authority.hpp>\n#include <graphene/protocol/operations.hpp>\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nshare_type custom_authority_create_operation::calculate_fee(const fee_params_t& k)const {\n   share_type core_fee_required = k.basic_fee;\n   // Note: practically the `*` won't cause an integer overflow, because k.price_per_byte is 32 bit\n   //       and the results of pack_size() won't be too big\n   core_fee_required += k.price_per_byte * (fc::raw::pack_size(restrictions) + fc::raw::pack_size(auth));\n   return core_fee_required;\n}\n\nvoid custom_authority_create_operation::validate()const {\n   FC_ASSERT(fee.amount >= 0, \"Fee amount can not be negative\");\n\n   FC_ASSERT(account != GRAPHENE_TEMP_ACCOUNT\n             && account != GRAPHENE_COMMITTEE_ACCOUNT\n             && account != GRAPHENE_WITNESS_ACCOUNT\n             && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT,\n             \"Can not create custom authority for special accounts\");\n\n   FC_ASSERT(valid_from < valid_to, \"valid_from must be earlier than valid_to\");\n\n   // Note: The authentication authority can be empty, but it cannot be impossible to satisify. Disable the authority\n   // using the `enabled` boolean rather than setting an impossible authority.\n\n   FC_ASSERT(auth.address_auths.size() == 0, \"Address authorities are not supported\");\n   FC_ASSERT(!auth.is_impossible(), \"Cannot use an imposible authority threshold\");\n\n   // Validate restrictions by constructing a predicate for them; this throws if restrictions aren't valid\n   get_restriction_predicate(restrictions, operation_type);\n}\n\nshare_type custom_authority_update_operation::calculate_fee(const fee_params_t& k)const {\n   share_type core_fee_required = k.basic_fee;\n   // Note: practically the `*` won't cause an integer overflow, because k.price_per_byte is 32 bit\n   //       and the results of pack_size() won't be too big\n   core_fee_required += k.price_per_byte * fc::raw::pack_size(restrictions_to_add);\n   if (new_auth)\n      core_fee_required += k.price_per_byte * fc::raw::pack_size(*new_auth);\n   return core_fee_required;\n}\n\nvoid custom_authority_update_operation::validate()const {\n   FC_ASSERT(fee.amount >= 0, \"Fee amount can not be negative\");\n\n   FC_ASSERT(account != GRAPHENE_TEMP_ACCOUNT\n             && account != GRAPHENE_COMMITTEE_ACCOUNT\n             && account != GRAPHENE_WITNESS_ACCOUNT\n             && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT,\n             \"Can not create custom authority for special accounts\");\n   if (new_valid_from && new_valid_to)\n      FC_ASSERT(*new_valid_from < *new_valid_to, \"valid_from must be earlier than valid_to\");\n   if (new_auth) {\n      FC_ASSERT(!new_auth->is_impossible(), \"Cannot use an impossible authority threshold\");\n      FC_ASSERT(new_auth->address_auths.size() == 0, \"Address auth is not supported\");\n   }\n   FC_ASSERT( new_enabled.valid() || new_valid_from.valid() || new_valid_to.valid() || new_auth.valid()\n              || !restrictions_to_remove.empty() || !restrictions_to_add.empty(),\n              \"Must update something\" );\n}\n\nvoid custom_authority_delete_operation::validate()const {\n   FC_ASSERT(fee.amount >= 0, \"Fee amount can not be negative\");\n\n   FC_ASSERT(account != GRAPHENE_TEMP_ACCOUNT\n             && account != GRAPHENE_COMMITTEE_ACCOUNT\n             && account != GRAPHENE_WITNESS_ACCOUNT\n             && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT,\n             \"Can not delete custom authority for special accounts\");\n}\n\n} } // graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/fee_schedule.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/fee_schedule.hpp>\n\nnamespace graphene { namespace protocol {\n\n   fee_schedule fee_schedule::get_default_impl()\n   {\n      fee_schedule result;\n      const auto count = fee_parameters::count();\n      result.parameters.reserve(count);\n      for( size_t i = 0; i < count; ++i )\n      {\n         fee_parameters x;\n         x.set_which(i);\n         result.parameters.insert(x);\n      }\n      return result;\n   }\n\n   const fee_schedule& fee_schedule::get_default()\n   {\n      static const auto result = get_default_impl();\n      return result;\n   }\n\n   struct zero_fee_visitor\n   {\n      using result_type = void;\n\n      template<typename ParamType>\n      result_type operator()(  ParamType& op )const\n      {\n         memset( (char*)&op, 0, sizeof(op) );\n      }\n   };\n\n   void fee_schedule::zero_all_fees()\n   {\n      *this = get_default();\n      for( fee_parameters& i : parameters )\n         i.visit( zero_fee_visitor() );\n      this->scale = 0;\n   }\n\n} } // graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/fee_schedule_calc.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace protocol {\n\n   struct calc_fee_visitor\n   {\n      using result_type = uint64_t;\n\n      const fee_schedule& param;\n      const operation::tag_type current_op;\n      calc_fee_visitor( const fee_schedule& p, const operation& op ):param(p),current_op(op.which())\n      { /* Nothing else to do */ }\n\n      template<typename OpType>\n      result_type operator()( const OpType& op )const\n      {\n         try {\n            return op.calculate_fee( param.get<OpType>() ).value;\n         } catch (fc::assert_exception& e) {\n             fee_parameters params;\n             params.set_which(current_op);\n             auto itr = param.parameters.find(params);\n             if( itr != param.parameters.end() )\n                params = *itr;\n             return op.calculate_fee( params.get<typename OpType::fee_params_t>() ).value;\n         }\n      }\n   };\n\n   template<>\n   uint64_t calc_fee_visitor::operator()(const htlc_create_operation& op)const\n   {\n      //TODO: refactor for performance (see https://github.com/bitshares/bitshares-core/issues/2150)\n      transfer_operation::fee_params_t t;\n      if (param.exists<transfer_operation>())\n         t = param.get<transfer_operation>();\n      return op.calculate_fee( param.get<htlc_create_operation>(), t.price_per_kbyte).value;\n   }\n\n   template<>\n   uint64_t calc_fee_visitor::operator()(const asset_create_operation& op)const\n   {\n      //TODO: refactor for performance (see https://github.com/bitshares/bitshares-core/issues/2150)\n      optional<uint64_t> sub_asset_creation_fee;\n      if( param.exists<account_transfer_operation>() && param.exists<ticket_create_operation>() )\n         sub_asset_creation_fee = param.get<account_transfer_operation>().fee;\n      asset_create_operation::fee_params_t old_asset_creation_fee_params;\n      if( param.exists<asset_create_operation>() )\n         old_asset_creation_fee_params = param.get<asset_create_operation>();\n      return op.calculate_fee( old_asset_creation_fee_params, sub_asset_creation_fee ).value;\n   }\n\n   asset fee_schedule::calculate_fee( const operation& op )const\n   {\n      uint64_t required_fee = op.visit( calc_fee_visitor( *this, op ) );\n      if( scale != GRAPHENE_100_PERCENT )\n      {\n         auto scaled = fc::uint128_t(required_fee) * scale;\n         scaled /= GRAPHENE_100_PERCENT;\n         FC_ASSERT( scaled <= GRAPHENE_MAX_SHARE_SUPPLY,\n                    \"Required fee after scaling would exceed maximum possible supply\" );\n         required_fee = static_cast<uint64_t>(scaled);\n      }\n      return asset( required_fee );\n   }\n\n   asset fee_schedule::calculate_fee( const operation& op, const price& core_exchange_rate )const\n   {\n      return calculate_fee( op ).multiply_and_round_up( core_exchange_rate );\n   }\n\n} } // graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/fee_schedule_serialization_pack.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_PACK( graphene::protocol::fee_schedule )\n"
  },
  {
    "path": "libraries/protocol/fee_schedule_serialization_variant.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_VARIANT( graphene::protocol::fee_schedule )\n"
  },
  {
    "path": "libraries/protocol/fee_schedule_set_fee.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/fee_schedule.hpp>\n\nnamespace graphene { namespace protocol {\n\n   struct set_fee_visitor\n   {\n      using result_type = void;\n\n      asset _fee;\n\n      explicit set_fee_visitor( const asset& f ):_fee(f){}\n\n      template<typename OpType>\n      void operator()( OpType& op )const\n      {\n         op.fee = _fee;\n      }\n   };\n\n   asset fee_schedule::set_fee( operation& op, const price& core_exchange_rate )const\n   {\n      auto f = calculate_fee( op, core_exchange_rate );\n      for( size_t i=0; i<MAX_FEE_STABILIZATION_ITERATION; ++i )\n      {\n         op.visit( set_fee_visitor( f ) );\n         auto f2 = calculate_fee( op, core_exchange_rate );\n         if( f >= f2 )\n            break;\n         f = f2;\n         if( 0 == i )\n         {\n            // no need for warnings on later iterations\n            wlog( \"set_fee requires multiple iterations to stabilize with core_exchange_rate ${p} on operation ${op}\",\n               (\"p\", core_exchange_rate) (\"op\", op) );\n         }\n      }\n      return f;\n   }\n\n} } // graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/htlc.cpp",
    "content": "/*\n * Copyright (c) 2018 jmjatlanta and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/htlc.hpp>\n\n#include <fc/io/raw.hpp>\n\n#define SECONDS_PER_DAY (60 * 60 * 24)\n\nnamespace graphene { namespace protocol {\n\n   void htlc_create_operation::validate()const {\n      FC_ASSERT( fee.amount >= 0, \"Fee amount should not be negative\" );\n      FC_ASSERT( amount.amount > 0, \"HTLC amount should be greater than zero\" );\n   }\n\n   share_type htlc_create_operation::calculate_fee( const fee_params_t& fee_params,\n         uint32_t fee_per_kb )const\n   {\n      uint64_t days = ( claim_period_seconds + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY;\n      // multiply with overflow check\n      share_type total_fee = fee_params.fee; \n      total_fee += share_type(fee_params.fee_per_day) * days;\n      if (extensions.value.memo.valid())\n         total_fee += calculate_data_fee( fc::raw::pack_size(extensions.value.memo), fee_per_kb);\n      return total_fee;\n   }\n\n   void htlc_redeem_operation::validate()const {\n      FC_ASSERT( fee.amount >= 0, \"Fee amount should not be negative\" );\n   }\n\n   share_type htlc_redeem_operation::calculate_fee( const fee_params_t& fee_params )const\n   {\n      uint64_t kb = ( preimage.size() + 1023 ) / 1024;\n      uint64_t product = kb * fee_params.fee_per_kb;\n      FC_ASSERT( kb == 0 || product / kb == fee_params.fee_per_kb, \"Fee calculation overflow\");\n      return fee_params.fee + product;\n   }\n\n   void htlc_extend_operation::validate()const {\n      FC_ASSERT( fee.amount >= 0 , \"Fee amount should not be negative\");\n   }\n\n   share_type htlc_extend_operation::calculate_fee( const fee_params_t& fee_params )const\n   {\n      uint32_t days = ( seconds_to_add + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY;\n      uint64_t per_day_fee = fee_params.fee_per_day * days;\n      FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, \"Fee calculation overflow\" );\n      return fee_params.fee + per_day_fee;\n   }\n} }\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeemed_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_refund_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/README.md",
    "content": "Protocol Definition \n--------------------\n\nThe classes declared in these headers provide the complete definition of the \nGraphene protocol and are organized according to feature.   Nothing in this\ndirectory should depend upon anything other than fc or other types defined\nin the protocol directory.  \n\nTo be more specific, implementation details such as the objects defined in\nthe object database should not be required here.\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/account.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/authority.hpp>\n#include <graphene/protocol/buyback.hpp>\n#include <graphene/protocol/special_authority.hpp>\n#include <graphene/protocol/vote.hpp>\n\nnamespace graphene { namespace protocol {\n\n   bool is_valid_name( const string& s );\n   bool is_cheap_name( const string& n );\n\n   /// These are the fields which can be updated by the active authority.\n   struct account_options\n   {\n      /// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non-\n      /// validated account activities. This field is here to prevent confusion if the active authority has zero or\n      /// multiple keys in it.\n      public_key_type  memo_key;\n      /// If this field is set to an account ID other than GRAPHENE_PROXY_TO_SELF_ACCOUNT,\n      /// then this account's votes will be ignored; its stake\n      /// will be counted as voting for the referenced account's selected votes instead.\n      account_id_type voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n\n      /// The number of active witnesses this account votes the blockchain should appoint\n      /// Must not exceed the actual number of witnesses voted for in @ref votes\n      uint16_t num_witness = 0;\n      /// The number of active committee members this account votes the blockchain should appoint\n      /// Must not exceed the actual number of committee members voted for in @ref votes\n      uint16_t num_committee = 0;\n      /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this\n      /// account's balance of core asset.\n      flat_set<vote_id_type> votes;\n      extensions_type        extensions;\n\n      /// Whether this account is voting\n      inline bool is_voting() const\n      {\n         return ( voting_account != GRAPHENE_PROXY_TO_SELF_ACCOUNT || !votes.empty() );\n      }\n\n      uint16_t num_committee_voted() const\n      {\n         if( voting_account != GRAPHENE_PROXY_TO_SELF_ACCOUNT )\n            return 0;\n         return std::count_if( votes.begin(), votes.end(),\n                               [](vote_id_type v){ return v.type() == vote_id_type::vote_type::committee; } );\n      }\n\n      void validate()const;\n   };\n\n   /**\n    *  @ingroup operations\n    */\n   struct account_create_operation : public base_operation\n   {\n      struct ext\n      {\n         optional< void_t >            null_ext;\n         optional< special_authority > owner_special_authority;\n         optional< special_authority > active_special_authority;\n         optional< buyback_account_options > buyback_options;\n      };\n\n      struct fee_params_t\n      {\n         uint64_t basic_fee      = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account\n         uint64_t premium_fee    = 2000*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account\n         uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset           fee;\n      /// This account pays the fee. Must be a lifetime member.\n      account_id_type registrar;\n\n      /// This account receives a portion of the fee split between registrar and referrer. Must be a member.\n      account_id_type referrer;\n      /// Of the fee split between registrar and referrer, this percentage goes to the referrer. The rest goes to the\n      /// registrar.\n      uint16_t        referrer_percent = 0;\n\n      string          name;\n      authority       owner;\n      authority       active;\n\n      account_options options;\n      extension< ext > extensions;\n\n      account_id_type fee_payer()const { return registrar; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& )const;\n\n      void get_required_active_authorities( flat_set<account_id_type>& a )const\n      {\n         // registrar should be required anyway as it is the fee_payer(), but we insert it here just to be sure\n         a.insert( registrar );\n         if( extensions.value.buyback_options.valid() )\n            a.insert( extensions.value.buyback_options->asset_to_buy_issuer );\n      }\n   };\n\n   /**\n    * @ingroup operations\n    * @brief Update an existing account\n    *\n    * This operation is used to update an existing account. It can be used to update the authorities,\n    * or adjust the options on the account.\n    * See @ref graphene::chain::account_object::options for the options which may be updated.\n    */\n   struct account_update_operation : public base_operation\n   {\n      struct ext\n      {\n         optional< void_t >            null_ext;\n         optional< special_authority > owner_special_authority;\n         optional< special_authority > active_special_authority;\n      };\n\n      struct fee_params_t\n      {\n         share_type fee             = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t   price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset fee;\n      /// The account to update\n      account_id_type account;\n\n      /// New owner authority. If set, this operation requires owner authority to execute.\n      optional<authority> owner;\n      /// New active authority. This can be updated by the current active authority.\n      optional<authority> active;\n\n      /// New account options\n      optional<account_options> new_options;\n      extension< ext > extensions;\n\n      account_id_type fee_payer()const { return account; }\n      void       validate()const;\n      share_type calculate_fee( const fee_params_t& k )const;\n\n      bool is_owner_update()const\n      { return owner || extensions.value.owner_special_authority.valid(); }\n\n      void get_required_owner_authorities( flat_set<account_id_type>& a )const\n      { if( is_owner_update() ) a.insert( account ); }\n\n      void get_required_active_authorities( flat_set<account_id_type>& a )const\n      { if( !is_owner_update() ) a.insert( account ); }\n   };\n\n   /**\n    * @brief This operation is used to whitelist and blacklist accounts, primarily for transacting in whitelisted assets\n    * @ingroup operations\n    *\n    * Accounts can freely specify opinions about other accounts, in the form of either whitelisting or blacklisting\n    * them. This information is used in chain validation only to determine whether an account is authorized to transact\n    * in an asset type which enforces a whitelist, but third parties can use this information for other uses as well,\n    * as long as it does not conflict with the use of whitelisted assets.\n    *\n    * An asset which enforces a whitelist specifies a list of accounts to maintain its whitelist, and a list of\n    * accounts to maintain its blacklist. In order for a given account A to hold and transact in a whitelisted asset S,\n    * A must be whitelisted by at least one of S's whitelist_authorities and blacklisted by none of S's\n    * blacklist_authorities. If A receives a balance of S, and is later removed from the whitelist(s) which allowed it\n    * to hold S, or added to any blacklist S specifies as authoritative, A's balance of S will be frozen until A's\n    * authorization is reinstated.\n    *\n    * This operation requires authorizing_account's signature, but not account_to_list's. The fee is paid by\n    * authorizing_account.\n    */\n   struct account_whitelist_operation : public base_operation\n   {\n      struct fee_params_t { share_type fee = 300000; };\n      enum account_listing {\n         no_listing = 0x0, ///< No opinion is specified about this account\n         white_listed = 0x1, ///< This account is whitelisted, but not blacklisted\n         black_listed = 0x2, ///< This account is blacklisted, but not whitelisted\n         white_and_black_listed = white_listed | black_listed ///< This account is both whitelisted and blacklisted\n      };\n\n      /// Paid by authorizing_account\n      asset           fee;\n      /// The account which is specifying an opinion of another account\n      account_id_type authorizing_account;\n      /// The account being opined about\n      account_id_type account_to_list;\n      /// The new white and blacklist status of account_to_list, as determined by authorizing_account\n      /// This is a bitfield using values defined in the account_listing enum\n      uint8_t new_listing = no_listing;\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return authorizing_account; }\n      void validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT(new_listing < 0x4); }\n   };\n\n   /**\n    * @brief Manage an account's membership status\n    * @ingroup operations\n    *\n    * This operation is used to upgrade an account to a member, or renew its subscription. If an account which is an\n    * unexpired annual subscription member publishes this operation with @ref upgrade_to_lifetime_member set to false,\n    * the account's membership expiration date will be pushed backward one year. If a basic account publishes it with\n    * @ref upgrade_to_lifetime_member set to false, the account will be upgraded to a subscription member with an\n    * expiration date one year after the processing time of this operation.\n    *\n    * Any account may use this operation to become a lifetime member by setting @ref upgrade_to_lifetime_member to\n    * true. Once an account has become a lifetime member, it may not use this operation anymore.\n    */\n   struct account_upgrade_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t membership_annual_fee   =  2000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint64_t membership_lifetime_fee = 10000 * GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to upgrade to a lifetime member\n      };\n\n      asset             fee;\n      /// The account to upgrade; must not already be a lifetime member\n      account_id_type   account_to_upgrade;\n      /// If true, the account will be upgraded to a lifetime member; otherwise, it will add a year to the subscription\n      bool              upgrade_to_lifetime_member = false;\n      extensions_type   extensions;\n\n      account_id_type fee_payer()const { return account_to_upgrade; }\n      void       validate()const;\n      share_type calculate_fee( const fee_params_t& k )const;\n   };\n\n   /**\n    * @brief transfers the account to another account while clearing the white list\n    * @ingroup operations\n    *\n    * In theory an account can be transferred by simply updating the authorities, but that kind\n    * of transfer lacks semantic meaning and is more often done to rotate keys without transferring\n    * ownership.   This operation is used to indicate the legal transfer of title to this account and\n    * a break in the operation history.\n    *\n    * The account_id's owner/active/voting/memo authority should be set to new_owner\n    *\n    * This operation will clear the account's whitelist statuses, but not the blacklist statuses.\n    */\n   struct account_transfer_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;\n      account_id_type account_id;\n      account_id_type new_owner;\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return account_id; }\n      void        validate()const;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT(graphene::protocol::account_options, (memo_key)(voting_account)(num_witness)(num_committee)(votes)(extensions))\nFC_REFLECT_ENUM( graphene::protocol::account_whitelist_operation::account_listing,\n                (no_listing)(white_listed)(black_listed)(white_and_black_listed))\n\nFC_REFLECT(graphene::protocol::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority)(buyback_options) )\nFC_REFLECT_TYPENAME(graphene::protocol::extension<graphene::protocol::account_create_operation::ext>)\nFC_REFLECT( graphene::protocol::account_create_operation,\n            (fee)(registrar)\n            (referrer)(referrer_percent)\n            (name)(owner)(active)(options)(extensions)\n          )\n\nFC_REFLECT(graphene::protocol::account_update_operation::ext, (null_ext)(owner_special_authority)(active_special_authority) )\nFC_REFLECT_TYPENAME(graphene::protocol::extension<graphene::protocol::account_update_operation::ext>)\nFC_REFLECT( graphene::protocol::account_update_operation,\n            (fee)(account)(owner)(active)(new_options)(extensions)\n          )\n\nFC_REFLECT( graphene::protocol::account_upgrade_operation,\n            (fee)(account_to_upgrade)(upgrade_to_lifetime_member)(extensions) )\n\nFC_REFLECT( graphene::protocol::account_whitelist_operation, (fee)(authorizing_account)(account_to_list)(new_listing)(extensions))\n\nFC_REFLECT( graphene::protocol::account_create_operation::fee_params_t, (basic_fee)(premium_fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::account_whitelist_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::account_update_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::account_upgrade_operation::fee_params_t, (membership_annual_fee)(membership_lifetime_fee) )\nFC_REFLECT( graphene::protocol::account_transfer_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::account_transfer_operation, (fee)(account_id)(new_owner)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_options )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/address.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n\n#include <fc/crypto/elliptic.hpp>\n#include <fc/crypto/ripemd160.hpp>\n\nnamespace graphene { namespace protocol {\n   struct pts_address;\n\n   /**\n    *  @brief a 160 bit hash of a public key\n    *\n    *  An address can be converted to or from a base58 string with 32 bit checksum.\n    *\n    *  An address is calculated as ripemd160( sha512( compressed_ecc_public_key ) )\n    *\n    *  When converted to a string, checksum calculated as the first 4 bytes ripemd160( address ) is\n    *  appended to the binary address before converting to base58.\n    */\n   class address\n   {\n      public:\n       address(){} ///< constructs empty / null address\n       explicit address( const std::string& base58str );   ///< converts to binary, validates checksum\n       explicit address( const fc::ecc::public_key& pub ); ///< converts to address\n       explicit address( const fc::ecc::public_key_data& pub ); ///< converts to address\n       explicit address( const pts_address& pub );         ///< converts to address\n       explicit address( const public_key_type& pubkey );  ///< converts to address\n\n       static bool is_valid( const std::string& base58str, const std::string& prefix = GRAPHENE_ADDRESS_PREFIX );\n\n       explicit operator std::string()const; ///< converts to base58 + checksum\n\n       fc::ripemd160 addr;\n   };\n   inline bool operator == ( const address& a, const address& b ) { return a.addr == b.addr; }\n   inline bool operator != ( const address& a, const address& b ) { return a.addr != b.addr; }\n   inline bool operator <  ( const address& a, const address& b ) { return a.addr <  b.addr; }\n\n   inline bool operator == ( const pts_address& a, const address& b ) { return address(a) == b; }\n   inline bool operator == ( const address& a, const pts_address& b ) { return a == address(b); }\n\n   inline bool operator == ( const public_key_type& a, const address& b ) { return address(a) == b; }\n   inline bool operator == ( const address& a, const public_key_type& b ) { return a == address(b); }\n\n} } // namespace graphene::protocol\n\nnamespace fc\n{\n   void to_variant( const graphene::protocol::address& var,  fc::variant& vo, uint32_t max_depth = 1 );\n   void from_variant( const fc::variant& var,  graphene::protocol::address& vo, uint32_t max_depth = 1 );\n}\n\nFC_REFLECT( graphene::protocol::address, (addr) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::address )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/assert.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol { \n\n   /**\n    *  Used to verify that account_id->name is equal to the given string literal.\n    */\n   struct account_name_eq_lit_predicate\n   {\n      account_id_type account_id;\n      string          name;\n\n      /**\n       *  Perform state-independent checks.  Verify\n       *  account_name is a valid account name.\n       */\n      bool validate()const;\n   };\n\n   /**\n    *  Used to verify that asset_id->symbol is equal to the given string literal.\n    */\n   struct asset_symbol_eq_lit_predicate\n   {\n      asset_id_type   asset_id;\n      string          symbol;\n\n      /**\n       *  Perform state independent checks.  Verify symbol is a\n       *  valid asset symbol.\n       */\n      bool validate()const;\n\n   };\n\n   /**\n    * Used to verify that a specific block is part of the\n    * blockchain history.  This helps protect some high-value\n    * transactions to newly created IDs\n    *\n    * The block ID must be within the last 2^16 blocks.\n    */\n   struct block_id_predicate\n   {\n      block_id_type id;\n      bool validate()const{ return true; }\n   };\n\n   /**\n    *  When defining predicates do not make the protocol dependent upon\n    *  implementation details.\n    */\n   typedef static_variant<\n      account_name_eq_lit_predicate,\n      asset_symbol_eq_lit_predicate,\n      block_id_predicate\n     > predicate;\n\n\n   /**\n    *  @brief assert that some conditions are true.\n    *  @ingroup operations\n    *\n    *  This operation performs no changes to the database state, but can but used to verify\n    *  pre or post conditions for other operations.\n    */\n   struct assert_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                      fee;\n      account_id_type            fee_paying_account;\n      vector<predicate>          predicates;\n      flat_set<account_id_type>  required_auths;\n      extensions_type            extensions;\n\n      account_id_type fee_payer()const { return fee_paying_account; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::assert_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::account_name_eq_lit_predicate, (account_id)(name) )\nFC_REFLECT( graphene::protocol::asset_symbol_eq_lit_predicate, (asset_id)(symbol) )\nFC_REFLECT( graphene::protocol::block_id_predicate, (id) )\nFC_REFLECT_TYPENAME( graphene::protocol::predicate )\nFC_REFLECT( graphene::protocol::assert_operation, (fee)(fee_paying_account)(predicates)(required_auths)(extensions) )\n \nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/asset.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace protocol {\n\n   struct price;\n\n   struct asset\n   {\n      asset( share_type a = 0, asset_id_type id = asset_id_type() )\n      :amount(a),asset_id(id){}\n\n      share_type    amount;\n      asset_id_type asset_id;\n\n      asset& operator += ( const asset& o )\n      {\n         FC_ASSERT( asset_id == o.asset_id );\n         amount += o.amount;\n         return *this;\n      }\n      asset& operator -= ( const asset& o )\n      {\n         FC_ASSERT( asset_id == o.asset_id );\n         amount -= o.amount;\n         return *this;\n      }\n      asset operator -()const { return asset( -amount, asset_id ); }\n\n      friend bool operator == ( const asset& a, const asset& b )\n      {\n         return std::tie( a.asset_id, a.amount ) == std::tie( b.asset_id, b.amount );\n      }\n      friend bool operator < ( const asset& a, const asset& b )\n      {\n         FC_ASSERT( a.asset_id == b.asset_id );\n         return a.amount < b.amount;\n      }\n      friend inline bool operator <= ( const asset& a, const asset& b )\n      {\n         return !(b < a);\n      }\n\n      friend inline bool operator != ( const asset& a, const asset& b )\n      {\n         return !(a == b);\n      }\n      friend inline bool operator > ( const asset& a, const asset& b )\n      {\n         return (b < a);\n      }\n      friend inline bool operator >= ( const asset& a, const asset& b )\n      {\n         return !(a < b);\n      }\n\n      friend asset operator - ( const asset& a, const asset& b )\n      {\n         FC_ASSERT( a.asset_id == b.asset_id );\n         return asset( a.amount - b.amount, a.asset_id );\n      }\n      friend asset operator + ( const asset& a, const asset& b )\n      {\n         FC_ASSERT( a.asset_id == b.asset_id );\n         return asset( a.amount + b.amount, a.asset_id );\n      }\n\n      static share_type scaled_precision( uint8_t precision );\n\n      asset multiply_and_round_up( const price& p )const; ///< Multiply and round up\n   };\n\n   /**\n    * @brief The price struct stores asset prices in the BitShares system.\n    *\n    * A price is defined as a ratio between two assets, and represents a possible exchange rate between those two\n    * assets. prices are generally not stored in any simplified form, i.e. a price of (1000 CORE)/(20 USD) is perfectly\n    * normal.\n    *\n    * The assets within a price are labeled base and quote. Throughout the BitShares code base, the convention used is\n    * that the base asset is the asset being sold, and the quote asset is the asset being purchased, where the price is\n    * represented as base/quote, so in the example price above the seller is looking to sell CORE asset and get USD in\n    * return.\n    */\n   struct price\n   {\n      explicit price(const asset& _base = asset(), const asset& _quote = asset())\n         : base(_base),quote(_quote){}\n\n      asset base;\n      asset quote;\n\n      static price max(asset_id_type base, asset_id_type quote );\n      static price min(asset_id_type base, asset_id_type quote );\n\n      static price call_price(const asset& debt, const asset& collateral, uint16_t collateral_ratio);\n\n      /// The unit price for an asset type A is defined to be a price such that for any asset m, m*A=m\n      static price unit_price(asset_id_type a = asset_id_type()) { return price(asset(1, a), asset(1, a)); }\n\n      price max()const { return price::max( base.asset_id, quote.asset_id ); }\n      price min()const { return price::min( base.asset_id, quote.asset_id ); }\n\n      double to_real()const { return double(base.amount.value)/double(quote.amount.value); }\n\n      bool is_null()const;\n      /// @brief Check if the object is valid\n      /// @param check_upper_bound Whether to check if the amounts in the price are too large\n      void validate( bool check_upper_bound = false )const;\n   };\n\n   price operator / ( const asset& base, const asset& quote );\n   inline price operator~( const price& p ) { return price{p.quote,p.base}; }\n\n   bool  operator <  ( const price& a, const price& b );\n   bool  operator == ( const price& a, const price& b );\n\n   inline bool  operator >  ( const price& a, const price& b ) { return (b < a); }\n   inline bool  operator <= ( const price& a, const price& b ) { return !(b < a); }\n   inline bool  operator >= ( const price& a, const price& b ) { return !(a < b); }\n   inline bool  operator != ( const price& a, const price& b ) { return !(a == b); }\n\n   asset operator *  ( const asset& a, const price& b ); ///< Multiply and round down\n\n   price operator *  ( const price& p, const ratio_type& r );\n   price operator /  ( const price& p, const ratio_type& r );\n\n   inline price& operator *=  ( price& p, const ratio_type& r )\n   { p = p * r; return p; }\n   inline price& operator /=  ( price& p, const ratio_type& r )\n   { p = p / r; return p; }\n\n   /**\n    *  @class price_feed\n    *  @brief defines market parameters for margin positions\n    */\n   struct price_feed\n   {\n      /**\n       *  Required maintenance collateral is defined\n       *  as a fixed point number with a maximum value of 10.000\n       *  and a minimum value of 1.000.  (denominated in GRAPHENE_COLLATERAL_RATIO_DENOM)\n       *\n       *  A black swan event occurs when value_of_collateral equals\n       *  value_of_debt * MSSR.  To avoid a black swan a margin call is\n       *  executed when value_of_debt * required_maintenance_collateral\n       *  equals value_of_collateral using rate.\n       *\n       *  Default requirement is $1.75 of collateral per $1 of debt\n       *\n       *  BlackSwan ---> SQR ---> MCR ----> SP\n       */\n      ///@{\n      /**\n       * Forced settlements will evaluate using this price, defined as BITASSET / COLLATERAL\n       */\n      price settlement_price;\n\n      /// Price at which automatically exchanging this asset for CORE from fee pool occurs (used for paying fees)\n      price core_exchange_rate;\n\n      /** Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM */\n      uint16_t maintenance_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;\n\n      /** Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM */\n      uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO;\n\n      /**\n       * This is the price at which a call order will relinquish COLLATERAL when margin called. It is\n       * also the price that establishes the minimum amount of collateral per debt that call orders must\n       * maintain to avoid possibility of black swan.  A call order maintaining less collateral per debt\n       * than this price is unable to meet the combined obligation to sell collateral at the Margin Call\n       * Offer Price (MCOP) *AND* to pay the margin call fee. The MSSP is related to the MCOP, but the\n       * MSSP accounts for the need to reserve extra collateral to pay the margin call fee, whereas the\n       * MCOP only accounts for the collateral to be traded to the call buyer.  Prior to the\n       * introduction of the Margin Call Fee Ratio (MCFR) with BSIP-74, the two prices (MSSP and MCOP)\n       * were identical, and MSSP could be thought of as \"the price at which you are forced to sell\n       * collateral if margin called,\" but this latter concept is now embodied by the MCOP.\n       *\n       * The Maximum Short Squeeze Price is computed as follows, in units of DEBT per COLLATERAL:\n       *\n       *   MSSP = settlement_price / MSSR\n       *\n       * @return The MSSP in units of DEBT per COLLATERAL.\n       */\n      price max_short_squeeze_price()const;\n      /**\n       * Older implementation of max_short_squeeze_price() due to hardfork changes. It came with\n       * the following commentary:\n       *\n       * When selling collateral to pay off debt, the least amount of debt to receive should be\n       *  min_usd = max_short_squeeze_price() * collateral\n       *\n       *  This is provided to ensure that a black swan cannot be trigged due to poor liquidity alone, it\n       *  must be confirmed by having the max_short_squeeze_price() move below the black swan price.\n       * @returns the Maximum Short Squeeze price for this asset\n       */\n      price max_short_squeeze_price_before_hf_1270()const;\n\n      /**\n       * Compute price at which margin calls offer to sell collateral.\n       *\n       * Margin calls offer a greater amount of COLLATERAL asset to the market to buy back DEBT\n       * asset than would otherwise be required in a fair exchange at the settlement_price.\n       * (I.e. they sell collateral \"cheaper\" than its price feed value.) This is done to attract a\n       * quick buyer of the call in order to preserve healthy collateralization of the DEBT asset\n       * overall.  The price at which the call is offered, in comparison to the settlement price, is\n       * determined by the Maximum Short Squeeze Ratio (MSSR) and the Margin Call Fee Ratio (MCFR)\n       * as follows, in units of DEBT per COLLATERAL:\n       *\n       *   MCOP = settlement_price / (MSSR - MCFR)\n       *\n       * Compare with Maximum Short Squeeze Price (MSSP), which is computed as follows:\n       *\n       *   MSSP = settlement_price / MSSR\n       *\n       * Since BSIP-74, we distinguish between Maximum Short Squeeze Price (MSSP) and Margin Call\n       * Order Price (MCOP). Margin calls previously offered collateral at the MSSP, but now they\n       * offer slightly less collateral per debt if Margin Call Fee Ratio (MCFR) is set, because\n       * the call order must reserve some collateral to pay the fee.  We must still retain the\n       * concept of MSSP, as it communicates the minimum collateralization before black swan may be\n       * triggered, but we add this new method to calculate MCOP.\n       *\n       * Note that when we calculate the MCOP, we enact a price floor to ensure the margin call never\n       * offers LESS collateral than the DEBT is worth. As such, it's important to calculate the\n       * realized fee, when trading at the offer price, as a delta between total relinquished collateral\n       * (DEBT*MSSP) and collateral sold to the buyer (DEBT*MCOP).  If you instead try to calculate the\n       * fee by direct multiplication of MCFR, you will get the wrong answer if the price was\n       * floored. (Fee is truncated when price is floored.)\n       *\n       * @param margin_call_fee_ratio MCFR value currently in effect. If zero or unset, returns\n       *    same result as @ref max_short_squeeze_price().\n       *\n       * @return The MCOP in units of DEBT per COLLATERAL.\n       */\n      price margin_call_order_price(const fc::optional<uint16_t>& margin_call_fee_ratio)const;\n\n      /// Compute the MCOR, the ratio between margin_call_order_price and feed price\n      /// @return MSSR - MCFR\n      ratio_type margin_call_order_ratio( const fc::optional<uint16_t>& margin_call_fee_ratio )const;\n\n      /**\n       * Ratio between max_short_squeeze_price and margin_call_order_price.\n       *\n       * This ratio, if it multiplied margin_call_order_price (expressed in DEBT/COLLATERAL), would\n       * yield the max_short_squeeze_price, apart perhaps for truncation (rounding) error.\n       *\n       * When a margin call is taker, matching an existing order on the books, it is possible the call\n       * gets a better realized price than the order price that it offered at.  In this case, the margin\n       * call fee is proportionaly reduced. This ratio is used to calculate the price at which the call\n       * relinquishes collateral (to meet both trade and fee obligations) based on actual realized match\n       * price.\n       *\n       * This function enacts the same flooring as margin_call_order_price() (MSSR - MCFR is floored at\n       * 1.00).  This ensures we apply the same fee truncation in the taker case as the maker case.\n       *\n       * @return (MSSR - MCFR) / MSSR\n       */\n      ratio_type margin_call_pays_ratio(const fc::optional<uint16_t>& margin_call_fee_ratio)const;\n\n      /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call\n      /// territory.\n      /// Calculation: ~settlement_price * maintenance_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM\n      price maintenance_collateralization()const;\n\n      /// Whether the parameters that affect margin calls in this price feed object are the same as the parameters\n      /// in the passed-in object\n      bool margin_call_params_equal( const price_feed& b ) const\n      {\n         if( this == &b )\n            return true;\n         return std::tie(   settlement_price,   maintenance_collateral_ratio,   maximum_short_squeeze_ratio ) ==\n                std::tie( b.settlement_price, b.maintenance_collateral_ratio, b.maximum_short_squeeze_ratio );\n      }\n      ///@}\n\n      void validate() const;\n      bool is_for( asset_id_type asset_id ) const;\n   private:\n      /// Helper function for other functions e.g. @ref margin_call_order_price\n      uint16_t get_margin_call_price_numerator(const fc::optional<uint16_t>& margin_call_fee_ratio)const;\n   };\n\n} }\n\nFC_REFLECT( graphene::protocol::asset, (amount)(asset_id) )\nFC_REFLECT( graphene::protocol::price, (base)(quote) )\n\nFC_REFLECT( graphene::protocol::price_feed,\n            (settlement_price)(maintenance_collateral_ratio)(maximum_short_squeeze_ratio)(core_exchange_rate) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::price )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::price_feed )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/asset_ops.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/memo.hpp>\n\nnamespace graphene { namespace protocol {\n\n   struct additional_asset_options\n   {\n      fc::optional<uint16_t>                  reward_percent;\n      fc::optional<flat_set<account_id_type>> whitelist_market_fee_sharing;\n      // After BSIP81 activation, taker_fee_percent is the taker fee\n      fc::optional<uint16_t>                  taker_fee_percent;\n   };\n   typedef extension<additional_asset_options> additional_asset_options_t;\n\n   bool is_valid_symbol( const string& symbol );\n\n   /**\n    * @brief The asset_options struct contains options available on all assets in the network\n    *\n    * @note Changes to this struct will break protocol compatibility\n    */\n   struct asset_options {\n      /// The maximum supply of this asset which may exist at any given time. This can be as large as\n      /// GRAPHENE_MAX_SHARE_SUPPLY\n      share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      /// When this asset is traded on the markets, this percentage of the total traded will be exacted and paid\n      /// to the issuer. This is a fixed point value, representing hundredths of a percent, i.e. a value of 100\n      /// in this field means a 1% fee is charged on market trades of this asset.\n      // BSIP81: Asset owners may specify different market fee rate for maker orders and taker orders\n      // After BSIP81 activation, market_fee_percent is the maker fee\n      uint16_t market_fee_percent = 0;\n      /// Market fees calculated as @ref market_fee_percent of the traded volume are capped to this value\n      share_type max_market_fee = GRAPHENE_MAX_SHARE_SUPPLY;\n\n      /// The flags which the issuer has permission to update. See @ref asset_issuer_permission_flags\n      uint16_t issuer_permissions = DEFAULT_UIA_ASSET_ISSUER_PERMISSION;\n      /// The currently active flags on this permission. See @ref asset_issuer_permission_flags\n      uint16_t flags = 0;\n\n      /// @return the bits in @ref flags which are allowed to be updated according to data in @ref issuer_permissions\n      uint16_t get_enabled_issuer_permissions_mask() const;\n\n      /// When a non-core asset is used to pay a fee, the blockchain must convert that asset to core asset in\n      /// order to accept the fee. If this asset's fee pool is funded, the chain will automatically deposite fees\n      /// in this asset to its accumulated fees, and withdraw from the fee pool the same amount as converted at\n      /// the core exchange rate.\n      price core_exchange_rate = price(asset(), asset(0, asset_id_type(1)));\n\n      /// A set of accounts which maintain whitelists to consult for this asset. If whitelist_authorities\n      /// is non-empty, then only accounts in whitelist_authorities are allowed to hold, use, or transfer the asset.\n      flat_set<account_id_type> whitelist_authorities;\n      /// A set of accounts which maintain blacklists to consult for this asset. If flags & white_list is set,\n      /// an account may only send, receive, trade, etc. in this asset if none of these accounts appears in\n      /// its account_object::blacklisting_accounts field. If the account is blacklisted, it may not transact in\n      /// this asset even if it is also whitelisted.\n      flat_set<account_id_type> blacklist_authorities;\n\n      /** defines the assets that this asset may be traded against in the market */\n      flat_set<asset_id_type>   whitelist_markets;\n      /** defines the assets that this asset may not be traded against in the market, must not overlap whitelist */\n      flat_set<asset_id_type>   blacklist_markets;\n\n      /**\n       * data that describes the meaning/purpose of this asset, fee will be charged proportional to\n       * size of description.\n       */\n      string description;\n      additional_asset_options_t extensions;\n\n      /// Perform internal consistency checks.\n      /// @throws fc::exception if any check fails\n      void validate()const;\n\n      /// Perform checks about @ref flags.\n      /// @throws fc::exception if any check fails\n      void validate_flags( bool is_market_issued, bool allow_disable_collateral_bid = true )const;\n   };\n\n   /**\n    * @brief The bitasset_options struct contains configurable options available only to BitAssets.\n    *\n    * @note Changes to this struct will break protocol compatibility\n    */\n   struct bitasset_options {\n\n      /// Defines how a BitAsset would respond to black swan events\n      enum class black_swan_response_type\n      {\n         /// All debt positions are closed, all or some collateral is moved to a global-settlement fund.\n         /// Debt asset holders can claim collateral via force-settlement.\n         /// It is not allowed to create new debt positions when the fund is not empty.\n         global_settlement = 0,\n         /// No debt position is closed, and the derived settlement price is dynamically capped at the collateral\n         /// ratio of the debt position with the least collateral ratio so that all debt positions are able to pay\n         /// off their debt when being margin called or force-settled.\n         /// It is allowed to create new debt positions and update existing debt positions.\n         /// Also known as \"Global Settlement Protection\".\n         no_settlement = 1,\n         /// Only the undercollateralized debt positions are closed and their collateral is moved to a fund which\n         /// can be claimed via force-settlement. The derived settlement price is capped at the fund's collateral\n         /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price\n         /// when the fund is not empty.\n         /// It is allowed to create new debt positions and update existing debt positions.\n         individual_settlement_to_fund = 2,\n         /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order\n         /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining\n         /// debt positions could be margin called at a worse price.\n         /// It is allowed to create new debt positions and update existing debt positions.\n         individual_settlement_to_order = 3,\n         /// Total number of available black swan response methods\n         BSRM_TYPE_COUNT = 4\n      };\n\n      struct ext\n      {\n         /// After BSIP77, when creating a new debt position or updating an existing position,\n         /// the position will be checked against this parameter.\n         /// Unused for prediction markets, although we allow it to be set for simpler implementation\n         fc::optional<uint16_t> initial_collateral_ratio;  // BSIP-77\n         /// After BSIP75, the asset owner can update MCR directly\n         fc::optional<uint16_t> maintenance_collateral_ratio; // BSIP-75\n         /// After BSIP75, the asset owner can update MSSR directly\n         fc::optional<uint16_t> maximum_short_squeeze_ratio;  // BSIP-75\n         fc::optional<uint16_t> margin_call_fee_ratio; // BSIP 74\n         fc::optional<uint16_t> force_settle_fee_percent;  // BSIP-87\n         // https://github.com/bitshares/bitshares-core/issues/2467\n         fc::optional<uint8_t> black_swan_response_method;\n      };\n\n      /// Time before a price feed expires\n      uint32_t feed_lifetime_sec = GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME;\n      /// Minimum number of unexpired feeds required to extract a median feed from\n      uint8_t minimum_feeds = 1;\n      /// This is the delay between the time a long requests settlement and the chain evaluates the settlement\n      uint32_t force_settlement_delay_sec = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY;\n      /// This is the percent to adjust the feed price in the short's favor in the event of a forced settlement\n      uint16_t force_settlement_offset_percent = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET;\n      /// Force settlement volume can be limited such that only a certain percentage of the total existing supply\n      /// of the asset may be force-settled within any given chain maintenance interval. This field stores the\n      /// percentage of the current supply which may be force settled within the current maintenance interval. If\n      /// force settlements come due in an interval in which the maximum volume has already been settled, the new\n      /// settlements will be enqueued and processed at the beginning of the next maintenance interval.\n      uint16_t maximum_force_settlement_volume = GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME;\n      /// This speicifies which asset type is used to collateralize short sales\n      /// This field may only be updated if the current supply of the asset is zero.\n      asset_id_type short_backing_asset;\n\n      extension<ext> extensions;\n\n      /// Perform internal consistency checks.\n      /// @throws fc::exception if any check fails\n      void validate()const;\n\n      /// Get the effective black swan response method\n      black_swan_response_type get_black_swan_response_method() const\n      {\n         if( !extensions.value.black_swan_response_method.valid() )\n            return black_swan_response_type::global_settlement;\n         return static_cast<black_swan_response_type>( *extensions.value.black_swan_response_method );\n      }\n   };\n\n\n   /**\n    * @ingroup operations\n    */\n   struct asset_create_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t symbol3        = 500000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint64_t symbol4        = 300000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint64_t long_symbol    = 5000   * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 10; /// only required for large memos.\n      };\n\n      asset                   fee;\n      /// This account must sign and pay the fee for this operation. Later, this account may update the asset\n      account_id_type         issuer;\n      /// The ticker symbol of this asset\n      string                  symbol;\n      /// Number of digits to the right of decimal point, must be less than or equal to 12\n      uint8_t                 precision = 0;\n\n      /// Options common to all assets.\n      ///\n      /// @note\n      /// common_options.core_exchange_rate technically needs to store the asset ID of this new asset. Since this\n      /// ID is not known at the time this operation is created, create this price as though the new asset has\n      /// instance ID 1, and the chain will overwrite it with the new asset's ID.\n      asset_options              common_options;\n      /// Options only available for BitAssets. MUST be non-null if and only if the asset is market-issued.\n      optional<bitasset_options> bitasset_opts;\n      /// For BitAssets, set this to true if the asset implements a prediction market; false otherwise\n      bool is_prediction_market = false;\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n      share_type      calculate_fee( const fee_params_t& k,\n                                     const optional<uint64_t>& sub_asset_creation_fee )const;\n   };\n\n   /**\n    *  @brief allows global settling of bitassets (black swan or prediction markets)\n    *\n    *  In order to use this operation, @ref asset_to_settle must have the global_settle flag set\n    *\n    *  When this operation is executed all balances are converted into the backing asset at the\n    *  settle_price and all open margin positions are called at the settle price.  If this asset is\n    *  used as backing for other bitassets, those bitassets will be force settled at their current\n    *  feed price.\n    */\n   struct asset_global_settle_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;\n      account_id_type issuer; ///< must equal issuer of @ref asset_to_settle\n      asset_id_type   asset_to_settle;\n      price           settle_price;\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Schedules a market-issued asset for automatic settlement\n    * @ingroup operations\n    *\n    * Holders of market-issued assests may request a forced settlement for some amount of their asset.\n    * This means that the specified sum will be locked by the chain and held for the settlement period,\n    * after which time the chain will\n    * choose a margin posision holder and buy the settled asset using the margin's collateral. The price of this sale\n    * will be based on the feed price for the market-issued asset being settled.\n    * The exact settlement price will be the\n    * feed price at the time of settlement with an offset in favor of the margin position, where the offset is a\n    * blockchain parameter set in the global_property_object.\n    *\n    * The fee is paid by @ref account, and @ref account must authorize this operation\n    */\n   struct asset_settle_operation : public base_operation\n   {\n      struct fee_params_t {\n         /** this fee should be high to encourage small settlement requests to\n          * be performed on the market rather than via forced settlement.\n          *\n          * Note that in the event of a black swan or prediction market close out\n          * everyone will have to pay this fee.\n          */\n         uint64_t fee      = 100 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset           fee;\n      /// Account requesting the force settlement. This account pays the fee\n      account_id_type account;\n      /// Amount of asset to force settle. This must be a market-issued asset\n      asset           amount;\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * Virtual op generated when force settlement is cancelled.\n    */\n   struct asset_settle_cancel_operation : public base_operation\n   {\n      struct fee_params_t { };\n\n      asset_settle_cancel_operation() = default;\n      asset_settle_cancel_operation( const force_settlement_id_type& fsid, const account_id_type& aid,\n            const asset& a ) : settlement(fsid), account(aid), amount(a) {}\n\n      asset           fee;\n      force_settlement_id_type settlement;\n      /// Account requesting the force settlement. This account pays the fee\n      account_id_type account;\n      /// Amount of asset to force settle. This must be a market-issued asset\n      asset           amount;\n\n      account_id_type fee_payer()const { return account; }\n      /***\n       * This is a virtual operation and should never be placed in a block\n       * (i.e. in a proposal)\n       */\n      void validate() const { FC_ASSERT( !\"Virtual operation\"); }\n\n      share_type calculate_fee(const fee_params_t& params)const\n      { return 0; }\n   };\n\n   /**\n    * @ingroup operations\n    */\n   struct asset_fund_fee_pool_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee =  GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee; ///< core asset\n      account_id_type from_account;\n      asset_id_type   asset_id;\n      share_type      amount; ///< core asset\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return from_account; }\n      void       validate()const;\n   };\n\n   /**\n    * @brief Update options common to all assets\n    * @ingroup operations\n    *\n    * There are a number of options which all assets in the network use. These options are enumerated in the @ref\n    * asset_options struct. This operation is used to update these options for an existing asset.\n    *\n    * @note This operation cannot be used to update BitAsset-specific options. For these options, use @ref\n    * asset_update_bitasset_operation instead.\n    *\n    * @pre @ref issuer SHALL be an existing account and MUST match asset_object::issuer on @ref asset_to_update\n    * @pre @ref fee SHALL be nonnegative, and @ref issuer MUST have a sufficient balance to pay it\n    * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate()\n    * @post @ref asset_to_update will have options matching those of new_options\n    */\n   struct asset_update_operation : public base_operation\n   {\n      struct ext\n      {\n         /// After BSIP48, the precision of an asset can be updated if no supply is available\n         /// @note The parties involved still need to be careful\n         fc::optional<uint8_t> new_precision;\n         /// After BSIP48, if this option is set to true, the asset's core_exchange_rate won't be updated.\n         /// This is especially useful for committee-owned bitassets which can not be updated quickly.\n         fc::optional<bool> skip_core_exchange_rate;\n      };\n\n      struct fee_params_t {\n         uint64_t fee            = 500 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 10;\n      };\n\n      asset_update_operation(){}\n\n      asset           fee;\n      account_id_type issuer;\n      asset_id_type   asset_to_update;\n\n      /// If the asset is to be given a new issuer, specify his ID here.\n      optional<account_id_type>   new_issuer;\n      asset_options               new_options;\n      extension<ext>              extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    * @brief Update options specific to BitAssets\n    * @ingroup operations\n    *\n    * BitAssets have some options which are not relevant to other asset types. This operation is used to update those\n    * options an an existing BitAsset.\n    *\n    * @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update\n    * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref graphene::chain::asset_object::is_market_issued()\n    *                           returns true\n    * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it\n    * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate()\n    * @post @ref asset_to_update will have BitAsset-specific options matching those of new_options\n    */\n   struct asset_update_bitasset_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;\n      account_id_type issuer;\n      asset_id_type   asset_to_update;\n\n      bitasset_options new_options;\n      extensions_type  extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Update the set of feed-producing accounts for a BitAsset\n    * @ingroup operations\n    *\n    * BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers.\n    * This operation is used to specify which accounts may produce feeds for a given BitAsset.\n    *\n    * @pre @ref issuer MUST be an existing account, and MUST match asset_object::issuer on @ref asset_to_update\n    * @pre @ref issuer MUST NOT be the committee account\n    * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref graphene::chain::asset_object::is_market_issued()\n    *                           returns true\n    * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it\n    * @pre Cardinality of @ref new_feed_producers MUST NOT exceed @ref chain_parameters::maximum_asset_feed_publishers\n    * @post @ref asset_to_update will have a set of feed producers matching @ref new_feed_producers\n    * @post All valid feeds supplied by feed producers in @ref new_feed_producers, which were already feed producers\n    * prior to execution of this operation, will be preserved\n    */\n   struct asset_update_feed_producers_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;\n      account_id_type issuer;\n      asset_id_type   asset_to_update;\n\n      flat_set<account_id_type> new_feed_producers;\n      extensions_type           extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Publish price feeds for market-issued assets\n    * @ingroup operations\n    *\n    * Price feed providers use this operation to publish their price feeds for market-issued assets. A price feed is\n    * used to tune the market for a particular market-issued asset. For each value in the feed, the median across all\n    * committee_member feeds for that asset is calculated and the market for the asset is configured with the median\n    * of that value.\n    *\n    * The feed in the operation contains three prices: a call price limit, a short price limit,\n    * and a settlement price.\n    * The call limit price is structured as (collateral asset) / (debt asset) and the short limit price is structured\n    * as (asset for sale) / (collateral asset). Note that the asset IDs are opposite to eachother, so if we're\n    * publishing a feed for USD, the call limit price will be CORE/USD and the short limit price will be USD/CORE. The\n    * settlement price may be flipped either direction, as long as it is a ratio between the market-issued asset and\n    * its collateral.\n    */\n   struct asset_publish_feed_operation : public base_operation\n   {\n      struct ext\n      {\n         /// After BSIP77, price feed producers can feed ICR too\n         fc::optional<uint16_t> initial_collateral_ratio;  // BSIP-77\n      };\n\n      struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                  fee; ///< paid for by publisher\n      account_id_type        publisher;\n      asset_id_type          asset_id; ///< asset for which the feed is published\n      price_feed             feed;\n      extension<ext>         extensions;\n\n      account_id_type fee_payer()const { return publisher; }\n      void            validate()const;\n   };\n\n   /**\n    * @ingroup operations\n    */\n   struct asset_issue_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset            fee;\n      account_id_type  issuer; ///< Must be asset_to_issue->asset_id->issuer\n      asset            asset_to_issue;\n      account_id_type  issue_to_account;\n\n\n      /** user provided data encrypted to the memo key of the \"to\" account */\n      optional<memo_data>  memo;\n      extensions_type      extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    * @brief used to take an asset out of circulation, returning to the issuer\n    * @ingroup operations\n    *\n    * @note You cannot use this operation on market-issued assets.\n    */\n   struct asset_reserve_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset             fee;\n      account_id_type   payer;\n      asset             amount_to_reserve;\n      extensions_type   extensions;\n\n      account_id_type fee_payer()const { return payer; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief used to transfer accumulated fees back to the issuer's balance.\n    */\n   struct asset_claim_fees_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      struct additional_options_type\n      {\n         /// Which asset to claim fees from. This is needed, e.g., to claim collateral-\n         /// denominated fees from a collateral-backed smart asset. If unset, assumed to be same\n         /// asset as amount_to_claim is denominated in, such as would be the case when claiming\n         /// market fees. If set, validation requires it to be a different asset_id than\n         /// amount_to_claim (else there would exist two ways to form the same request).\n         fc::optional<asset_id_type> claim_from_asset_id;\n      };\n\n      asset           fee;\n      account_id_type issuer; ///< must match issuer of asset from which we claim fees\n      asset           amount_to_claim;\n\n      extension<additional_options_type> extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Update issuer of an asset\n    * @ingroup operations\n    *\n    * An issuer has general administrative power of an asset and in some cases\n    * also its shares issued to individuals. Thus, changing the issuer today\n    * requires the use of a separate operation that needs to be signed by the\n    * owner authority.\n    *\n    */\n   struct asset_update_issuer_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee            = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset           fee;\n      account_id_type issuer;\n      asset_id_type   asset_to_update;\n      account_id_type new_issuer;\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n\n      void get_required_owner_authorities( flat_set<account_id_type>& a )const\n      { a.insert( issuer ); }\n\n      void get_required_active_authorities( flat_set<account_id_type>& a )const\n      {  }\n\n   };\n\n   /**\n    * @brief Transfers BTS from the fee pool of a specified asset back to the issuer's balance\n\n    * @param fee Payment for the operation execution\n    * @param issuer Account which will be used for transfering BTS\n    * @param asset_id Id of the asset whose fee pool is going to be drained\n    * @param amount_to_claim Amount of BTS to claim from the fee pool\n    * @param extensions Field for future expansion\n\n    * @pre @ref fee must be paid in the asset other than the one whose pool is being drained\n    * @pre @ref amount_to_claim should be specified in the core asset\n    * @pre @ref amount_to_claim should be nonnegative\n    */\n   struct asset_claim_pool_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset           fee;\n      account_id_type issuer;\n      asset_id_type   asset_id;        /// fee.asset_id must != asset_id\n      asset           amount_to_claim; /// core asset\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::asset_claim_fees_operation, (fee)(issuer)(amount_to_claim)(extensions) )\nFC_REFLECT( graphene::protocol::asset_claim_fees_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_claim_fees_operation::additional_options_type, (claim_from_asset_id) )\n\nFC_REFLECT( graphene::protocol::asset_claim_pool_operation, (fee)(issuer)(asset_id)(amount_to_claim)(extensions) )\nFC_REFLECT( graphene::protocol::asset_claim_pool_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::asset_options,\n            (max_supply)\n            (market_fee_percent)\n            (max_market_fee)\n            (issuer_permissions)\n            (flags)\n            (core_exchange_rate)\n            (whitelist_authorities)\n            (blacklist_authorities)\n            (whitelist_markets)\n            (blacklist_markets)\n            (description)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::bitasset_options::ext,\n            (initial_collateral_ratio)\n            (maintenance_collateral_ratio)\n            (maximum_short_squeeze_ratio)\n            (margin_call_fee_ratio)\n            (force_settle_fee_percent)\n            (black_swan_response_method)\n          )\n\nFC_REFLECT( graphene::protocol::bitasset_options,\n            (feed_lifetime_sec)\n            (minimum_feeds)\n            (force_settlement_delay_sec)\n            (force_settlement_offset_percent)\n            (maximum_force_settlement_volume)\n            (short_backing_asset)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::additional_asset_options,\n            (reward_percent)(whitelist_market_fee_sharing)(taker_fee_percent) )\n\nFC_REFLECT( graphene::protocol::asset_update_operation::ext, (new_precision)(skip_core_exchange_rate) )\nFC_REFLECT( graphene::protocol::asset_publish_feed_operation::ext, (initial_collateral_ratio) )\n\nFC_REFLECT( graphene::protocol::asset_create_operation::fee_params_t,\n            (symbol3)(symbol4)(long_symbol)(price_per_kbyte) )\n\nFC_REFLECT( graphene::protocol::asset_global_settle_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_settle_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_settle_cancel_operation::fee_params_t, )\nFC_REFLECT( graphene::protocol::asset_fund_fee_pool_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_update_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::asset_update_issuer_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_update_bitasset_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_update_feed_producers_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_publish_feed_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::asset_issue_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::asset_reserve_operation::fee_params_t, (fee) )\n\n\nFC_REFLECT( graphene::protocol::asset_create_operation,\n            (fee)\n            (issuer)\n            (symbol)\n            (precision)\n            (common_options)\n            (bitasset_opts)\n            (is_prediction_market)\n            (extensions)\n          )\nFC_REFLECT( graphene::protocol::asset_update_operation,\n            (fee)\n            (issuer)\n            (asset_to_update)\n            (new_issuer)\n            (new_options)\n            (extensions)\n          )\nFC_REFLECT( graphene::protocol::asset_update_issuer_operation,\n            (fee)\n            (issuer)\n            (asset_to_update)\n            (new_issuer)\n            (extensions)\n          )\nFC_REFLECT( graphene::protocol::asset_update_bitasset_operation,\n            (fee)\n            (issuer)\n            (asset_to_update)\n            (new_options)\n            (extensions)\n          )\nFC_REFLECT( graphene::protocol::asset_update_feed_producers_operation,\n            (fee)(issuer)(asset_to_update)(new_feed_producers)(extensions)\n          )\nFC_REFLECT( graphene::protocol::asset_publish_feed_operation,\n            (fee)(publisher)(asset_id)(feed)(extensions) )\nFC_REFLECT( graphene::protocol::asset_settle_operation, (fee)(account)(amount)(extensions) )\nFC_REFLECT( graphene::protocol::asset_settle_cancel_operation, (fee)(settlement)(account)(amount) )\nFC_REFLECT( graphene::protocol::asset_global_settle_operation,\n            (fee)(issuer)(asset_to_settle)(settle_price)(extensions) )\nFC_REFLECT( graphene::protocol::asset_issue_operation,\n            (fee)(issuer)(asset_to_issue)(issue_to_account)(memo)(extensions) )\nFC_REFLECT( graphene::protocol::asset_reserve_operation,\n            (fee)(payer)(amount_to_reserve)(extensions) )\n\nFC_REFLECT( graphene::protocol::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_options )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options::ext )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::additional_asset_options )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::ext )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::ext )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION(\n   graphene::protocol::asset_update_feed_producers_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_cancel_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed_producers_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/authority.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/types.hpp>\n#include <graphene/protocol/address.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    *  @class authority\n    *  @brief Identifies a weighted set of keys and accounts that must approve operations.\n    */\n   struct authority\n   {\n      authority(){}\n      template<class ...Args>\n      authority(uint32_t threshhold, Args... auths)\n         : weight_threshold(threshhold)\n      {\n         add_authorities(auths...);\n      }\n\n      enum classification\n      {\n         /** the key that is authorized to change owner, active, and voting keys */\n         owner  = 0,\n         /** the key that is able to perform normal operations */\n         active = 1,\n         key    = 2\n      };\n      void add_authority( const public_key_type& k, weight_type w )\n      {\n         key_auths[k] = w;\n      }\n      void add_authority( const address& k, weight_type w )\n      {\n         address_auths[k] = w;\n      }\n      void add_authority( account_id_type k, weight_type w )\n      {\n         account_auths[k] = w;\n      }\n      bool is_impossible()const\n      {\n         uint64_t auth_weights = 0;\n         for( const auto& item : account_auths ) auth_weights += item.second;\n         for( const auto& item : key_auths ) auth_weights += item.second;\n         for( const auto& item : address_auths ) auth_weights += item.second;\n         return auth_weights < weight_threshold;\n      }\n\n      template<typename AuthType>\n      void add_authorities(AuthType k, weight_type w)\n      {\n         add_authority(k, w);\n      }\n      template<typename AuthType, class ...Args>\n      void add_authorities(AuthType k, weight_type w, Args... auths)\n      {\n         add_authority(k, w);\n         add_authorities(auths...);\n      }\n\n      vector<public_key_type> get_keys() const\n      {\n         vector<public_key_type> result;\n         result.reserve( key_auths.size() );\n         for( const auto& k : key_auths )\n            result.push_back(k.first);\n         return result;\n      }\n      vector<address> get_addresses() const\n      {\n         vector<address> result;\n         result.reserve( address_auths.size() );\n         for( const auto& k : address_auths )\n            result.push_back(k.first);\n         return result;\n      }\n\n\n      friend bool operator == ( const authority& a, const authority& b )\n      {\n         return (a.weight_threshold == b.weight_threshold) &&\n                (a.account_auths == b.account_auths) &&\n                (a.key_auths == b.key_auths) &&\n                (a.address_auths == b.address_auths); \n      }\n      friend bool operator!= ( const authority& a, const authority& b ) { return !(a==b); }\n      uint32_t num_auths()const { return account_auths.size() + key_auths.size() + address_auths.size(); }\n      void     clear() { account_auths.clear(); key_auths.clear(); address_auths.clear(); weight_threshold = 0; }\n\n      static authority null_authority()\n      {\n         return authority( 1, GRAPHENE_NULL_ACCOUNT, 1 );\n      }\n\n      uint32_t                              weight_threshold = 0;\n      flat_map<account_id_type,weight_type> account_auths;\n      flat_map<public_key_type,weight_type> key_auths;\n      /** needed for backward compatibility only */\n      flat_map<address,weight_type>         address_auths;\n   };\n\n/**\n * Add all account members of the given authority to the given flat_set.\n */\nvoid add_authority_accounts(\n   flat_set<account_id_type>& result,\n   const authority& a\n   );\n\n} } // namespace graphene::protocol\n\nFC_REFLECT( graphene::protocol::authority, (weight_threshold)(account_auths)(key_auths)(address_auths) )\nFC_REFLECT_ENUM( graphene::protocol::authority::classification, (owner)(active)(key) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::authority )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/balance.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/authority.hpp>\n\nnamespace graphene { namespace protocol { \n\n   /**\n    * @brief Claim a balance in a @ref graphene::chain::balance_object\n    *\n    * This operation is used to claim the balance in a given @ref graphene::chain::balance_object.\n    * If the balance object contains a\n    * vesting balance, @ref total_claimed must not exceed @ref graphene::chain::balance_object::available\n    * at the time of evaluation. If\n    * the object contains a non-vesting balance, @ref total_claimed must be the full balance of the object.\n    */\n   struct balance_claim_operation : public base_operation\n   {\n      struct fee_params_t {};\n\n      asset             fee;\n      account_id_type   deposit_to_account;\n      balance_id_type   balance_to_claim;\n      public_key_type   balance_owner_key;\n      asset             total_claimed;\n\n      account_id_type fee_payer()const { return deposit_to_account; }\n      share_type      calculate_fee(const fee_params_t& )const { return 0; }\n      void            validate()const;\n      void            get_required_authorities( vector<authority>& a )const\n      {\n         a.push_back( authority( 1, balance_owner_key, 1 ) );\n      }\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::balance_claim_operation::fee_params_t,  )\nFC_REFLECT( graphene::protocol::balance_claim_operation,\n            (fee)(deposit_to_account)(balance_to_claim)(balance_owner_key)(total_claimed) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::balance_claim_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/base.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/ext.hpp>\n#include <graphene/protocol/types.hpp>\n\n#include <fc/thread/future.hpp>\n\nnamespace graphene { namespace protocol {\n   struct asset;\n   struct authority;\n\n   /**\n    *  @defgroup operations Operations\n    *  @ingroup transactions Transactions\n    *  @brief A set of valid comands for mutating the globally shared state.\n    *\n    *  An operation can be thought of like a function that will modify the global\n    *  shared state of the blockchain.  The members of each struct are like function\n    *  arguments and each operation can potentially generate a return value.\n    *\n    *  Operations can be grouped into transactions (@ref transaction) to ensure that they occur\n    *  in a particular order and that all operations apply successfully or\n    *  no operations apply.\n    *\n    *  Each operation is a fully defined state transition and can exist in a transaction on its own.\n    *\n    *  @section operation_design_principles Design Principles\n    *\n    *  Operations have been carefully designed to include all of the information necessary to\n    *  interpret them outside the context of the blockchain.   This means that information about\n    *  current chain state is included in the operation even though it could be inferred from\n    *  a subset of the data.   This makes the expected outcome of each operation well defined and\n    *  easily understood without access to chain state.\n    *\n    *  @subsection balance_calculation Balance Calculation Principle\n    *\n    *    We have stipulated that the current account balance may be entirely calculated from\n    *    just the subset of operations that are relevant to that account.  There should be\n    *    no need to process the entire blockchain inorder to know your account's balance.\n    *\n    *  @subsection fee_calculation Explicit Fee Principle\n    *\n    *    Blockchain fees can change from time to time and it is important that a signed\n    *    transaction explicitly agree to the fees it will be paying.  This aids with account\n    *    balance updates and ensures that the sender agreed to the fee prior to making the\n    *    transaction.\n    *\n    *  @subsection defined_authority Explicit Authority\n    *\n    *    Each operation shall contain enough information to know which accounts must authorize\n    *    the operation.  This principle enables authority verification to occur in a centralized,\n    *    optimized, and parallel manner.\n    *\n    *  @subsection relevancy_principle Explicit Relevant Accounts\n    *\n    *    Each operation contains enough information to enumerate all accounts for which the\n    *    operation should apear in its account history.  This principle enables us to easily\n    *    define and enforce the @ref balance_calculation. This is superset of the @ref defined_authority\n    *\n    *  @{\n    */\n\n   struct void_result{};\n\n   struct generic_operation_result\n   {\n      flat_set<object_id_type> new_objects;\n      flat_set<object_id_type> updated_objects;\n      flat_set<object_id_type> removed_objects;\n   };\n\n   struct generic_exchange_operation_result\n   {\n      vector<asset> paid;\n      vector<asset> received;\n      vector<asset> fees;\n   };\n\n   struct extendable_operation_result_dtl\n   {\n      optional<flat_set<account_id_type>> impacted_accounts;\n      optional<flat_set<object_id_type>>  new_objects;\n      optional<flat_set<object_id_type>>  updated_objects;\n      optional<flat_set<object_id_type>>  removed_objects;\n      optional<vector<asset>>             paid;\n      optional<vector<asset>>             received;\n      optional<vector<asset>>             fees;\n   };\n\n   using extendable_operation_result = extension<extendable_operation_result_dtl>;\n\n   using operation_result = fc::static_variant <\n         /* 0 */ void_result,\n         /* 1 */ object_id_type,\n         /* 2 */ asset,\n         /* 3 */ generic_operation_result,\n         /* 4 */ generic_exchange_operation_result,\n         /* 5 */ extendable_operation_result\n         >;\n\n   struct base_operation\n   {\n      virtual ~base_operation() = default;\n      template<typename T>\n      share_type calculate_fee(const T& params)const\n      {\n         return params.fee;\n      }\n      virtual void get_required_authorities( vector<authority>& )const{ /* do nothing by default */ }\n      virtual void get_required_active_authorities( flat_set<account_id_type>& )const{ /* do nothing by default */ }\n      virtual void get_required_owner_authorities( flat_set<account_id_type>& )const{ /* do nothing by default */ }\n      virtual void validate()const{ /* do nothing by default */ }\n      fc::optional< fc::future<void> > validate_parallel( uint32_t skip )const;\n\n      static uint64_t calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte );\n   };\n\n   /**\n    *  For future expansion many structus include a single member of type\n    *  extensions_type that can be changed when updating a protocol.  You can\n    *  always add new types to a static_variant without breaking backward\n    *  compatibility.\n    */\n   using future_extensions = static_variant<void_t>;\n\n   /**\n    *  A flat_set is used to make sure that only one extension of\n    *  each type is added and that they are added in order.\n    *\n    *  @note static_variant compares only the type tag and not the\n    *  content.\n    */\n   using extensions_type = future_extensions::flat_set_type;\n\n   ///@}\n\n} } // graphene::protocol\n\nFC_REFLECT_TYPENAME( graphene::protocol::extendable_operation_result )\nFC_REFLECT_TYPENAME( graphene::protocol::operation_result )\nFC_REFLECT_TYPENAME( graphene::protocol::future_extensions )\nFC_REFLECT_TYPENAME( graphene::protocol::extensions_type )\nFC_REFLECT( graphene::protocol::void_result, )\nFC_REFLECT( graphene::protocol::generic_operation_result, (new_objects)(updated_objects)(removed_objects) )\nFC_REFLECT( graphene::protocol::generic_exchange_operation_result, (paid)(received)(fees) )\nFC_REFLECT( graphene::protocol::extendable_operation_result_dtl,\n            (impacted_accounts)(new_objects)(updated_objects)(removed_objects)(paid)(received)(fees) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::generic_operation_result ) // impl in operations.cpp\n// impl in operations.cpp\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::generic_exchange_operation_result )\n// impl in operations.cpp\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::extendable_operation_result_dtl )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/block.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/transaction.hpp>\n\nnamespace graphene { namespace protocol {\n\n   class block_header\n   {\n   public:\n      digest_type                   digest()const;\n      block_id_type                 previous;\n      uint32_t                      block_num()const { return num_from_id(previous) + 1; }\n      fc::time_point_sec            timestamp;\n      witness_id_type               witness;\n      checksum_type                 transaction_merkle_root;\n      // Note: when we need to add data to `extensions`, remember to review `database::_generate_block()`.\n      //       More info in https://github.com/bitshares/bitshares-core/issues/1136\n      extensions_type               extensions;\n\n      virtual ~block_header() = default;\n\n      static uint32_t num_from_id(const block_id_type& id);\n   };\n\n   class signed_block_header : public block_header\n   {\n   public:\n      const block_id_type&       id()const;\n      const fc::ecc::public_key& signee()const;\n      void                       sign( const fc::ecc::private_key& signer );\n      bool                       validate_signee( const fc::ecc::public_key& expected_signee )const;\n\n      signature_type             witness_signature;\n\n      signed_block_header() = default;\n      explicit signed_block_header( const block_header& header ) : block_header( header ) {}\n   protected:\n      mutable fc::ecc::public_key _signee;\n      mutable block_id_type       _block_id;\n   };\n\n   class signed_block : public signed_block_header\n   {\n   public:\n      const checksum_type& calculate_merkle_root()const;\n      vector<processed_transaction> transactions;\n   protected:\n      mutable checksum_type   _calculated_merkle_root;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::block_header, (previous)(timestamp)(witness)(transaction_merkle_root)(extensions) )\nFC_REFLECT_DERIVED( graphene::protocol::signed_block_header, (graphene::protocol::block_header), (witness_signature) )\nFC_REFLECT_DERIVED( graphene::protocol::signed_block, (graphene::protocol::signed_block_header), (transactions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::block_header)\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::signed_block_header)\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::signed_block)\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/buyback.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace protocol {\n\nstruct buyback_account_options\n{\n   /**\n    * The asset to buy.\n    */\n   asset_id_type       asset_to_buy;\n\n   /**\n    * Issuer of the asset.  Must sign the transaction, must match issuer\n    * of specified asset.\n    */\n   account_id_type     asset_to_buy_issuer;\n\n   /**\n    * What assets the account is willing to buy with.\n    * Other assets will just sit there since the account has null authority.\n    */\n   flat_set< asset_id_type > markets;\n};\n\n} }\n\nFC_REFLECT( graphene::protocol::buyback_account_options, (asset_to_buy)(asset_to_buy_issuer)(markets) );\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::buyback_account_options )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/chain_parameters.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <memory>\n#include <graphene/protocol/base.hpp>\n\nnamespace graphene { namespace protocol {\n   struct fee_schedule;\n\n   struct htlc_options\n   {\n      uint32_t max_timeout_secs;\n      uint32_t max_preimage_size;\n   };\n\n   struct custom_authority_options_type\n   {\n      uint32_t max_custom_authority_lifetime_seconds = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS;\n      uint32_t max_custom_authorities_per_account = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT;\n      uint32_t max_custom_authorities_per_account_op = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT_OP;\n      uint32_t max_custom_authority_restrictions = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_RESTRICTIONS;\n   };\n\n   struct chain_parameters\n   {\n      /** using a shared_ptr breaks the circular dependency created between operations and the fee schedule */\n      std::shared_ptr<const fee_schedule> current_fees;                  ///< current schedule of fees\n      const fee_schedule& get_current_fees() const { FC_ASSERT(current_fees); return *current_fees; }\n      fee_schedule& get_mutable_fees() { FC_ASSERT(current_fees); return const_cast<fee_schedule&>(*current_fees); }\n\n      uint8_t                 block_interval                      = GRAPHENE_DEFAULT_BLOCK_INTERVAL; ///< interval in seconds between blocks\n      uint32_t                maintenance_interval                = GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL; ///< interval in sections between blockchain maintenance events\n      uint8_t                 maintenance_skip_slots              = GRAPHENE_DEFAULT_MAINTENANCE_SKIP_SLOTS; ///< number of block_intervals to skip at maintenance time\n      uint32_t                committee_proposal_review_period    = GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC; ///< minimum time in seconds that a proposed transaction requiring committee authority may not be signed, prior to expiration\n      uint32_t                maximum_transaction_size            = GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE; ///< maximum allowable size in bytes for a transaction\n      uint32_t                maximum_block_size                  = GRAPHENE_DEFAULT_MAX_BLOCK_SIZE; ///< maximum allowable size in bytes for a block\n      uint32_t                maximum_time_until_expiration       = GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION; ///< maximum lifetime in seconds for transactions to be valid, before expiring\n      uint32_t                maximum_proposal_lifetime           = GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC; ///< maximum lifetime in seconds for proposed transactions to be kept, before expiring\n      uint8_t                 maximum_asset_whitelist_authorities = GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES; ///< maximum number of accounts which an asset may list as authorities for its whitelist OR blacklist\n      uint8_t                 maximum_asset_feed_publishers       = GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS; ///< the maximum number of feed publishers for a given asset\n      uint16_t                maximum_witness_count               = GRAPHENE_DEFAULT_MAX_WITNESSES; ///< maximum number of active witnesses\n      uint16_t                maximum_committee_count             = GRAPHENE_DEFAULT_MAX_COMMITTEE; ///< maximum number of active committee_members\n      uint16_t                maximum_authority_membership        = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP; ///< largest number of keys/accounts an authority can have\n      uint16_t                reserve_percent_of_fee              = GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE; ///< the percentage of the network's allocation of a fee that is taken out of circulation\n      uint16_t                network_percent_of_fee              = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; ///< percent of transaction fees paid to network\n      uint16_t                lifetime_referrer_percent_of_fee    = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE; ///< percent of fee which should go to lifetime referrer\n      uint32_t                cashback_vesting_period_seconds     = GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC; ///< time after cashback rewards are accrued before they become liquid\n      share_type              cashback_vesting_threshold          = GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD; ///< the maximum cashback that can be received without vesting\n      bool                    count_non_member_votes              = true; ///< set to false to restrict voting privlegages to member accounts\n      bool                    allow_non_member_whitelists         = false; ///< true if non-member accounts may set whitelists and blacklists; false otherwise\n      share_type              witness_pay_per_block               = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK; ///< CORE to be allocated to witnesses (per block)\n      uint32_t                witness_pay_vesting_seconds         = GRAPHENE_DEFAULT_WITNESS_PAY_VESTING_SECONDS; ///< vesting_seconds parameter for witness VBO's\n      share_type              worker_budget_per_day               = GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY; ///< CORE to be allocated to workers (per day)\n      uint16_t                max_predicate_opcode                = GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE; ///< predicate_opcode must be less than this number\n      share_type              fee_liquidation_threshold           = GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD; ///< value in CORE at which accumulated fees in blockchain-issued market assets should be liquidated\n      uint16_t                accounts_per_fee_scale              = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings\n      uint8_t                 account_fee_scale_bitshifts         = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling\n      uint8_t                 max_authority_depth                 = GRAPHENE_MAX_SIG_CHECK_DEPTH;\n\n      struct ext\n      {\n         optional< htlc_options > updatable_htlc_options;\n         optional< custom_authority_options_type > custom_authority_options;\n         optional< uint16_t > market_fee_network_percent;\n         optional< uint16_t > maker_fee_discount_percent;\n      };\n\n      extension<ext> extensions;\n\n      void validate()const;\n      \n      chain_parameters();\n      chain_parameters(const chain_parameters& other);\n      chain_parameters(chain_parameters&& other);\n      chain_parameters& operator=(const chain_parameters& other);\n      chain_parameters& operator=(chain_parameters&& other);\n\n      /// If @c market_fee_network_percent in @ref extensions is valid, return the value it contains,\n      /// otherwise return 0\n      uint16_t get_market_fee_network_percent() const;\n\n      /// If @c maker_fee_discount_percent in @ref extensions is valid, return the value it contains,\n      /// otherwise return 0\n      uint16_t get_maker_fee_discount_percent() const;\n\n      private:\n      static void safe_copy(chain_parameters& to, const chain_parameters& from);\n   };\n\n} }  // graphene::protocol\n\nFC_REFLECT( graphene::protocol::htlc_options,\n      (max_timeout_secs)\n      (max_preimage_size)\n)\n\nFC_REFLECT( graphene::protocol::custom_authority_options_type,\n      (max_custom_authority_lifetime_seconds)\n      (max_custom_authorities_per_account)\n      (max_custom_authorities_per_account_op)\n      (max_custom_authority_restrictions)\n)\n\nFC_REFLECT( graphene::protocol::chain_parameters::ext,\n      (updatable_htlc_options)\n      (custom_authority_options)\n      (market_fee_network_percent)\n      (maker_fee_discount_percent)\n)\n\nFC_REFLECT( graphene::protocol::chain_parameters,\n            (current_fees)\n            (block_interval)\n            (maintenance_interval)\n            (maintenance_skip_slots)\n            (committee_proposal_review_period)\n            (maximum_transaction_size)\n            (maximum_block_size)\n            (maximum_time_until_expiration)\n            (maximum_proposal_lifetime)\n            (maximum_asset_whitelist_authorities)\n            (maximum_asset_feed_publishers)\n            (maximum_witness_count)\n            (maximum_committee_count)\n            (maximum_authority_membership)\n            (reserve_percent_of_fee)\n            (network_percent_of_fee)\n            (lifetime_referrer_percent_of_fee)\n            (cashback_vesting_period_seconds)\n            (cashback_vesting_threshold)\n            (count_non_member_votes)\n            (allow_non_member_whitelists)\n            (witness_pay_per_block)\n            (worker_budget_per_day)\n            (max_predicate_opcode)\n            (fee_liquidation_threshold)\n            (accounts_per_fee_scale)\n            (account_fee_scale_bitshifts)\n            (max_authority_depth)\n            (extensions)\n          )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::chain_parameters )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/committee_member.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/chain_parameters.hpp>\n\nnamespace graphene { namespace protocol { \n\n   /**\n    * @brief Create a committee_member object, as a bid to hold a committee_member seat on the network.\n    * @ingroup operations\n    *\n    * Accounts which wish to become committee_members may use this operation to create a committee_member object\n    * which stakeholders may vote on to approve its position as a committee_member.\n    */\n   struct committee_member_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                                 fee;\n      /// The account which owns the committee_member. This account pays the fee for this operation.\n      account_id_type                       committee_member_account;\n      string                                url;\n\n      account_id_type fee_payer()const { return committee_member_account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Update a committee_member object.\n    * @ingroup operations\n    *\n    * Currently the only field which can be updated is the `url`\n    * field.\n    */\n   struct committee_member_update_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                                 fee;\n      /// The committee member to update.\n      committee_member_id_type              committee_member;\n      /// The account which owns the committee_member. This account pays the fee for this operation.\n      account_id_type                       committee_member_account;\n      optional< string >                    new_url;\n\n      account_id_type fee_payer()const { return committee_member_account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Used by committee_members to update the global parameters of the blockchain.\n    * @ingroup operations\n    *\n    * This operation allows the committee_members to update the global parameters on the blockchain.\n    * These control various tunable aspects of the chain, including block and maintenance intervals,\n    * maximum data sizes, the fees charged by the network, etc.\n    *\n    * This operation may only be used in a proposed transaction, and a proposed transaction which contains this\n    * operation must have a review period specified in the current global parameters before it may be accepted.\n    */\n   struct committee_member_update_global_parameters_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset             fee;\n      chain_parameters  new_parameters;\n\n      account_id_type fee_payer()const { return account_id_type(); }\n      void            validate()const;\n   };\n\n   /// TODO: committee_member_resign_operation : public base_operation\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::committee_member_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::committee_member_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::committee_member_update_global_parameters_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::committee_member_create_operation,\n            (fee)(committee_member_account)(url) )\nFC_REFLECT( graphene::protocol::committee_member_update_operation,\n            (fee)(committee_member)(committee_member_account)(new_url) )\nFC_REFLECT( graphene::protocol::committee_member_update_global_parameters_operation, (fee)(new_parameters) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION(\n   graphene::protocol::committee_member_update_global_parameters_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_global_parameters_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/confidential.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/authority.hpp>\n\nnamespace graphene { namespace protocol {\n\nusing fc::ecc::blind_factor_type;\n\n/**\n * @defgroup stealth Stealth Transfer\n * @brief Operations related to stealth transfer of value\n *\n * Stealth Transfers enable users to maintain their finanical privacy against even\n * though all transactions are public.  Every account has three balances:\n *\n * 1. Public Balance - everyone can see the balance changes and the parties involved\n * 2. Blinded Balance - everyone can see who is transacting but not the amounts involved\n * 3. Stealth Balance - both the amounts and parties involved are obscured\n *\n * Account owners may set a flag that allows their account to receive(or not) transfers of these kinds\n * Asset issuers can enable or disable the use of each of these types of accounts.\n *\n * Using the \"temp account\" which has no permissions required, users can transfer a\n * stealth balance to the temp account and then use the temp account to register a new\n * account.  In this way users can use stealth funds to create anonymous accounts with which\n * they can perform other actions that are not compatible with blinded balances (such as market orders)\n *\n * @section referral_program Referral Progam\n *\n * Stealth transfers that do not specify any account id cannot pay referral fees so 100% of the\n * transaction fee is paid to the network.\n *\n * @section transaction_fees Fees\n *\n * Stealth transfers can have an arbitrarylly large size and therefore the transaction fee for\n * stealth transfers is based purley on the data size of the transaction.\n */\n///@{\n\n/**\n *  @ingroup stealth\n *  This data is encrypted and stored in the\n *  encrypted memo portion of the blind output.\n */\nstruct blind_memo\n{\n   account_id_type     from;\n   share_type          amount;\n   string              message;\n   /** set to the first 4 bytes of the shared secret\n    * used to encrypt the memo.  Used to verify that\n    * decryption was successful.\n    */\n   uint32_t            check= 0;\n};\n\n/**\n *  @ingroup stealth\n */\nstruct blind_input\n{\n   fc::ecc::commitment_type      commitment;\n   /** provided to maintain the invariant that all authority\n    * required by an operation is explicit in the operation.  Must\n    * match blinded_balance_id->owner\n    */\n   authority                      owner;\n};\n\n/**\n *  When sending a stealth tranfer we assume users are unable to scan\n *  the full blockchain; therefore, payments require confirmation data\n *  to be passed out of band.   We assume this out-of-band channel is\n *  not secure and therefore the contents of the confirmation must be\n *  encrypted. \n */\nstruct stealth_confirmation\n{\n   struct memo_data\n   {\n      optional<public_key_type> from;\n      asset                     amount;\n      fc::sha256                blinding_factor;\n      fc::ecc::commitment_type  commitment;\n      uint32_t                  check = 0;\n   };\n\n   /**\n    *  Packs *this then encodes as base58 encoded string.\n    */\n   operator string()const;\n   /**\n    * Unpacks from a base58 string\n    */\n   stealth_confirmation( const std::string& base58 );\n   stealth_confirmation(){}\n\n   public_key_type           one_time_key;\n   optional<public_key_type> to;\n   vector<char>              encrypted_memo;\n};\n\n/**\n *  @class blind_output\n *  @brief Defines data required to create a new blind commitment\n *  @ingroup stealth\n *\n *  The blinded output that must be proven to be greater than 0\n */\nstruct blind_output\n{\n   fc::ecc::commitment_type                commitment;\n   /** only required if there is more than one blind output */\n   range_proof_type                        range_proof;\n   authority                               owner;\n   optional<stealth_confirmation>          stealth_memo;\n};\n\n\n/**\n *  @class transfer_to_blind_operation\n *  @ingroup stealth\n *  @brief Converts public account balance to a blinded or stealth balance\n */\nstruct transfer_to_blind_operation : public base_operation\n{\n   struct fee_params_t {\n      uint64_t fee              = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account\n      uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION;\n   };\n\n\n   asset                 fee;\n   asset                 amount;\n   account_id_type       from;\n   blind_factor_type     blinding_factor;\n   vector<blind_output>  outputs;\n\n   account_id_type fee_payer()const { return from; }\n   void            validate()const;\n   share_type      calculate_fee(const fee_params_t& )const;\n};\n\n/**\n *  @ingroup stealth\n *  @brief Converts blinded/stealth balance to a public account balance\n */\nstruct transfer_from_blind_operation : public base_operation\n{\n   struct fee_params_t {\n      uint64_t fee              = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account\n   };\n\n   asset                 fee;\n   asset                 amount;\n   account_id_type       to;\n   blind_factor_type     blinding_factor;\n   vector<blind_input>   inputs;\n\n   account_id_type fee_payer()const { return GRAPHENE_TEMP_ACCOUNT; }\n   void            validate()const;\n\n   void            get_required_authorities( vector<authority>& a )const\n   {\n      for( const auto& in : inputs )\n         a.push_back( in.owner ); \n   }\n};\n\n/**\n *  @ingroup stealth\n *  @brief Transfers from blind to blind\n *\n *  There are two ways to transfer value while maintaining privacy:\n *  1. account to account with amount kept secret\n *  2. stealth transfers with amount sender/receiver kept secret\n *\n *  When doing account to account transfers, everyone with access to the\n *  memo key can see the amounts, but they will not have access to the funds.\n *\n *  When using stealth transfers the same key is used for control and reading\n *  the memo.\n *\n *  This operation is more expensive than a normal transfer and has\n *  a fee proportional to the size of the operation.\n *\n *  All assets in a blind transfer must be of the same type: fee.asset_id\n *  The fee_payer is the temp account and can be funded from the blinded values.\n *\n *  Using this operation you can transfer from an account and/or blinded balances\n *  to an account and/or blinded balances.\n *\n *  Stealth Transfers:\n *\n *  Assuming Receiver has key pair R,r and has shared public key R with Sender\n *  Assuming Sender has key pair S,s\n *  Generate one time key pair  O,o  as s.child(nonce) where nonce can be inferred from transaction\n *  Calculate secret V = o*R\n *  blinding_factor = sha256(V)\n *  memo is encrypted via aes of V\n *  owner = R.child(sha256(blinding_factor))\n *\n *  Sender gives Receiver output ID to complete the payment.\n *\n *  This process can also be used to send money to a cold wallet without having to\n *  pre-register any accounts.\n *\n *  Outputs are assigned the same IDs as the inputs until no more input IDs are available,\n *  in which case a the return value will be the *first* ID allocated for an output.  Additional\n *  output IDs are allocated sequentially thereafter.   If there are fewer outputs than inputs\n *  then the input IDs are freed and never used again.\n */\nstruct blind_transfer_operation : public base_operation\n{\n   struct fee_params_t {\n      uint64_t fee              = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account\n      uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION;\n   };\n\n   asset                 fee;\n   vector<blind_input>   inputs;\n   vector<blind_output>  outputs;\n    \n   /** graphene TEMP account */\n   account_id_type fee_payer()const;\n   void            validate()const;\n   share_type      calculate_fee( const fee_params_t& k )const;\n\n   void            get_required_authorities( vector<authority>& a )const\n   {\n      for( const auto& in : inputs )\n         a.push_back( in.owner ); \n   }\n};\n\n///@} endgroup stealth\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::stealth_confirmation,\n            (one_time_key)(to)(encrypted_memo) )\n\nFC_REFLECT( graphene::protocol::stealth_confirmation::memo_data,\n            (from)(amount)(blinding_factor)(commitment)(check) )\n\nFC_REFLECT( graphene::protocol::blind_memo,\n            (from)(amount)(message)(check) )\nFC_REFLECT( graphene::protocol::blind_input,\n            (commitment)(owner) )\nFC_REFLECT( graphene::protocol::blind_output,\n            (commitment)(range_proof)(owner)(stealth_memo) )\nFC_REFLECT( graphene::protocol::transfer_to_blind_operation,\n            (fee)(amount)(from)(blinding_factor)(outputs) )\nFC_REFLECT( graphene::protocol::transfer_from_blind_operation,\n            (fee)(amount)(to)(blinding_factor)(inputs) )\nFC_REFLECT( graphene::protocol::blind_transfer_operation,\n            (fee)(inputs)(outputs) )\nFC_REFLECT( graphene::protocol::transfer_to_blind_operation::fee_params_t, (fee)(price_per_output) )\nFC_REFLECT( graphene::protocol::transfer_from_blind_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::blind_transfer_operation::fee_params_t, (fee)(price_per_output) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/config.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#define GRAPHENE_SYMBOL \"BTS\"\n#define GRAPHENE_ADDRESS_PREFIX \"BTS\"\n\n#define GRAPHENE_BLOCKCHAIN_PRECISION        uint64_t( 100000 )\n#define GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS 5\n\n#define GRAPHENE_MIN_ACCOUNT_NAME_LENGTH 1\n#define GRAPHENE_MAX_ACCOUNT_NAME_LENGTH 63\n\n#define GRAPHENE_MIN_ASSET_SYMBOL_LENGTH 3\n#define GRAPHENE_MAX_ASSET_SYMBOL_LENGTH 16\n\nconstexpr int64_t GRAPHENE_MAX_SHARE_SUPPLY (1000000000000000LL); // 10 ^ 15\n\n#define GRAPHENE_MAX_WORKER_NAME_LENGTH                       63\n#define GRAPHENE_MAX_URL_LENGTH                               127\n\n#define GRAPHENE_MAX_SIG_CHECK_DEPTH 2\n\n#define GRAPHENE_IRREVERSIBLE_THRESHOLD                      (70 * GRAPHENE_1_PERCENT)\n\n/**\n * every second, the fraction of burned core asset which cycles is\n * GRAPHENE_CORE_ASSET_CYCLE_RATE / (1 << GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS)\n */\n#define GRAPHENE_CORE_ASSET_CYCLE_RATE                        17\n#define GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS                   32\n\n/**\n * Don't allow the committee_members to publish a limit that would\n * make the network unable to operate.\n */\n#define GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT 1024\n#define GRAPHENE_MIN_BLOCK_INTERVAL   1 /* seconds */\n#define GRAPHENE_MAX_BLOCK_INTERVAL  30 /* seconds */\n\n#define GRAPHENE_DEFAULT_BLOCK_INTERVAL  5 /* seconds */\n#define GRAPHENE_DEFAULT_MAX_TRANSACTION_SIZE 2048\n#define GRAPHENE_DEFAULT_MAX_BLOCK_SIZE  (2*1000*1000) /* < 2 MiB (less than MAX_MESSAGE_SIZE in graphene/net/config.hpp) */\n#define GRAPHENE_DEFAULT_MAX_TIME_UNTIL_EXPIRATION (60*60*24) // seconds,  aka: 1 day\n#define GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL  (60*60*24) // seconds, aka: 1 day\n#define GRAPHENE_DEFAULT_MAINTENANCE_SKIP_SLOTS 3  // number of slots to skip for maintenance interval\n\n#define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_DELAY                 (60*60*24) ///< 1 day\n#define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_OFFSET                0 ///< 1%\n#define GRAPHENE_DEFAULT_FORCE_SETTLEMENT_MAX_VOLUME            (20* GRAPHENE_1_PERCENT) ///< 20%\n#define GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME                    (60*60*24) ///< 1 day\n#define GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP               10\n#define GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES        10\n#define GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS              10\n\n#define GRAPHENE_DEFAULT_MIN_WITNESS_COUNT                    (11)\n#define GRAPHENE_DEFAULT_MIN_COMMITTEE_MEMBER_COUNT           (11)\n#define GRAPHENE_DEFAULT_MAX_WITNESSES                        (1001) // SHOULD BE ODD\n#define GRAPHENE_DEFAULT_MAX_COMMITTEE                        (1001) // SHOULD BE ODD\n#define GRAPHENE_DEFAULT_MAX_PROPOSAL_LIFETIME_SEC            (60*60*24*7*4) // Four weeks\n#define GRAPHENE_DEFAULT_COMMITTEE_PROPOSAL_REVIEW_PERIOD_SEC (60*60*24*7*2) // Two weeks\n#define GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE               (20*GRAPHENE_1_PERCENT)\n#define GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE     (30*GRAPHENE_1_PERCENT)\n#define GRAPHENE_DEFAULT_CASHBACK_VESTING_PERIOD_SEC          (60*60*24*365) ///< 1 year\n#define GRAPHENE_DEFAULT_CASHBACK_VESTING_THRESHOLD           (GRAPHENE_BLOCKCHAIN_PRECISION*int64_t(100))\n#define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE                  (20*GRAPHENE_1_PERCENT)\n#define GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE                    1\n#define GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD            GRAPHENE_BLOCKCHAIN_PRECISION * 100;\n#define GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE               1000\n#define GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS          4\n#define GRAPHENE_DEFAULT_MAX_BUYBACK_MARKETS                  4\n\n#define GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK            (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t( 10) )\n#define GRAPHENE_DEFAULT_WITNESS_PAY_VESTING_SECONDS      (60*60*24)\n#define GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY            (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(500) * 1000 )\n#define GRAPHENE_DEFAULT_MINIMUM_FEEDS                       7\n\n#define GRAPHENE_MIN_BLOCK_SIZE_LIMIT (GRAPHENE_MIN_TRANSACTION_SIZE_LIMIT*5) // 5 transactions per block\n\n/** percentage fields are fixed point with a denominator of 10,000 */\n#define GRAPHENE_100_PERCENT                                    10000\n#define GRAPHENE_1_PERCENT                                      (GRAPHENE_100_PERCENT/100)\n/** NOTE: making this a power of 2 (say 2^15) would greatly accelerate fee calcs */\n\n#define GRAPHENE_MAX_MARKET_FEE_PERCENT                         GRAPHENE_100_PERCENT\n/**\n *  These ratios are fixed point numbers with a denominator of GRAPHENE_COLLATERAL_RATIO_DENOM, the\n *  minimum maitenance collateral is therefore 1.001x and the default\n *  maintenance ratio is 1.75x\n */\n///@{\n#define GRAPHENE_COLLATERAL_RATIO_DENOM                 1000\n#define GRAPHENE_MIN_COLLATERAL_RATIO                   1001  ///< lower than this could result in divide by 0\n#define GRAPHENE_MAX_COLLATERAL_RATIO                   32000 ///< higher than this is unnecessary and may exceed int16 storage\n#define GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO   1750 ///< Call when collateral only pays off 175% the debt\n#define GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO        1500 ///< Stop calling when collateral only pays off 150% of the debt\n///@}\n\n/// Denominator for SameT Fund fee calculation\nconstexpr uint32_t GRAPHENE_FEE_RATE_DENOM = 1000000;\n\n/// How long a credit offer will be kept active, in days\nconstexpr int64_t GRAPHENE_MAX_CREDIT_OFFER_DAYS = 380;\n/// How long a credit offer will be kept active, in seconds\nconstexpr int64_t GRAPHENE_MAX_CREDIT_OFFER_SECS = GRAPHENE_MAX_CREDIT_OFFER_DAYS * 86400;\n/// How long a credit deal will be kept, in days\nconstexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_DAYS = 380;\n/// How long a credit deal will be kept, in seconds\nconstexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_SECS = GRAPHENE_MAX_CREDIT_DEAL_DAYS * 86400;\n\n/// How many iterations to run in @c fee_schedule::set_fee()\nconstexpr size_t MAX_FEE_STABILIZATION_ITERATION  = 4;\n\n/**\n *  Reserved Account IDs with special meaning\n */\n///@{\n/// Represents the current committee members, two-week review period\n#define GRAPHENE_COMMITTEE_ACCOUNT (graphene::protocol::account_id_type(0))\n/// Represents the current witnesses\n#define GRAPHENE_WITNESS_ACCOUNT (graphene::protocol::account_id_type(1))\n/// Represents the current committee members\n#define GRAPHENE_RELAXED_COMMITTEE_ACCOUNT (graphene::protocol::account_id_type(2))\n/// Represents the canonical account with NO authority (nobody can access funds in null account)\n#define GRAPHENE_NULL_ACCOUNT (graphene::protocol::account_id_type(3))\n/// Represents the canonical account with WILDCARD authority (anybody can access funds in temp account)\n#define GRAPHENE_TEMP_ACCOUNT (graphene::protocol::account_id_type(4))\n/// Represents the canonical account for specifying you will vote directly (as opposed to a proxy)\n#define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::protocol::account_id_type(5))\n/// Sentinel value used in the scheduler.\n#define GRAPHENE_NULL_WITNESS (graphene::protocol::witness_id_type(0))\n///@}\n\n#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743))\n\n/// Maximum duration before a custom authority can expire (1 month)\n#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS (60*60*24*30)\n/// Maximum number of custom authorities a particular account can set\n#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT 10\n/// Maximum number of custom authorities a particular account can set for a particular operation\n#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT_OP 3\n/// Maximum number of restrictions a custom authority can contain\n#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_RESTRICTIONS 10\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/credit_offer.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @brief Create a new credit offer\n    * @ingroup operations\n    *\n    * A credit offer is a fund that can be used by other accounts who provide certain collateral.\n    */\n   struct credit_offer_create_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee             = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset           fee;                   ///< Operation fee\n      account_id_type owner_account;         ///< Owner of the credit offer\n      asset_id_type   asset_type;            ///< Asset type in the credit offer\n      share_type      balance;               ///< Usable amount in the credit offer\n      uint32_t        fee_rate = 0;          ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM\n      uint32_t        max_duration_seconds = 0; ///< The time limit that borrowed funds should be repaid\n      share_type      min_deal_amount;          ///< Minimum amount to borrow for each new deal\n      bool            enabled = false;          ///< Whether this offer is available\n      time_point_sec  auto_disable_time;        ///< The time when this offer will be disabled automatically\n\n      /// Types and rates of acceptable collateral\n      flat_map<asset_id_type, price>          acceptable_collateral;\n\n      /// Allowed borrowers and their maximum amounts to borrow. No limitation if empty.\n      flat_map<account_id_type, share_type>   acceptable_borrowers;\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return owner_account; }\n      void            validate()const override;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    * @brief Delete a credit offer\n    * @ingroup operations\n    */\n   struct credit_offer_delete_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 0; };\n\n      asset                fee;                ///< Operation fee\n      account_id_type      owner_account;      ///< The account who owns the credit offer\n      credit_offer_id_type offer_id;           ///< ID of the credit offer\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return owner_account; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief Update a credit offer\n    * @ingroup operations\n    */\n   struct credit_offer_update_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee             = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset                    fee;                   ///< Operation fee\n      account_id_type          owner_account;         ///< Owner of the credit offer\n      credit_offer_id_type     offer_id;              ///< ID of the credit offer\n      optional<asset>          delta_amount;          ///< Delta amount, optional\n      optional<uint32_t>       fee_rate;              ///< New fee rate, optional\n      optional<uint32_t>       max_duration_seconds;  ///< New repayment time limit, optional\n      optional<share_type>     min_deal_amount;       ///< Minimum amount to borrow for each new deal, optional\n      optional<bool>           enabled;               ///< Whether this offer is available, optional\n      optional<time_point_sec> auto_disable_time;     ///< New time to disable automatically, optional\n\n      /// New types and rates of acceptable collateral, optional\n      optional<flat_map<asset_id_type, price>>          acceptable_collateral;\n\n      /// New allowed borrowers and their maximum amounts to borrow, optional\n      optional<flat_map<account_id_type, share_type>>   acceptable_borrowers;\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return owner_account; }\n      void            validate()const override;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /// Defines automatic repayment types\n   enum class credit_deal_auto_repayment_type\n   {\n      /// Do not repay automatically\n      no_auto_repayment = 0,\n      /// Automatically repay fully when and only when the account balance is sufficient\n      only_full_repayment = 1,\n      /// Automatically repay as much as possible using available account balance\n      allow_partial_repayment = 2,\n      /// Total number of available automatic repayment types\n      CDAR_TYPE_COUNT = 3\n   };\n\n   /**\n    * @brief Accept a credit offer, thereby creating a credit deal\n    * @ingroup operations\n    */\n   struct credit_offer_accept_operation : public base_operation\n   {\n      struct ext\n      {\n         /// After the core-2595 hard fork, the account can specify whether and how to automatically repay\n         fc::optional<uint8_t> auto_repay;\n      };\n\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          borrower;           ///< The account who accepts the offer\n      credit_offer_id_type     offer_id;           ///< ID of the credit offer\n      asset                    borrow_amount;      ///< The amount to borrow\n      asset                    collateral;         ///< The collateral\n      uint32_t                 max_fee_rate = 0;   ///< The maximum acceptable fee rate\n      uint32_t                 min_duration_seconds = 0; ///< The minimum acceptable duration\n\n      extension<ext> extensions; ///< Extensions\n\n      account_id_type fee_payer()const { return borrower; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief Repay a credit deal\n    * @ingroup operations\n    */\n   struct credit_deal_repay_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who repays to the credit offer\n      credit_deal_id_type      deal_id;            ///< ID of the credit deal\n      asset                    repay_amount;       ///< The amount to repay\n      asset                    credit_fee;         ///< The credit fee relative to the amount to repay\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief A credit deal expired without being fully repaid\n    * @ingroup operations\n    * @note This is a virtual operation.\n    */\n   struct credit_deal_expired_operation : public base_operation\n   {\n      struct fee_params_t {};\n\n      credit_deal_expired_operation() = default;\n\n      credit_deal_expired_operation( const credit_deal_id_type& did, const credit_offer_id_type& oid,\n            const account_id_type& o, const account_id_type& b, const asset& u, const asset& c, const uint32_t fr)\n      : deal_id(did), offer_id(oid), offer_owner(o), borrower(b), unpaid_amount(u), collateral(c), fee_rate(fr)\n      { /* Nothing to do */ }\n\n      asset                    fee;                ///< Only for compatibility, unused\n      credit_deal_id_type      deal_id;            ///< ID of the credit deal\n      credit_offer_id_type     offer_id;           ///< ID of the credit offer\n      account_id_type          offer_owner;        ///< Owner of the credit offer\n      account_id_type          borrower;           ///< The account who repays to the credit offer\n      asset                    unpaid_amount;      ///< The amount that is unpaid\n      asset                    collateral;         ///< The collateral liquidated\n      uint32_t                 fee_rate = 0;       ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM\n\n      account_id_type fee_payer()const { return borrower; }\n      void            validate()const override { FC_ASSERT( !\"virtual operation\" ); }\n\n      /// This is a virtual operation; there is no fee\n      share_type      calculate_fee(const fee_params_t&)const { return 0; }\n   };\n\n   /**\n    * @brief Update a credit deal\n    * @ingroup operations\n    */\n   struct credit_deal_update_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who owns the credit deal\n      credit_deal_id_type      deal_id;            ///< ID of the credit deal\n      uint8_t                  auto_repay;         ///< The specified automatic repayment type\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const override;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::credit_offer_create_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::credit_offer_delete_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::credit_offer_update_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::credit_offer_accept_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::credit_deal_repay_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::credit_deal_expired_operation::fee_params_t, ) // VIRTUAL\nFC_REFLECT( graphene::protocol::credit_deal_update_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::credit_offer_create_operation,\n            (fee)\n            (owner_account)\n            (asset_type)\n            (balance)\n            (fee_rate)\n            (max_duration_seconds)\n            (min_deal_amount)\n            (enabled)\n            (auto_disable_time)\n            (acceptable_collateral)\n            (acceptable_borrowers)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::credit_offer_delete_operation,\n            (fee)\n            (owner_account)\n            (offer_id)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::credit_offer_update_operation,\n            (fee)\n            (owner_account)\n            (offer_id)\n            (delta_amount)\n            (fee_rate)\n            (max_duration_seconds)\n            (min_deal_amount)\n            (enabled)\n            (auto_disable_time)\n            (acceptable_collateral)\n            (acceptable_borrowers)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::credit_offer_accept_operation::ext,\n            (auto_repay)\n          )\n\nFC_REFLECT( graphene::protocol::credit_offer_accept_operation,\n            (fee)\n            (borrower)\n            (offer_id)\n            (borrow_amount)\n            (collateral)\n            (max_fee_rate)\n            (min_duration_seconds)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::credit_deal_repay_operation,\n            (fee)\n            (account)\n            (deal_id)\n            (repay_amount)\n            (credit_fee)\n            (extensions)\n          )\n\nFC_REFLECT( graphene::protocol::credit_deal_expired_operation,\n            (fee)\n            (deal_id)\n            (offer_id)\n            (offer_owner)\n            (borrower)\n            (unpaid_amount)\n            (collateral)\n            (fee_rate)\n          )\n\nFC_REFLECT( graphene::protocol::credit_deal_update_operation,\n            (fee)\n            (account)\n            (deal_id)\n            (auto_repay)\n            (extensions)\n          )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::ext )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_params_t )\n// Note: credit_deal_expired_operation is virtual so no external serialization for its fee_params_t\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_expired_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/custom.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol { \n\n   /**\n    * @brief provides a generic way to add higher level protocols on top of witness consensus\n    * @ingroup operations\n    *\n    * There is no validation for this operation other than that required auths are valid and a fee\n    * is paid that is appropriate for the data contained.\n    */\n   struct custom_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; \n         uint32_t price_per_kbyte = 10;\n      };\n\n      asset                     fee;\n      account_id_type           payer;\n      flat_set<account_id_type> required_auths;\n      uint16_t                  id = 0;\n      vector<char>              data;\n\n      account_id_type   fee_payer()const { return payer; }\n      void              validate()const;\n      share_type        calculate_fee(const fee_params_t& k)const;\n      void              get_required_active_authorities( flat_set<account_id_type>& auths )const {\n         auths.insert( required_auths.begin(), required_auths.end() );\n      }\n   };\n\n} } // namespace graphene::protocol\n\nFC_REFLECT( graphene::protocol::custom_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::custom_operation, (fee)(payer)(required_auths)(id)(data) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/custom_authority.hpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/authority.hpp>\n#include <graphene/protocol/restriction.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @brief Create a new custom authority\n    * @ingroup operations\n    */\n   struct custom_authority_create_operation : public base_operation {\n      struct fee_params_t {\n         uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_byte = GRAPHENE_BLOCKCHAIN_PRECISION / 10;\n      };\n\n      /// Operation fee\n      asset fee;\n      /// Account which is setting the custom authority; also pays the fee\n      account_id_type account;\n      /// Whether the custom authority is enabled or not\n      bool enabled;\n      /// Date when custom authority becomes active\n      time_point_sec valid_from;\n      /// Expiration date for custom authority\n      time_point_sec valid_to;\n      /// Tag of the operation this custom authority can authorize\n      unsigned_int operation_type;\n      /// Authentication requirements for the custom authority\n      authority auth;\n      /// Restrictions on operations this custom authority can authenticate\n      vector<restriction> restrictions;\n\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return account; }\n      void validate()const;\n      share_type calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    * @brief Update a custom authority\n    * @ingroup operations\n    */\n   struct custom_authority_update_operation : public base_operation {\n      struct fee_params_t {\n         uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_byte = GRAPHENE_BLOCKCHAIN_PRECISION / 10;\n      };\n\n      /// Operation fee\n      asset fee;\n      /// Account which owns the custom authority to update; also pays the fee\n      account_id_type account;\n      /// ID of the custom authority to update\n      custom_authority_id_type authority_to_update;\n      /// Change to whether the custom authority is enabled or not\n      optional<bool> new_enabled;\n      /// Change to the custom authority begin date\n      optional<time_point_sec> new_valid_from;\n      /// Change to the custom authority expiration date\n      optional<time_point_sec> new_valid_to;\n      /// Change to the authentication for the custom authority\n      optional<authority> new_auth;\n      /// Set of IDs of restrictions to remove\n      flat_set<uint16_t> restrictions_to_remove;\n      /// Vector of new restrictions\n      vector<restriction> restrictions_to_add;\n\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return account; }\n      void validate()const;\n      share_type calculate_fee(const fee_params_t& k)const;\n   };\n\n\n   /**\n    * @brief Delete a custom authority\n    * @ingroup operations\n    */\n   struct custom_authority_delete_operation : public base_operation {\n      struct fee_params_t { uint64_t fee =  GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      /// Operation fee\n      asset fee;\n      /// Account which owns the custom authority to update; also pays the fee\n      account_id_type account;\n      /// ID of the custom authority to delete\n      custom_authority_id_type authority_to_delete;\n\n      extensions_type extensions;\n\n      account_id_type fee_payer()const { return account; }\n      void validate()const;\n      share_type calculate_fee(const fee_params_t& k)const { return k.fee; }\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT(graphene::protocol::custom_authority_create_operation::fee_params_t, (basic_fee)(price_per_byte))\nFC_REFLECT(graphene::protocol::custom_authority_update_operation::fee_params_t, (basic_fee)(price_per_byte))\nFC_REFLECT(graphene::protocol::custom_authority_delete_operation::fee_params_t, (fee))\n\nFC_REFLECT(graphene::protocol::custom_authority_create_operation,\n           (fee)(account)(enabled)(valid_from)(valid_to)(operation_type)(auth)(restrictions)(extensions))\n\nFC_REFLECT(graphene::protocol::custom_authority_update_operation,\n           (fee)(account)(authority_to_update)(new_enabled)(new_valid_from)\n           (new_valid_to)(new_auth)(restrictions_to_remove)(restrictions_to_add)(extensions))\nFC_REFLECT(graphene::protocol::custom_authority_delete_operation, (fee)(account)(authority_to_delete)(extensions))\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/exceptions.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/exception/exception.hpp>\n\n#define GRAPHENE_ASSERT( expr, exc_type, FORMAT, ... )                \\\n   FC_MULTILINE_MACRO_BEGIN                                           \\\n   if( !(expr) )                                                      \\\n      FC_THROW_EXCEPTION( exc_type, FORMAT, __VA_ARGS__ );            \\\n   FC_MULTILINE_MACRO_END\n\nnamespace graphene { namespace protocol {\n\n   FC_DECLARE_EXCEPTION( protocol_exception, 4000000 )\n\n   FC_DECLARE_DERIVED_EXCEPTION( transaction_exception,      protocol_exception, 4010000 )\n\n   FC_DECLARE_DERIVED_EXCEPTION( tx_missing_active_auth,     transaction_exception, 4010001 )\n   FC_DECLARE_DERIVED_EXCEPTION( tx_missing_owner_auth,      transaction_exception, 4010002 )\n   FC_DECLARE_DERIVED_EXCEPTION( tx_missing_other_auth,      transaction_exception, 4010003 )\n   FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_sig,          transaction_exception, 4010004 )\n   FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate_sig,           transaction_exception, 4010005 )\n   FC_DECLARE_DERIVED_EXCEPTION( invalid_committee_approval, transaction_exception, 4010006 )\n   FC_DECLARE_DERIVED_EXCEPTION( insufficient_fee,           transaction_exception, 4010007 )\n\n} } // graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/ext.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/io/varint.hpp>\n#include <fc/io/raw_fwd.hpp>\n#include <fc/reflect/reflect.hpp>\n#include <fc/exception/exception.hpp>\n\nnamespace graphene { namespace protocol {\n\nusing fc::unsigned_int;\n\ntemplate< typename T >\nstruct extension\n{\n   extension() {}\n\n   T value;\n};\n\ntemplate< typename T >\nstruct graphene_extension_pack_count_visitor\n{\n   graphene_extension_pack_count_visitor( const T& v ) : value(v) {}\n\n   template<typename Member, class Class, Member (Class::*member)>\n   void operator()( const char* name )const\n   {\n      count += ((value.*member).valid()) ? 1 : 0;\n   }\n\n   const T& value;\n   mutable uint32_t count = 0;\n};\n\ntemplate< typename Stream, typename T >\nstruct graphene_extension_pack_read_visitor\n{\n   graphene_extension_pack_read_visitor( Stream& s, const T& v, uint32_t _max_depth )\n   : stream(s), value(v), max_depth(_max_depth - 1)\n   {\n      FC_ASSERT( _max_depth > 0 );\n   }\n\n   template<typename Member, class Class, Member (Class::*member)>\n   void operator()( const char* name )const\n   {\n      if( (value.*member).valid() )\n      {\n         fc::raw::pack( stream, unsigned_int( which ), max_depth );\n         fc::raw::pack( stream, *(value.*member), max_depth );\n      }\n      ++which;\n   }\n\n   Stream& stream;\n   const T& value;\n   mutable uint32_t which = 0;\n   const uint32_t max_depth;\n};\n\n\ntemplate< typename Stream, typename T >\nstruct graphene_extension_unpack_visitor\n{\n   graphene_extension_unpack_visitor( Stream& s, T& v, uint32_t _max_depth )\n   : stream(s), value(v), max_depth(_max_depth - 1)\n   {\n      FC_ASSERT( _max_depth > 0 );\n      unsigned_int c;\n      fc::raw::unpack( stream, c, max_depth );\n      count_left = c.value;\n      maybe_read_next_which();\n   }\n\n   void maybe_read_next_which()const\n   {\n      if( count_left > 0 )\n      {\n         unsigned_int w;\n         fc::raw::unpack( stream, w, max_depth );\n         next_which = w.value;\n      }\n   }\n\n   template< typename Member, class Class, Member (Class::*member)>\n   void operator()( const char* name )const\n   {\n      if( (count_left > 0) && (which == next_which) )\n      {\n         typename Member::value_type temp;\n         fc::raw::unpack( stream, temp, max_depth );\n         (value.*member) = temp;\n         --count_left;\n         maybe_read_next_which();\n      }\n      else\n         (value.*member).reset();\n      ++which;\n   }\n\n   mutable uint32_t      which = 0;\n   mutable uint32_t next_which = 0;\n   mutable uint32_t count_left = 0;\n\n   Stream& stream;\n   T& value;\n   const uint32_t max_depth;\n};\n\n} } // graphene::protocol\n\nnamespace fc {\n\ntemplate< typename T >\nstruct graphene_extension_from_variant_visitor\n{\n   graphene_extension_from_variant_visitor( const variant_object& v, T& val, uint32_t max_depth )\n      : vo( v ), value( val ), _max_depth(max_depth - 1)\n   {\n      FC_ASSERT( max_depth > 0, \"Recursion depth exceeded!\" );\n      count_left = vo.size();\n   }\n\n   template<typename Member, class Class, Member (Class::*member)>\n   void operator()( const char* name )const\n   {\n      auto it = vo.find(name);\n      if( it != vo.end() )\n      {\n         from_variant( it->value(), (value.*member), _max_depth );\n         assert( count_left > 0 );    // x.find(k) returns true for n distinct values of k only if x.size() >= n\n         --count_left;\n      }\n   }\n\n   const variant_object& vo;\n   T& value;\n   const uint32_t _max_depth;\n   mutable uint32_t count_left = 0;\n};\n\ntemplate< typename T >\nvoid from_variant( const fc::variant& var, graphene::protocol::extension<T>& value, uint32_t max_depth )\n{\n   value = graphene::protocol::extension<T>();\n   if( var.is_null() )\n      return;\n   if( var.is_array() )\n   {\n      FC_ASSERT( var.size() == 0 );\n      return;\n   }\n\n   graphene_extension_from_variant_visitor<T> vtor( var.get_object(), value.value, max_depth );\n   fc::reflector<T>::visit( vtor );\n   FC_ASSERT( vtor.count_left == 0 );    // unrecognized extension throws here\n}\n\ntemplate< typename T >\nstruct graphene_extension_to_variant_visitor\n{\n   graphene_extension_to_variant_visitor( const T& v, uint32_t max_depth ) : value(v), mvo(max_depth) {}\n\n   template<typename Member, class Class, Member (Class::*member)>\n   void operator()( const char* name )const\n   {\n      if( (value.*member).valid() )\n         mvo( name, value.*member );\n   }\n\n   const T& value;\n   mutable limited_mutable_variant_object mvo;\n};\n\ntemplate< typename T >\nvoid to_variant( const graphene::protocol::extension<T>& value, fc::variant& var, uint32_t max_depth )\n{\n   graphene_extension_to_variant_visitor<T> vtor( value.value, max_depth );\n   fc::reflector<T>::visit( vtor );\n   var = vtor.mvo;\n}\n\nnamespace raw {\n\ntemplate< typename Stream, typename T >\nvoid pack( Stream& stream, const graphene::protocol::extension<T>& value, uint32_t _max_depth=FC_PACK_MAX_DEPTH )\n{\n   FC_ASSERT( _max_depth > 0 );\n   --_max_depth;\n   graphene::protocol::graphene_extension_pack_count_visitor<T> count_vtor( value.value );\n   fc::reflector<T>::visit( count_vtor );\n   fc::raw::pack( stream, unsigned_int( count_vtor.count ), _max_depth );\n   graphene::protocol::graphene_extension_pack_read_visitor<Stream,T> read_vtor( stream, value.value, _max_depth );\n   fc::reflector<T>::visit( read_vtor );\n}\n\n\ntemplate< typename Stream, typename T >\nvoid unpack( Stream& s, graphene::protocol::extension<T>& value, uint32_t _max_depth=FC_PACK_MAX_DEPTH )\n{\n   FC_ASSERT( _max_depth > 0 );\n   --_max_depth;\n   value = graphene::protocol::extension<T>();\n   graphene::protocol::graphene_extension_unpack_visitor<Stream, T> vtor( s, value.value, _max_depth );\n   fc::reflector<T>::visit( vtor );\n   FC_ASSERT( vtor.count_left == 0 ); // unrecognized extension throws here\n}\n\n} // fc::raw\n\ntemplate<typename T> struct get_typename< graphene::protocol::extension<T> >\n{ \n   static const char* name()\n   { \n      static std::string n = std::string(\"graphene::protocol::extension<\") \n         + fc::get_typename<T>::name() + std::string(\">\");\n      return n.c_str();\n   } \n};\n\n\n} // fc\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/fba.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol {\n\nstruct fba_distribute_operation : public base_operation\n{\n   struct fee_params_t {};\n\n   asset fee;   // always zero\n   account_id_type account_id;\n   // We use object_id_type because this is an implementaton object, and therefore is not known to the protocol library\n   object_id_type fba_id;\n   share_type amount;\n\n   account_id_type fee_payer()const { return account_id; }\n   void validate()const { FC_ASSERT( false ); }\n   share_type calculate_fee(const fee_params_t& k)const { return 0; }\n};\n\n} }\n\nFC_REFLECT( graphene::protocol::fba_distribute_operation::fee_params_t,  )\n\nFC_REFLECT( graphene::protocol::fba_distribute_operation, (fee)(account_id)(fba_id)(amount) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::fba_distribute_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/fee_schedule.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n\nnamespace graphene { namespace protocol {\n\n   template<typename T> struct transform_to_fee_parameters;\n   template<typename ...T>\n   struct transform_to_fee_parameters<fc::static_variant<T...>>\n   {\n      using type = fc::static_variant< typename T::fee_params_t... >;\n   };\n   using fee_parameters = transform_to_fee_parameters<operation>::type;\n\n   template<typename Operation>\n   class fee_helper {\n     public:\n      const typename Operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( typename Operation::fee_params_t() );\n         FC_ASSERT( itr != parameters.end() );\n         return itr->template get<typename Operation::fee_params_t>();\n      }\n   };\n\n   template<>\n   class fee_helper<account_create_operation> {\n     public:\n      const account_create_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( account_create_operation::fee_params_t() );\n         FC_ASSERT( itr != parameters.end() );\n         return itr->get<account_create_operation::fee_params_t>();\n      }\n      typename account_create_operation::fee_params_t& get(fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( account_create_operation::fee_params_t() );\n         FC_ASSERT( itr != parameters.end() );\n         return itr->get<account_create_operation::fee_params_t>();\n      }\n   };\n\n   template<>\n   class fee_helper<bid_collateral_operation> {\n     public:\n      const bid_collateral_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( bid_collateral_operation::fee_params_t() );\n         if ( itr != parameters.end() )\n            return itr->get<bid_collateral_operation::fee_params_t>();\n\n         static bid_collateral_operation::fee_params_t bid_collateral_dummy;\n         bid_collateral_dummy.fee = fee_helper<call_order_update_operation>().cget(parameters).fee;\n         return bid_collateral_dummy;\n      }\n   };\n\n   template<>\n   class fee_helper<asset_update_issuer_operation> {\n     public:\n      const asset_update_issuer_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( asset_update_issuer_operation::fee_params_t() );\n         if ( itr != parameters.end() )\n            return itr->get<asset_update_issuer_operation::fee_params_t>();\n\n         static asset_update_issuer_operation::fee_params_t dummy;\n         dummy.fee = fee_helper<asset_update_operation>().cget(parameters).fee;\n         return dummy;\n      }\n   };\n\n   template<>\n   class fee_helper<asset_claim_pool_operation> {\n     public:\n      const asset_claim_pool_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( asset_claim_pool_operation::fee_params_t() );\n         if ( itr != parameters.end() )\n            return itr->get<asset_claim_pool_operation::fee_params_t>();\n\n         static asset_claim_pool_operation::fee_params_t asset_claim_pool_dummy;\n         asset_claim_pool_dummy.fee = fee_helper<asset_fund_fee_pool_operation>().cget(parameters).fee;\n         return asset_claim_pool_dummy;\n      }\n   };\n\n   template<>\n   class fee_helper<ticket_create_operation> {\n     public:\n      const ticket_create_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         static ticket_create_operation::fee_params_t param;\n         return param;\n      }\n   };\n\n   template<>\n   class fee_helper<ticket_update_operation> {\n     public:\n      const ticket_update_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         static ticket_update_operation::fee_params_t param;\n         return param;\n      }\n   };\n\n   template<>\n   class fee_helper<htlc_create_operation> {\n     public:\n      const htlc_create_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( htlc_create_operation::fee_params_t() );\n         if ( itr != parameters.end() )\n            return itr->get<htlc_create_operation::fee_params_t>();\n\n         static htlc_create_operation::fee_params_t htlc_create_operation_fee_dummy;\n         return htlc_create_operation_fee_dummy;\n      }\n   };\n\n   template<>\n   class fee_helper<htlc_redeem_operation> {\n     public:\n      const htlc_redeem_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( htlc_redeem_operation::fee_params_t() );\n         if ( itr != parameters.end() )\n            return itr->get<htlc_redeem_operation::fee_params_t>();\n\n         static htlc_redeem_operation::fee_params_t htlc_redeem_operation_fee_dummy;\n         return htlc_redeem_operation_fee_dummy;\n      }\n   };\n   template<>\n   class fee_helper<htlc_extend_operation> {\n     public:\n      const htlc_extend_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const\n      {\n         auto itr = parameters.find( htlc_extend_operation::fee_params_t() );\n         if ( itr != parameters.end() )\n            return itr->get<htlc_extend_operation::fee_params_t>();\n\n         static htlc_extend_operation::fee_params_t htlc_extend_operation_fee_dummy;\n         return htlc_extend_operation_fee_dummy;\n      }\n   };\n   /**\n    *  @brief contains all of the parameters necessary to calculate the fee for any operation\n    */\n   struct fee_schedule\n   {\n      static const fee_schedule& get_default();\n\n      /**\n       *  Finds the appropriate fee parameter struct for the operation\n       *  and then calculates the appropriate fee in CORE asset.\n       */\n      asset calculate_fee( const operation& op )const;\n      /**\n       *  Finds the appropriate fee parameter struct for the operation\n       *  and then calculates the appropriate fee in an asset specified\n       *  implicitly by core_exchange_rate.\n       */\n      asset calculate_fee( const operation& op, const price& core_exchange_rate )const;\n      /**\n       *  Updates the operation with appropriate fee and returns the fee.\n       */\n      asset set_fee( operation& op, const price& core_exchange_rate = price::unit_price() )const;\n\n      void zero_all_fees();\n\n      /**\n       *  Validates all of the parameters are present and accounted for.\n       */\n      void validate()const {}\n\n      template<typename Operation>\n      const typename Operation::fee_params_t& get()const\n      {\n         return fee_helper<Operation>().cget(parameters);\n      }\n      template<typename Operation>\n      typename Operation::fee_params_t& get()\n      {\n         return fee_helper<Operation>().get(parameters);\n      }\n      template<typename Operation>\n      bool exists()const\n      {\n         auto itr = parameters.find(typename Operation::fee_params_t());\n         return itr != parameters.end();\n      }\n\n      /**\n       *  @note must be sorted by fee_parameters.which() and have no duplicates\n       */\n      fee_parameters::flat_set_type parameters;\n      uint32_t                      scale = GRAPHENE_100_PERCENT; ///< fee * scale / GRAPHENE_100_PERCENT\n   private:\n      static fee_schedule get_default_impl();\n   };\n\n   using fee_schedule_type = fee_schedule;\n\n} } // graphene::protocol\n\nFC_REFLECT_TYPENAME( graphene::protocol::fee_parameters )\nFC_REFLECT( graphene::protocol::fee_schedule, (parameters)(scale) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::fee_schedule )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/htlc.hpp",
    "content": "/*\n * Copyright (c) 2018 jmjatlanta and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/time.hpp>\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/memo.hpp>\n#include <algorithm> // std::max\n\nnamespace graphene { namespace protocol {\n      typedef fc::ripemd160    htlc_algo_ripemd160;\n      typedef fc::sha1         htlc_algo_sha1;\n      typedef fc::sha256       htlc_algo_sha256;\n      typedef fc::hash160      htlc_algo_hash160;\n\n      typedef fc::static_variant<\n         htlc_algo_ripemd160,\n         htlc_algo_sha1,\n         htlc_algo_sha256,\n         htlc_algo_hash160\n      > htlc_hash;\n\n      struct htlc_create_operation : public base_operation \n      {\n         struct fee_params_t {\n            uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n            uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         };\n\n         // paid to network\n         asset fee;\n         // where the held monies are to come from\n         account_id_type from;\n         // where the held monies will go if the preimage is provided\n         account_id_type to;\n         // the amount to hold\n         asset amount;\n         // the (typed) hash of the preimage\n         htlc_hash preimage_hash;\n         // the size of the preimage\n         uint16_t preimage_size;\n         // The time the funds will be returned to the source if not claimed\n         uint32_t claim_period_seconds;\n         \n         // additional extensions\n         struct additional_options_type\n         {\n            fc::optional<memo_data> memo;\n         };\n         extension<additional_options_type> extensions;\n\n         /***\n          * @brief Does simple validation of this object\n          */\n         void validate()const;\n         \n         /**\n          * @brief who will pay the fee\n          */\n         account_id_type fee_payer()const { return from; }\n\n         /****\n          * @brief calculates the fee to be paid for this operation\n          */\n         share_type calculate_fee(const fee_params_t& fee_params, uint32_t fee_per_kb)const;\n      };\n\n      struct htlc_redeem_operation : public base_operation\n      {\n         struct fee_params_t {\n            uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n            uint64_t fee_per_kb = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         };\n         \n         // paid to network\n         asset fee;\n         // the object we are attempting to update\n         htlc_id_type htlc_id;\n         // who is attempting to update the transaction\n         account_id_type redeemer;\n         // the preimage (not used if after epoch timeout)\n         std::vector<char> preimage;\n         // for future expansion\n         extensions_type extensions; \n\n         /***\n          * @brief Perform obvious checks to validate this object\n          */\n    \t   void validate()const;\n         \n         /**\n          * @brief Who is to pay the fee\n          */\n         account_id_type fee_payer()const { return redeemer; }\n\n         /****\n          * @brief calculates the fee to be paid for this operation\n          */\n         share_type calculate_fee(const fee_params_t& fee_params)const;\n      };\n\n      /**\n       * virtual op to assist with notifying related parties\n       */\n      struct htlc_redeemed_operation : public base_operation\n      {\n         struct fee_params_t {};\n\n         htlc_redeemed_operation() {}\n         htlc_redeemed_operation( htlc_id_type htlc_id, account_id_type from, account_id_type to,\n               account_id_type redeemer, asset amount, const htlc_hash& preimage_hash, uint16_t preimage_size,\n               const std::vector<char>& preimage ) :\n               htlc_id(htlc_id), from(from), to(to), redeemer(redeemer), amount(amount),\n               htlc_preimage_hash(preimage_hash), htlc_preimage_size(preimage_size), preimage(preimage) {}\n\n         account_id_type fee_payer()const { return to; }\n         void validate()const { FC_ASSERT( !\"virtual operation\" ); }\n\n         share_type      calculate_fee(const fee_params_t& k)const { return 0; }\n\n         htlc_id_type htlc_id;\n         account_id_type from, to, redeemer;\n         asset amount;\n         htlc_hash htlc_preimage_hash;\n         uint16_t htlc_preimage_size;\n\n         asset fee;\n         std::vector<char> preimage;\n      };\n\n      struct htlc_extend_operation : public base_operation\n      {\n         struct fee_params_t {\n            uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n            uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         };\n         \n         // paid to network\n         asset fee;\n         // the object we are attempting to update\n         htlc_id_type htlc_id;\n         // who is attempting to update the transaction\n         account_id_type update_issuer;\n         // how much to add\n         uint32_t seconds_to_add;\n         // for future expansion\n         extensions_type extensions; \n\n         /***\n          * @brief Perform obvious checks to validate this object\n          */\n         void validate()const;\n         \n         /**\n          * @brief Who is to pay the fee\n          */\n         account_id_type fee_payer()const { return update_issuer; }\n\n         /****\n          * @brief calculates the fee to be paid for this operation\n          */\n         share_type calculate_fee(const fee_params_t& fee_params)const;\n      };\n\n      struct htlc_refund_operation : public base_operation\n      {\n         struct fee_params_t {};\n\n         htlc_refund_operation(){}\n         htlc_refund_operation( const htlc_id_type& htlc_id,\n               const account_id_type& htlc_from, const account_id_type& htlc_to, const asset& amount,\n               const htlc_hash& preimage_hash, uint16_t preimage_size ) :\n               htlc_id(htlc_id), to(htlc_from), original_htlc_recipient(htlc_to), htlc_amount(amount),\n               htlc_preimage_hash(preimage_hash), htlc_preimage_size(preimage_size) {}\n\n         account_id_type fee_payer()const { return to; }\n         void            validate()const { FC_ASSERT( !\"virtual operation\" ); }\n\n         /// This is a virtual operation; there is no fee\n         share_type      calculate_fee(const fee_params_t& k)const { return 0; }\n\n         asset fee;\n\n         htlc_id_type htlc_id; // of the associated htlc object; it is deleted during emittance of this operation\n         account_id_type to, original_htlc_recipient;\n         account_id_type htlc_from() const { return to; };\n         account_id_type htlc_to()   const { return original_htlc_recipient; };\n         asset htlc_amount;\n         htlc_hash htlc_preimage_hash;\n         uint16_t htlc_preimage_size;\n      };\n   } \n}\n\nFC_REFLECT_TYPENAME( graphene::protocol::htlc_hash )\n\nFC_REFLECT( graphene::protocol::htlc_create_operation::fee_params_t, (fee) (fee_per_day) )\nFC_REFLECT( graphene::protocol::htlc_create_operation::additional_options_type, (memo))\nFC_REFLECT( graphene::protocol::htlc_redeem_operation::fee_params_t, (fee) (fee_per_kb) )\nFC_REFLECT( graphene::protocol::htlc_redeemed_operation::fee_params_t, ) // VIRTUAL\nFC_REFLECT( graphene::protocol::htlc_extend_operation::fee_params_t, (fee) (fee_per_day))\nFC_REFLECT( graphene::protocol::htlc_refund_operation::fee_params_t, ) // VIRTUAL\n\nFC_REFLECT( graphene::protocol::htlc_create_operation,\n      (fee)(from)(to)(amount)(preimage_hash)(preimage_size)(claim_period_seconds)(extensions))\nFC_REFLECT( graphene::protocol::htlc_redeem_operation, (fee)(htlc_id)(redeemer)(preimage)(extensions))\nFC_REFLECT( graphene::protocol::htlc_redeemed_operation,\n      (fee)(htlc_id)(from)(to)(redeemer)(amount)(htlc_preimage_hash)(htlc_preimage_size)(preimage))\nFC_REFLECT( graphene::protocol::htlc_extend_operation, (fee)(htlc_id)(update_issuer)(seconds_to_add)(extensions))\nFC_REFLECT( graphene::protocol::htlc_refund_operation,\n      (fee)(htlc_id)(to)(original_htlc_recipient)(htlc_amount)(htlc_preimage_hash)(htlc_preimage_size))\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeemed_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_refund_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/liquidity_pool.hpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @brief Create a new liquidity pool\n    * @ingroup operations\n    */\n   struct liquidity_pool_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;                         ///< Operation fee\n      account_id_type account;                     ///< The account who creates the liquidity pool\n      asset_id_type   asset_a;                     ///< Type of the first asset in the pool\n      asset_id_type   asset_b;                     ///< Type of the second asset in the pool\n      asset_id_type   share_asset;                 ///< Type of the share asset aka the LP token\n      uint16_t        taker_fee_percent = 0;       ///< Taker fee percent\n      uint16_t        withdrawal_fee_percent = 0;  ///< Withdrawal fee percent\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Delete a liquidity pool\n    * @ingroup operations\n    */\n   struct liquidity_pool_delete_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 0; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who owns the liquidity pool\n      liquidity_pool_id_type   pool;               ///< ID of the liquidity pool\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Update a liquidity pool\n    * @ingroup operations\n    */\n   struct liquidity_pool_update_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who owns the liquidity pool\n      liquidity_pool_id_type   pool;               ///< ID of the liquidity pool\n      optional<uint16_t>       taker_fee_percent;       ///< Taker fee percent\n      optional<uint16_t>       withdrawal_fee_percent;  ///< Withdrawal fee percent\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Deposit to a liquidity pool\n    * @ingroup operations\n    */\n   struct liquidity_pool_deposit_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION / 10; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who deposits to the liquidity pool\n      liquidity_pool_id_type   pool;               ///< ID of the liquidity pool\n      asset                    amount_a;           ///< The amount of the first asset to deposit\n      asset                    amount_b;           ///< The amount of the second asset to deposit\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Withdraw from a liquidity pool\n    * @ingroup operations\n    */\n   struct liquidity_pool_withdraw_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who withdraws from the liquidity pool\n      liquidity_pool_id_type   pool;               ///< ID of the liquidity pool\n      asset                    share_amount;       ///< The amount of the share asset to use\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Exchange with a liquidity pool\n    * @ingroup operations\n    * @note The result of this operation is a @ref generic_exchange_operation_result.\n    *       There are 3 fees in the result, stored in this order:\n    *       * maker market fee\n    *       * taker market fee\n    *       * liquidity pool taker fee\n    */\n   struct liquidity_pool_exchange_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who exchanges with the liquidity pool\n      liquidity_pool_id_type   pool;               ///< ID of the liquidity pool\n      asset                    amount_to_sell;     ///< The amount of one asset type to sell\n      asset                    min_to_receive;     ///< The minimum amount of the other asset type to receive\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::liquidity_pool_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::liquidity_pool_delete_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::liquidity_pool_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::liquidity_pool_deposit_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::liquidity_pool_withdraw_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::liquidity_pool_exchange_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::liquidity_pool_create_operation,\n            (fee)(account)(asset_a)(asset_b)(share_asset)\n            (taker_fee_percent)(withdrawal_fee_percent)(extensions) )\nFC_REFLECT( graphene::protocol::liquidity_pool_delete_operation,\n            (fee)(account)(pool)(extensions) )\nFC_REFLECT( graphene::protocol::liquidity_pool_update_operation,\n            (fee)(account)(pool)(taker_fee_percent)(withdrawal_fee_percent)(extensions) )\nFC_REFLECT( graphene::protocol::liquidity_pool_deposit_operation,\n            (fee)(account)(pool)(amount_a)(amount_b)(extensions) )\nFC_REFLECT( graphene::protocol::liquidity_pool_withdraw_operation,\n            (fee)(account)(pool)(share_amount)(extensions) )\nFC_REFLECT( graphene::protocol::liquidity_pool_exchange_operation,\n            (fee)(account)(pool)(amount_to_sell)(min_to_receive)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/market.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * Creates a take profit limit order\n    */\n   struct create_take_profit_order_action\n   {\n      /// Asset ID that will be used to pay operation fee for placing the take profit order\n      asset_id_type   fee_asset_id;\n      /// A percentage indicating how far the price of the take profit order differs from the original order\n      uint16_t        spread_percent = 0;\n      /// A percentage indicating how much amount to sell in the take profit order\n      uint16_t        size_percent = GRAPHENE_100_PERCENT;\n      /// How long the take profit order to be kept on the market\n      uint32_t        expiration_seconds = 0;\n      /// Whether to create another take profit order for this take profit order if this take profit order is matched\n      bool            repeat = false;\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      void            validate()const;\n   };\n\n   /// Automatic actions for limit orders\n   using limit_order_auto_action = static_variant< create_take_profit_order_action >;\n\n   /**\n    *  @class limit_order_create_operation\n    *  @brief instructs the blockchain to attempt to sell one asset for another\n    *  @ingroup operations\n    *\n    *  The blockchain will atempt to sell amount_to_sell.asset_id for as\n    *  much min_to_receive.asset_id as possible.  The fee will be paid by\n    *  the seller's account.  Market fees will apply as specified by the\n    *  issuer of both the selling asset and the receiving asset as\n    *  a percentage of the amount exchanged.\n    *\n    *  If either the selling asset or the receiving asset is white list\n    *  restricted, the order will only be created if the seller is on\n    *  the white list of the restricted asset type.\n    *\n    *  Market orders are matched in the order they are included\n    *  in the block chain.\n    */\n   struct limit_order_create_operation : public base_operation\n   {\n      /**\n       * Options to be used in @ref limit_order_create_operation\n       *\n       * @note this struct can be expanded by adding more options in the end.\n       */\n      struct options_type\n      {\n         /// Automatic actions when the limit order is filled or partially filled\n         optional< vector< limit_order_auto_action > > on_fill;\n      };\n\n      struct fee_params_t { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;\n      account_id_type seller;\n      asset           amount_to_sell;\n      asset           min_to_receive;\n\n      /// The order will be removed from the books if not filled by expiration\n      /// Upon expiration, all unsold asset will be returned to seller\n      time_point_sec expiration = time_point_sec::maximum();\n\n      /// If this flag is set the entire order must be filled or the operation is rejected\n      bool fill_or_kill = false;\n\n      using extensions_type = extension<options_type>; // note: will be jsonified to {...} but not [...]\n      extensions_type   extensions;\n\n      pair<asset_id_type,asset_id_type> get_market()const\n      {\n         return amount_to_sell.asset_id < min_to_receive.asset_id ?\n                std::make_pair(amount_to_sell.asset_id, min_to_receive.asset_id) :\n                std::make_pair(min_to_receive.asset_id, amount_to_sell.asset_id);\n      }\n      account_id_type fee_payer()const { return seller; }\n      void            validate()const;\n      price           get_price()const { return amount_to_sell / min_to_receive; }\n   };\n\n   /**\n    * @ingroup operations\n    * Used to update an existing limit order.\n    */\n   struct limit_order_update_operation : public base_operation\n   {\n       struct fee_params_t {\n           uint64_t fee = ( GRAPHENE_BLOCKCHAIN_PRECISION * 3 ) / 8;\n       };\n\n       asset fee;\n       account_id_type seller;\n       limit_order_id_type order;\n       optional<price> new_price;\n       optional<asset> delta_amount_to_sell;\n       optional<time_point_sec> new_expiration;\n       /// Automatic actions when the limit order is filled or partially filled\n       optional< vector< limit_order_auto_action > > on_fill;\n\n       extensions_type extensions;\n\n       account_id_type fee_payer() const { return seller; }\n       void validate() const override;\n   };\n\n   /**\n    *  @ingroup operations\n    *  Used to cancel an existing limit order. Both fee_pay_account and the\n    *  account to receive the proceeds must be the same as order->seller.\n    *\n    *  @return the amount actually refunded\n    */\n   struct limit_order_cancel_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 0; };\n\n      asset               fee;\n      limit_order_id_type order;\n      /** must be order->seller */\n      account_id_type     fee_paying_account;\n      extensions_type   extensions;\n\n      account_id_type fee_payer()const { return fee_paying_account; }\n      void            validate()const;\n   };\n\n   /**\n    *  @ingroup operations\n    *\n    *  This operation can be used to add collateral, cover, and adjust the margin call price for a particular user.\n    *\n    *  For prediction markets the collateral and debt must always be equal.\n    *\n    *  This operation will fail if it would trigger a margin call that couldn't be filled.  If the margin call hits\n    *  the call price limit then it will fail if the call price is above the settlement price.\n    *\n    *  @note this operation can be used to force a market order using the collateral without requiring outside funds.\n    */\n   struct call_order_update_operation : public base_operation\n   {\n      /**\n       * Options to be used in @ref call_order_update_operation.\n       *\n       * @note this struct can be expanded by adding more options in the end.\n       */\n      struct options_type\n      {\n         /// Maximum CR to maintain when selling collateral on margin call\n         optional<uint16_t> target_collateral_ratio;\n      };\n\n      /** this is slightly more expensive than limit orders, this pricing impacts prediction markets */\n      struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset               fee;\n      account_id_type     funding_account; ///< pays fee, collateral, and cover\n      asset               delta_collateral; ///< the amount of collateral to add to the margin position\n      asset               delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt\n\n      using extensions_type = extension<options_type>; // note: this will be jsonified to {...} but no longer [...]\n      extensions_type     extensions;\n\n      account_id_type fee_payer()const { return funding_account; }\n      void            validate()const;\n   };\n\n   /**\n    * @ingroup operations\n    *\n    * @note This is a virtual operation that is created while matching orders and\n    * emitted for the purpose of accurately tracking account history, accelerating\n    * a reindex.\n    */\n   struct fill_order_operation : public base_operation\n   {\n      struct fee_params_t {};\n\n      fill_order_operation(){}\n      fill_order_operation( object_id_type o, account_id_type a, asset p, asset r, asset f, price fp, bool m )\n         :order_id(o),account_id(a),pays(p),receives(r),fee(f),fill_price(fp),is_maker(m) {}\n\n      object_id_type      order_id;\n      account_id_type     account_id;\n      asset               pays;\n      asset               receives;\n      asset               fee; // paid by receiving account\n      price               fill_price;\n      bool                is_maker = true;\n\n      pair<asset_id_type,asset_id_type> get_market()const\n      {\n         return pays.asset_id < receives.asset_id ?\n                std::make_pair( pays.asset_id, receives.asset_id ) :\n                std::make_pair( receives.asset_id, pays.asset_id );\n      }\n      account_id_type fee_payer()const { return account_id; }\n      void            validate()const { FC_ASSERT( !\"virtual operation\" ); }\n\n      /// This is a virtual operation; there is no fee\n      share_type      calculate_fee(const fee_params_t&)const { return 0; }\n   };\n\n   /**\n    *  @ingroup operations\n    *\n    *  This operation can be used after a global settlement to bid collateral for\n    *  taking over part of the debt and the settlement_fund (see BSIP-0018).\n    */\n   struct bid_collateral_operation : public base_operation\n   {\n      /** should be equivalent to call_order_update fee */\n      struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset               fee;\n      account_id_type     bidder; ///< pays fee and additional collateral\n      asset               additional_collateral; ///< the amount of collateral to bid for the debt\n      asset               debt_covered; ///< the amount of debt to take over\n      extensions_type     extensions;\n\n      account_id_type fee_payer()const { return bidder; }\n      void            validate()const;\n   };\n\n   /**\n    * @ingroup operations\n    *\n    * @note This is a virtual operation that is created while reviving a\n    * bitasset from collateral bids.\n    */\n   struct execute_bid_operation : public base_operation\n   {\n      struct fee_params_t {};\n\n      execute_bid_operation(){}\n      execute_bid_operation( account_id_type a, asset d, asset c )\n         : bidder(a), debt(d), collateral(c) {}\n\n      account_id_type     bidder;\n      asset               debt;\n      asset               collateral;\n      asset               fee;\n\n      account_id_type fee_payer()const { return bidder; }\n      void            validate()const { FC_ASSERT( !\"virtual operation\" ); }\n\n      /// This is a virtual operation; there is no fee\n      share_type      calculate_fee(const fee_params_t&)const { return 0; }\n   };\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::create_take_profit_order_action,\n            (fee_asset_id)(spread_percent)(size_percent)(expiration_seconds)(repeat)(extensions) )\n\nFC_REFLECT( graphene::protocol::limit_order_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::limit_order_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::limit_order_cancel_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::call_order_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::bid_collateral_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::fill_order_operation::fee_params_t,  ) // VIRTUAL\nFC_REFLECT( graphene::protocol::execute_bid_operation::fee_params_t,  ) // VIRTUAL\n\nFC_REFLECT( graphene::protocol::limit_order_create_operation::options_type, (on_fill) )\nFC_REFLECT( graphene::protocol::call_order_update_operation::options_type, (target_collateral_ratio) )\n\nFC_REFLECT( graphene::protocol::limit_order_create_operation,\n            (fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill)(extensions))\nFC_REFLECT( graphene::protocol::limit_order_update_operation,\n            (fee)(seller)(order)(new_price)(delta_amount_to_sell)(new_expiration)(on_fill)(extensions))\nFC_REFLECT( graphene::protocol::limit_order_cancel_operation,\n            (fee)(fee_paying_account)(order)(extensions) )\nFC_REFLECT( graphene::protocol::call_order_update_operation,\n            (fee)(funding_account)(delta_collateral)(delta_debt)(extensions) )\nFC_REFLECT( graphene::protocol::fill_order_operation,\n            (fee)(order_id)(account_id)(pays)(receives)(fill_price)(is_maker) )\nFC_REFLECT( graphene::protocol::bid_collateral_operation,\n            (fee)(bidder)(additional_collateral)(debt_covered)(extensions) )\nFC_REFLECT( graphene::protocol::execute_bid_operation,\n            (fee)(bidder)(debt)(collateral) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::create_take_profit_order_action)\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::options_type )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::options_type )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::fill_order_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::execute_bid_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/memo.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    *  @brief defines the keys used to derive the shared secret\n    *\n    *  Because account authorities and keys can change at any time, each memo must\n    *  capture the specific keys used to derive the shared secret.  In order to read\n    *  the cipher message you will need one of the two private keys.\n    *\n    *  If @ref from == @ref to and @ref from == 0 then no encryption is used, the memo is public.\n    *  If @ref from == @ref to and @ref from != 0 then invalid memo data\n    *\n    */\n   struct memo_data\n   {\n      public_key_type from;\n      public_key_type to;\n      /**\n       * 64 bit nonce format:\n       * [  8 bits | 56 bits   ]\n       * [ entropy | timestamp ]\n       * Timestamp is number of microseconds since the epoch\n       * Entropy is a byte taken from the hash of a new private key\n       *\n       * This format is not mandated or verified; it is chosen to ensure uniqueness of key-IV pairs only. This should\n       * be unique with high probability as long as the generating host has a high-resolution clock OR a strong source\n       * of entropy for generating private keys.\n       */\n      uint64_t nonce = 0;\n      /**\n       * This field contains the AES encrypted packed @ref memo_message\n       */\n      vector<char> message;\n\n      /// @note custom_nonce is for debugging only; do not set to a nonzero value in production\n      void        set_message(const fc::ecc::private_key& priv,\n                              const fc::ecc::public_key& pub, const string& msg, uint64_t custom_nonce = 0);\n\n      std::string get_message(const fc::ecc::private_key& priv,\n                              const fc::ecc::public_key& pub)const;\n   };\n\n   /**\n    * @brief defines a message and checksum to enable validation of successful decryption\n    *\n    * When encrypting/decrypting a checksum is required to determine whether or not\n    * decryption was successful.\n    */\n   struct memo_message\n   {\n      memo_message(){}\n      memo_message( uint32_t checksum, const std::string& text )\n      :checksum(checksum),text(text){}\n\n      uint32_t    checksum = 0;\n      std::string text;\n\n      string serialize() const;\n      static memo_message deserialize(const string& serial);\n   };\n\n} } // namespace graphene::protocol\n\nFC_REFLECT( graphene::protocol::memo_message, (checksum)(text) )\nFC_REFLECT( graphene::protocol::memo_data, (from)(to)(nonce)(message) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::memo_message )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::memo_data )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/object_id.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <fc/exception/exception.hpp>\n#include <fc/io/varint.hpp>\n\nnamespace graphene { namespace db {\n\n   struct object_id_type\n   {\n      static constexpr uint8_t instance_bits = 48;\n      static constexpr uint8_t type_and_instance_bits = 56;\n      static constexpr uint64_t one_byte_mask = 0x00ff;\n      static constexpr uint64_t max_instance = 0x0000ffffffffffff;\n\n      object_id_type() = default;\n      object_id_type( uint8_t s, uint8_t t, uint64_t i ){ reset( s, t, i ); }\n\n      void reset( uint8_t s, uint8_t t, uint64_t i )\n      {\n         FC_ASSERT( i >> instance_bits == 0, \"instance overflow\", (\"instance\",i) );\n         number = ( (uint64_t(s) << type_and_instance_bits) | (uint64_t(t) << instance_bits) ) | i;\n      }\n\n      uint8_t  space()const      { return number >> type_and_instance_bits; }\n      uint8_t  type()const       { return (number >> instance_bits) & one_byte_mask; }\n      uint16_t space_type()const { return number >> instance_bits; }\n      uint64_t instance()const   { return number & max_instance; }\n      bool     is_null()const { return 0 == number; }\n      explicit operator uint64_t()const { return number; }\n\n      friend bool  operator == ( const object_id_type& a, const object_id_type& b ) { return a.number == b.number; }\n      friend bool  operator != ( const object_id_type& a, const object_id_type& b ) { return a.number != b.number; }\n      friend bool  operator < ( const object_id_type& a, const object_id_type& b ) { return a.number < b.number; }\n      friend bool  operator > ( const object_id_type& a, const object_id_type& b ) { return a.number > b.number; }\n\n      object_id_type& operator++() { ++number; return *this; }\n\n      friend object_id_type operator+(const object_id_type& a, int64_t delta ) {\n         return object_id_type( a.space(), a.type(), a.instance() + delta );\n      }\n      friend size_t hash_value( const object_id_type& v ) { return std::hash<uint64_t>()(v.number); }\n\n      template< typename T >\n      bool is() const\n      {\n         return space_type() == T::space_type;\n      }\n\n\n      template< typename T >\n      T as() const\n      {\n         return T( *this );\n      }\n\n      explicit operator std::string() const\n      {\n         return fc::to_string(space()) + \".\" + fc::to_string(type()) + \".\" + fc::to_string(instance());\n      }\n\n      uint64_t number = 0;\n   };\n\n   class object;\n   class object_database;\n\n   /// This template is used to downcast a generic object type to a specific xyz_object type.\n   template<typename ObjectID>\n   struct object_downcast { using type = object; };\n   // This macro specializes the above template for a specific xyz_object type\n#define MAP_OBJECT_ID_TO_TYPE(OBJECT) \\\n   namespace graphene { namespace db { \\\n   template<> \\\n   struct object_downcast<const graphene::db::object_id<OBJECT::space_id, \\\n                                                        OBJECT::type_id>&> { using type = OBJECT; }; \\\n   } }\n   template<typename ObjectID>\n   using object_downcast_t = typename object_downcast<ObjectID>::type;\n\n   template<uint8_t SpaceID, uint8_t TypeID>\n   struct object_id\n   {\n      static constexpr uint8_t type_bits = 8;\n      static constexpr uint8_t instance_bits = 48;\n      static constexpr uint64_t max_instance = 0x0000ffffffffffff;\n\n      static constexpr uint8_t space_id = SpaceID;\n      static constexpr uint8_t type_id = TypeID;\n\n      static constexpr uint16_t space_type = uint16_t(uint16_t(space_id) << type_bits) | uint16_t(type_id);\n\n      static constexpr object_id max()\n      {\n         return object_id( max_instance );\n      }\n\n      object_id() = default;\n      explicit object_id( const fc::unsigned_int& i ):instance(i)\n      {\n         validate();\n      }\n      explicit object_id( uint64_t i ):instance(i)\n      {\n         validate();\n      }\n      explicit object_id( const object_id_type& id ):instance(id.instance())\n      {\n         // Won't overflow, but need to check space and type\n         FC_ASSERT( id.is<std::remove_reference_t<decltype(*this)>>(), \"space or type mismatch\" );\n      }\n\n      void validate()const\n      {\n         FC_ASSERT( (instance.value >> instance_bits) == 0, \"instance overflow\", (\"instance\",instance) );\n      }\n\n      object_id& operator=( const object_id_type& o )\n      {\n         *this = object_id(o);\n         return *this;\n      }\n\n      friend object_id operator+(const object_id& a, int64_t delta )\n      { return object_id( uint64_t(a.instance.value+delta) ); }\n\n      explicit operator object_id_type()const { return object_id_type( SpaceID, TypeID, instance.value ); }\n      explicit operator uint64_t()const { return object_id_type( *this ).number; }\n\n      template<typename DB>\n      auto operator()(const DB& db)const -> const decltype(db.get(*this))& { return db.get(*this); }\n\n      friend bool  operator == ( const object_id& a, const object_id& b ) { return a.instance == b.instance; }\n      friend bool  operator != ( const object_id& a, const object_id& b ) { return a.instance != b.instance; }\n      friend bool  operator == ( const object_id_type& a, const object_id& b ) { return a == object_id_type(b); }\n      friend bool  operator != ( const object_id_type& a, const object_id& b ) { return a != object_id_type(b); }\n      friend bool  operator == ( const object_id& a, const object_id_type& b ) { return object_id_type(a) == b; }\n      friend bool  operator != ( const object_id& a, const object_id_type& b ) { return object_id_type(a) != b; }\n      friend bool  operator == ( const object_id& a, const fc::unsigned_int& b ) { return a.instance == b; }\n      friend bool  operator != ( const object_id& a, const fc::unsigned_int& b ) { return a.instance != b; }\n      friend bool  operator == ( const fc::unsigned_int& a, const object_id& b ) { return a == b.instance; }\n      friend bool  operator != ( const fc::unsigned_int& a, const object_id& b ) { return a != b.instance; }\n\n      friend bool  operator < ( const object_id& a, const object_id& b )\n      { return a.instance.value < b.instance.value; }\n      friend bool  operator > ( const object_id& a, const object_id& b )\n      { return a.instance.value > b.instance.value; }\n\n      friend size_t hash_value( const object_id& v ) { return std::hash<uint64_t>()(v.instance.value); }\n\n      explicit operator std::string() const\n      {\n         return fc::to_string(space_id) + \".\" + fc::to_string(type_id) + \".\" + fc::to_string(instance.value);\n      }\n\n      fc::unsigned_int instance; // default is 0\n   };\n\n} } // graphene::db\n\nFC_REFLECT( graphene::db::object_id_type, (number) )\n\n// REFLECT object_id manually because it has 2 template params\nnamespace fc {\ntemplate<uint8_t SpaceID, uint8_t TypeID>\nstruct get_typename<graphene::db::object_id<SpaceID,TypeID>>\n{\n   static const char* name() {\n      return typeid(get_typename).name();\n      static std::string _str = string(\"graphene::db::object_id<\") + fc::to_string(SpaceID) + \":\"\n                                                                   + fc::to_string(TypeID)  + \">\";\n      return _str.c_str();\n   }\n};\n\ntemplate<uint8_t SpaceID, uint8_t TypeID>\nstruct reflector<graphene::db::object_id<SpaceID,TypeID> >\n{\n    using type = graphene::db::object_id<SpaceID,TypeID>;\n    using is_defined = std::true_type;\n    using native_members = typelist::list<fc::field_reflection<0, type, unsigned_int, &type::instance>>;\n    using inherited_members = typelist::list<>;\n    using members = native_members;\n    using base_classes = typelist::list<>;\n    enum  member_count_enum {\n      local_member_count = 1,\n      total_member_count = 1\n    };\n    template<typename Visitor>\n    static inline void visit( const Visitor& visitor )\n    {\n       using member_type = decltype(((type*)nullptr)->instance);\n       visitor.TEMPLATE operator()<member_type,type,&type::instance>( \"instance\" );\n    }\n};\nnamespace member_names {\ntemplate<uint8_t S, uint8_t T>\nstruct member_name<graphene::db::object_id<S,T>, 0> { static constexpr const char* value = \"instance\"; };\n}\n\n\n inline void to_variant( const graphene::db::object_id_type& var,  fc::variant& vo, uint32_t max_depth = 1 )\n {\n    vo = std::string( var );\n }\n\n inline void from_variant( const fc::variant& var,  graphene::db::object_id_type& vo, uint32_t max_depth = 1 )\n { try {\n    const auto& s = var.get_string();\n    auto first_dot = s.find('.');\n    FC_ASSERT( first_dot != std::string::npos, \"Missing the first dot\" );\n    FC_ASSERT( first_dot != 0, \"Missing the space part\" );\n    auto second_dot = s.find('.',first_dot+1);\n    FC_ASSERT( second_dot != std::string::npos, \"Missing the second dot\" );\n    FC_ASSERT( second_dot != first_dot+1, \"Missing the type part\" );\n    auto space_id = fc::to_uint64( s.substr( 0, first_dot ) );\n    FC_ASSERT( space_id <= graphene::db::object_id_type::one_byte_mask, \"space overflow\" );\n    auto type_id =  fc::to_uint64( s.substr( first_dot+1, (second_dot-first_dot)-1 ) );\n    FC_ASSERT( type_id <= graphene::db::object_id_type::one_byte_mask, \"type overflow\");\n    auto instance = fc::to_uint64(s.substr( second_dot+1 ));\n    vo.reset( static_cast<uint8_t>(space_id), static_cast<uint8_t>(type_id), instance );\n } FC_CAPTURE_AND_RETHROW( (var) ) } // GCOVR_EXCL_LINE\n\n template<uint8_t SpaceID, uint8_t TypeID>\n void to_variant( const graphene::db::object_id<SpaceID,TypeID>& var,  fc::variant& vo, uint32_t max_depth = 1 )\n {\n    vo = std::string( var );\n }\n\n template<uint8_t SpaceID, uint8_t TypeID>\n void from_variant( const fc::variant& var,  graphene::db::object_id<SpaceID,TypeID>& vo, uint32_t max_depth = 1 )\n { try {\n    const auto& s = var.get_string();\n    auto first_dot = s.find('.');\n    FC_ASSERT( first_dot != std::string::npos, \"Missing the first dot\" );\n    FC_ASSERT( first_dot != 0, \"Missing the space part\" );\n    auto second_dot = s.find('.',first_dot+1);\n    FC_ASSERT( second_dot != std::string::npos, \"Missing the second dot\" );\n    FC_ASSERT( second_dot != first_dot+1, \"Missing the type part\" );\n    FC_ASSERT( fc::to_uint64( s.substr( 0, first_dot ) ) == SpaceID &&\n               fc::to_uint64( s.substr( first_dot+1, (second_dot-first_dot)-1 ) ) == TypeID,\n               \"Space.Type.0 (${SpaceID}.${TypeID}.0) doesn't match expected value ${var}\",\n               (\"TypeID\",TypeID)(\"SpaceID\",SpaceID)(\"var\",var) );\n    graphene::db::object_id<SpaceID,TypeID> tmp { fc::to_uint64(s.substr( second_dot+1 )) };\n    vo = tmp;\n } FC_CAPTURE_AND_RETHROW( (var) ) } // GCOVR_EXCL_LINE\n\n} // namespace fc\n\nnamespace std {\n     template <> struct hash<graphene::db::object_id_type>\n     {\n          size_t operator()(const graphene::db::object_id_type& x) const\n          {\n              return std::hash<uint64_t>()(x.number);\n          }\n     };\n}\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/operations.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/account.hpp>\n#include <graphene/protocol/assert.hpp>\n#include <graphene/protocol/asset_ops.hpp>\n#include <graphene/protocol/balance.hpp>\n#include <graphene/protocol/committee_member.hpp>\n#include <graphene/protocol/confidential.hpp>\n#include <graphene/protocol/credit_offer.hpp>\n#include <graphene/protocol/custom.hpp>\n#include <graphene/protocol/custom_authority.hpp>\n#include <graphene/protocol/fba.hpp>\n#include <graphene/protocol/htlc.hpp>\n#include <graphene/protocol/liquidity_pool.hpp>\n#include <graphene/protocol/market.hpp>\n#include <graphene/protocol/proposal.hpp>\n#include <graphene/protocol/samet_fund.hpp>\n#include <graphene/protocol/ticket.hpp>\n#include <graphene/protocol/transfer.hpp>\n#include <graphene/protocol/vesting.hpp>\n#include <graphene/protocol/withdraw_permission.hpp>\n#include <graphene/protocol/witness.hpp>\n#include <graphene/protocol/worker.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @ingroup operations\n    *\n    * Defines the set of valid operations as a discriminated union type.\n    */\n   using operation = fc::static_variant<\n            /*  0 */ transfer_operation,\n            /*  1 */ limit_order_create_operation,\n            /*  2 */ limit_order_cancel_operation,\n            /*  3 */ call_order_update_operation,\n            /*  4 */ fill_order_operation,           // VIRTUAL\n            /*  5 */ account_create_operation,\n            /*  6 */ account_update_operation,\n            /*  7 */ account_whitelist_operation,\n            /*  8 */ account_upgrade_operation,\n            /*  9 */ account_transfer_operation,\n            /* 10 */ asset_create_operation,\n            /* 11 */ asset_update_operation,\n            /* 12 */ asset_update_bitasset_operation,\n            /* 13 */ asset_update_feed_producers_operation,\n            /* 14 */ asset_issue_operation,\n            /* 15 */ asset_reserve_operation,\n            /* 16 */ asset_fund_fee_pool_operation,\n            /* 17 */ asset_settle_operation,\n            /* 18 */ asset_global_settle_operation,\n            /* 19 */ asset_publish_feed_operation,\n            /* 20 */ witness_create_operation,\n            /* 21 */ witness_update_operation,\n            /* 22 */ proposal_create_operation,\n            /* 23 */ proposal_update_operation,\n            /* 24 */ proposal_delete_operation,\n            /* 25 */ withdraw_permission_create_operation,\n            /* 26 */ withdraw_permission_update_operation,\n            /* 27 */ withdraw_permission_claim_operation,\n            /* 28 */ withdraw_permission_delete_operation,\n            /* 29 */ committee_member_create_operation,\n            /* 30 */ committee_member_update_operation,\n            /* 31 */ committee_member_update_global_parameters_operation,\n            /* 32 */ vesting_balance_create_operation,\n            /* 33 */ vesting_balance_withdraw_operation,\n            /* 34 */ worker_create_operation,\n            /* 35 */ custom_operation,\n            /* 36 */ assert_operation,\n            /* 37 */ balance_claim_operation,\n            /* 38 */ override_transfer_operation,\n            /* 39 */ transfer_to_blind_operation,\n            /* 40 */ blind_transfer_operation,\n            /* 41 */ transfer_from_blind_operation,\n            /* 42 */ asset_settle_cancel_operation,  // VIRTUAL\n            /* 43 */ asset_claim_fees_operation,\n            /* 44 */ fba_distribute_operation,       // VIRTUAL\n            /* 45 */ bid_collateral_operation,\n            /* 46 */ execute_bid_operation,          // VIRTUAL\n            /* 47 */ asset_claim_pool_operation,\n            /* 48 */ asset_update_issuer_operation,\n            /* 49 */ htlc_create_operation,\n            /* 50 */ htlc_redeem_operation,\n            /* 51 */ htlc_redeemed_operation,         // VIRTUAL\n            /* 52 */ htlc_extend_operation,\n            /* 53 */ htlc_refund_operation,           // VIRTUAL\n            /* 54 */ custom_authority_create_operation,\n            /* 55 */ custom_authority_update_operation,\n            /* 56 */ custom_authority_delete_operation,\n            /* 57 */ ticket_create_operation,\n            /* 58 */ ticket_update_operation,\n            /* 59 */ liquidity_pool_create_operation,\n            /* 60 */ liquidity_pool_delete_operation,\n            /* 61 */ liquidity_pool_deposit_operation,\n            /* 62 */ liquidity_pool_withdraw_operation,\n            /* 63 */ liquidity_pool_exchange_operation,\n            /* 64 */ samet_fund_create_operation,\n            /* 65 */ samet_fund_delete_operation,\n            /* 66 */ samet_fund_update_operation,\n            /* 67 */ samet_fund_borrow_operation,\n            /* 68 */ samet_fund_repay_operation,\n            /* 69 */ credit_offer_create_operation,\n            /* 70 */ credit_offer_delete_operation,\n            /* 71 */ credit_offer_update_operation,\n            /* 72 */ credit_offer_accept_operation,\n            /* 73 */ credit_deal_repay_operation,\n            /* 74 */ credit_deal_expired_operation,   // VIRTUAL\n            /* 75 */ liquidity_pool_update_operation,\n            /* 76 */ credit_deal_update_operation,\n            /* 77 */ limit_order_update_operation\n         >;\n\n   /**\n    *  Appends required authorites to the result vector.  The authorities appended are not the\n    *  same as those returned by get_required_auth \n    *\n    *  @return a set of required authorities for @p op\n    */\n   void operation_get_required_authorities( const operation& op,\n                                            flat_set<account_id_type>& active,\n                                            flat_set<account_id_type>& owner,\n                                            vector<authority>& other,\n                                            bool ignore_custom_operation_required_auths );\n\n   void operation_validate( const operation& op );\n\n   /**\n    *  @brief necessary to support nested operations inside the proposal_create_operation\n    */\n   struct op_wrapper\n   {\n         explicit op_wrapper(const operation& op = operation()):op(op){}\n         operation op;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT_TYPENAME( graphene::protocol::operation )\nFC_REFLECT( graphene::protocol::op_wrapper, (op) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::op_wrapper )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/proposal.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol { \n   /**\n     * @defgroup proposed_transactions  The BitShares Transaction Proposal Protocol\n     * @ingroup operations\n     *\n     * BitShares allows users to propose a transaction which requires approval of multiple accounts in order to execute.\n     * The user proposes a transaction using proposal_create_operation, then signatory accounts use\n     * proposal_update_operations to add or remove their approvals from this operation. When a sufficient number of\n     * approvals have been granted, the operations in the proposal are used to create a virtual transaction which is\n     * subsequently evaluated. Even if the transaction fails, the proposal will be kept until the expiration time, at\n     * which point, if sufficient approval is granted, the transaction will be evaluated a final time. This allows\n     * transactions which will not execute successfully until a given time to still be executed through the proposal\n     * mechanism. The first time the proposed transaction succeeds, the proposal will be regarded as resolved, and all\n     * future updates will be invalid.\n     *\n     * The proposal system allows for arbitrarily complex or recursively nested authorities. If a recursive authority\n     * (i.e. an authority which requires approval of 'nested' authorities on other accounts) is required for a\n     * proposal, then a second proposal can be used to grant the nested authority's approval. That is, a second\n     * proposal can be created which, when sufficiently approved, adds the approval of a nested authority to the first\n     * proposal. This multiple-proposal scheme can be used to acquire approval for an arbitrarily deep authority tree.\n     *\n     * Note that at any time, a proposal can be approved in a single transaction if sufficient signatures are available\n     * on the proposal_update_operation, as long as the authority tree to approve the proposal does not exceed the\n     * maximum recursion depth. In practice, however, it is easier to use proposals to acquire all approvals, as this\n     * leverages on-chain notification of all relevant parties that their approval is required. Off-chain\n     * multi-signature approval requires some off-chain mechanism for acquiring several signatures on a single\n     * transaction. This off-chain synchronization can be avoided using proposals.\n     * @{\n     */\n   /**\n    * op_wrapper is used to get around the circular definition of operation and proposals that contain them.\n    */\n   struct op_wrapper;\n   /**\n    * @brief The proposal_create_operation creates a transaction proposal, for use in multi-sig scenarios\n    * @ingroup operations\n    *\n    * Creates a transaction proposal. The operations which compose the transaction are listed in order in proposed_ops,\n    * and expiration_time specifies the time by which the proposal must be accepted or it will fail permanently. The\n    * expiration_time cannot be farther in the future than the maximum expiration time set in the global properties\n    * object.\n    */\n   struct proposal_create_operation : public base_operation\n   {\n       struct fee_params_t {\n          uint64_t fee            = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; \n          uint32_t price_per_kbyte = 10;\n       };\n\n       asset              fee;\n       account_id_type    fee_paying_account;\n       vector<op_wrapper> proposed_ops;\n       time_point_sec     expiration_time;\n       optional<uint32_t> review_period_seconds;\n       extensions_type    extensions;\n\n       /**\n        * Constructs a proposal_create_operation suitable for committee\n        * proposals, with expiration time and review period set\n        * appropriately.  No proposed_ops are added.  When used to\n        * create a proposal to change chain parameters, this method\n        * expects to receive the currently effective parameters, not\n        * the proposed parameters.  (The proposed parameters will go\n        * in proposed_ops, and proposed_ops is untouched by this\n        * function.)\n        */\n       static proposal_create_operation committee_proposal(const chain_parameters& param, fc::time_point_sec head_block_time );\n\n       account_id_type fee_payer()const { return fee_paying_account; }\n       void            validate()const;\n       share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    * @brief The proposal_update_operation updates an existing transaction proposal\n    * @ingroup operations\n    *\n    * This operation allows accounts to add or revoke approval of a proposed transaction. Signatures sufficient to\n    * satisfy the authority of each account in approvals are required on the transaction containing this operation.\n    *\n    * If an account with a multi-signature authority is listed in approvals_to_add or approvals_to_remove, either all\n    * required signatures to satisfy that account's authority must be provided in the transaction containing this\n    * operation, or a secondary proposal must be created which contains this operation.\n    *\n    * NOTE: If the proposal requires only an account's active authority, the account must not update adding its owner\n    * authority's approval. This is considered an error. An owner approval may only be added if the proposal requires\n    * the owner's authority.\n    *\n    * If an account's owner and active authority are both required, only the owner authority may approve. An attempt to\n    * add or remove active authority approval to such a proposal will fail.\n    */\n   struct proposal_update_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee            = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; \n         uint32_t price_per_kbyte = 10;\n      };\n\n      account_id_type            fee_paying_account;\n      asset                      fee;\n      proposal_id_type           proposal;\n      flat_set<account_id_type>  active_approvals_to_add;\n      flat_set<account_id_type>  active_approvals_to_remove;\n      flat_set<account_id_type>  owner_approvals_to_add;\n      flat_set<account_id_type>  owner_approvals_to_remove;\n      flat_set<public_key_type>  key_approvals_to_add;\n      flat_set<public_key_type>  key_approvals_to_remove;\n      extensions_type            extensions;\n\n      account_id_type fee_payer()const { return fee_paying_account; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n      void get_required_authorities( vector<authority>& )const;\n      void get_required_active_authorities( flat_set<account_id_type>& )const;\n      void get_required_owner_authorities( flat_set<account_id_type>& )const;\n   };\n\n   /**\n    * @brief The proposal_delete_operation deletes an existing transaction proposal\n    * @ingroup operations\n    *\n    * This operation allows the early veto of a proposed transaction. It may be used by any account which is a required\n    * authority on the proposed transaction, when that account's holder feels the proposal is ill-advised and he decides\n    * he will never approve of it and wishes to put an end to all discussion of the issue. Because he is a required\n    * authority, he could simply refuse to add his approval, but this would leave the topic open for debate until the\n    * proposal expires. Using this operation, he can prevent any further breath from being wasted on such an absurd\n    * proposal.\n    */\n   struct proposal_delete_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee =  GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      account_id_type   fee_paying_account;\n      bool              using_owner_authority = false;\n      asset             fee;\n      proposal_id_type  proposal;\n      extensions_type   extensions;\n\n      account_id_type fee_payer()const { return fee_paying_account; }\n      void       validate()const;\n   };\n   ///@}\n   \n}} // graphene::protocol\n\nFC_REFLECT( graphene::protocol::proposal_create_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::proposal_update_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::proposal_delete_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::proposal_create_operation, (fee)(fee_paying_account)(expiration_time)\n            (proposed_ops)(review_period_seconds)(extensions) )\nFC_REFLECT( graphene::protocol::proposal_update_operation, (fee)(fee_paying_account)(proposal)\n            (active_approvals_to_add)(active_approvals_to_remove)(owner_approvals_to_add)(owner_approvals_to_remove)\n            (key_approvals_to_add)(key_approvals_to_remove)(extensions) )\nFC_REFLECT( graphene::protocol::proposal_delete_operation, (fee)(fee_paying_account)(using_owner_authority)(proposal)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/pts_address.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <array>\n#include <cstring>\n#include <string>\n\n#include <fc/io/datastream.hpp>\n#include <fc/io/raw_fwd.hpp>\n#include <fc/variant.hpp>\n\nnamespace fc { namespace ecc { class public_key; } }\n\nnamespace graphene { namespace protocol {\n\n   /**\n    *  Implements address stringification and validation from PTS\n    */\n   struct pts_address\n   {\n       static constexpr uint8_t default_version = 56;\n\n       pts_address(); ///< constructs empty / null address\n       explicit pts_address( const std::string& base58str );   ///< converts to binary, validates checksum\n       /// Constructs from a public key\n       explicit pts_address( const fc::ecc::public_key& pub,\n                             bool compressed = true,\n                             uint8_t version = default_version );\n\n       uint8_t version()const { return addr.at(0); }\n       bool is_valid()const;\n\n       explicit operator std::string()const; ///< converts to base58 + checksum\n\n       std::array<char,25> addr{}; ///< binary representation of address, 0-initialized\n   };\n\n   inline bool operator == ( const pts_address& a, const pts_address& b ) { return a.addr == b.addr; }\n   inline bool operator != ( const pts_address& a, const pts_address& b ) { return a.addr != b.addr; }\n   inline bool operator <  ( const pts_address& a, const pts_address& b ) { return a.addr <  b.addr; }\n\n} } // namespace graphene::protocol\n\nnamespace std\n{\n   template<>\n   struct hash<graphene::protocol::pts_address>\n   {\n       public:\n         size_t operator()(const graphene::protocol::pts_address &a) const\n         {\n            size_t s;\n            std::memcpy( (char*)&s, a.addr.data() + a.addr.size() - sizeof(s), sizeof(s) );\n            return s;\n         }\n   };\n}\n\n#include <fc/reflect/reflect.hpp>\nFC_REFLECT( graphene::protocol::pts_address, (addr) )\n\nnamespace fc\n{\n   void to_variant( const graphene::protocol::pts_address& var,  fc::variant& vo, uint32_t max_depth = 1 );\n   void from_variant( const fc::variant& var,  graphene::protocol::pts_address& vo, uint32_t max_depth = 1 );\n\nnamespace raw {\n   extern template void pack( datastream<size_t>& s, const graphene::protocol::pts_address& tx,\n                              uint32_t _max_depth );\n   extern template void pack( datastream<char*>& s, const graphene::protocol::pts_address& tx,\n                              uint32_t _max_depth );\n   extern template void unpack( datastream<const char*>& s, graphene::protocol::pts_address& tx,\n                                uint32_t _max_depth );\n} } // fc::raw\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/restriction.hpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace protocol {\n\n/**\n  * Defines the set of valid operation restritions as a discriminated union type.\n  */\nstruct restriction {\n   enum function_type {\n      func_eq,\n      func_ne,\n      func_lt,\n      func_le,\n      func_gt,\n      func_ge,\n      func_in,\n      func_not_in,\n      func_has_all,\n      func_has_none,\n      func_attr,\n      func_logical_or,\n      func_variant_assert,\n      FUNCTION_TYPE_COUNT ///< Sentry value which contains the number of different types\n   };\n\n   // A variant assertion argument is a pair of the tag expected to be in the variant, and the restrictions to apply\n   // to the value\n   using variant_assert_argument_type = pair<int64_t, vector<restriction>>;\n\n#define GRAPHENE_OP_RESTRICTION_ARGUMENTS_VARIADIC \\\n   /*  0 */ void_t, \\\n   /*  1 */ bool, \\\n   /*  2 */ int64_t, \\\n   /*  3 */ string, \\\n   /*  4 */ time_point_sec, \\\n   /*  5 */ public_key_type, \\\n   /*  6 */ fc::sha256, \\\n   /*  7 */ account_id_type, \\\n   /*  8 */ asset_id_type, \\\n   /*  9 */ force_settlement_id_type, \\\n   /* 10 */ committee_member_id_type, \\\n   /* 11 */ witness_id_type, \\\n   /* 12 */ limit_order_id_type, \\\n   /* 13 */ call_order_id_type, \\\n   /* 14 */ custom_id_type, \\\n   /* 15 */ proposal_id_type, \\\n   /* 16 */ withdraw_permission_id_type, \\\n   /* 17 */ vesting_balance_id_type, \\\n   /* 18 */ worker_id_type, \\\n   /* 19 */ balance_id_type, \\\n   /* 20 */ flat_set<bool>, \\\n   /* 21 */ flat_set<int64_t>, \\\n   /* 22 */ flat_set<string>, \\\n   /* 23 */ flat_set<time_point_sec>, \\\n   /* 24 */ flat_set<public_key_type>, \\\n   /* 25 */ flat_set<fc::sha256>, \\\n   /* 26 */ flat_set<account_id_type>, \\\n   /* 27 */ flat_set<asset_id_type>, \\\n   /* 28 */ flat_set<force_settlement_id_type>, \\\n   /* 29 */ flat_set<committee_member_id_type>, \\\n   /* 30 */ flat_set<witness_id_type>, \\\n   /* 31 */ flat_set<limit_order_id_type>, \\\n   /* 32 */ flat_set<call_order_id_type>, \\\n   /* 33 */ flat_set<custom_id_type>, \\\n   /* 34 */ flat_set<proposal_id_type>, \\\n   /* 35 */ flat_set<withdraw_permission_id_type>, \\\n   /* 36 */ flat_set<vesting_balance_id_type>, \\\n   /* 37 */ flat_set<worker_id_type>, \\\n   /* 38 */ flat_set<balance_id_type>, \\\n   /* 39 */ vector<restriction>, \\\n   /* 40 */ vector<vector<restriction>>, \\\n   /* 41 */ variant_assert_argument_type\n\n   using argument_type = fc::static_variant<GRAPHENE_OP_RESTRICTION_ARGUMENTS_VARIADIC>;\n\n   unsigned_int member_index;\n   unsigned_int restriction_type;\n   argument_type argument;\n\n   extensions_type extensions;\n\n   restriction() = default;\n   restriction(const unsigned_int& member_index, function_type type, const argument_type& argument)\n      : member_index(member_index), restriction_type(type), argument(argument) {}\n\n   static size_t restriction_count(const vector<restriction>& restrictions);\n   size_t restriction_count() const;\n};\n\n} } // graphene::protocol\n\nFC_REFLECT_ENUM(graphene::protocol::restriction::function_type,\n                (func_eq)\n                (func_ne)\n                (func_lt)\n                (func_le)\n                (func_gt)\n                (func_ge)\n                (func_in)\n                (func_not_in)\n                (func_has_all)\n                (func_has_none)\n                (func_attr)\n                (func_logical_or)\n                (func_variant_assert)\n                (FUNCTION_TYPE_COUNT))\n\nFC_REFLECT(graphene::protocol::restriction,\n           (member_index)\n           (restriction_type)\n           (argument)\n           (extensions))\n\n\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/restriction_predicate.hpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/restriction.hpp>\n#include <graphene/protocol/operations.hpp>\n\n#include <functional>\n\nnamespace graphene { namespace protocol {\n\n/// A type describing the result of a restriction predicate\nstruct predicate_result {\n   /// Whether or not the operation complied with the restrictions or not\n   bool success = false;\n\n   /// Enumeration of the general reasons a predicate may reject\n   enum rejection_reason {\n      predicate_was_false,\n      null_optional,\n      incorrect_variant_type\n   };\n\n   /// An indicator of what rejection occurred at a particular restriction -- either an index to a sub-restriction, a\n   /// list of rejection results from the branches of a logical OR, or the immediate reason for rejection\n   using rejection_indicator = static_variant<size_t, vector<predicate_result>, rejection_reason>;\n   /// Failure indicators, ordered from the outermost restriction to the innermost (the location of the rejection)\n   vector<rejection_indicator> rejection_path;\n\n   static predicate_result Rejection(rejection_reason reason) { return {false, {reason}}; }\n   static predicate_result Rejection(vector<predicate_result> branches) { return {false, {std::move(branches)}}; }\n   static predicate_result Success() { return {true, {}}; }\n\n   operator bool() const { return success; }\n\n   /// Reverse the order of the rejection path. Returns a reference to this object\n   predicate_result& reverse_path();\n};\n\n/// A restriction predicate is a function accepting an operation and returning a predicate_result\nusing restriction_predicate_function = std::function<predicate_result(const operation&)>;\n\n/**\n * @brief get_restriction_predicate Get a predicate function for the supplied restriction\n * @param rs The restrictions to evaluate operations against\n * @param op_type The tag specifying which operation type the restrictions apply to\n * @return A predicate function which evaluates an operation to determine whether it complies with the restriction\n */\nrestriction_predicate_function get_restriction_predicate(vector<restriction> rs, operation::tag_type op_type);\n\n} } // namespace graphene::protocol\n\nFC_REFLECT_ENUM(graphene::protocol::predicate_result::rejection_reason,\n                (predicate_was_false)(null_optional)(incorrect_variant_type))\nFC_REFLECT_TYPENAME(graphene::protocol::predicate_result::rejection_indicator)\nFC_REFLECT(graphene::protocol::predicate_result, (success)(rejection_path))\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/samet_fund.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @brief Create a new SameT Fund object\n    * @ingroup operations\n    *\n    * A SameT Fund is a fund which can be used by a borrower and have to be repaid in the same transaction.\n    */\n   struct samet_fund_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;                   ///< Operation fee\n      account_id_type owner_account;         ///< Owner of the fund\n      asset_id_type   asset_type;            ///< Asset type in the fund\n      share_type      balance;               ///< Usable amount in the fund\n      uint32_t        fee_rate = 0;          ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return owner_account; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief Delete a SameT Fund object\n    * @ingroup operations\n    */\n   struct samet_fund_delete_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 0; };\n\n      asset                fee;                ///< Operation fee\n      account_id_type      owner_account;      ///< The account who owns the SameT Fund object\n      samet_fund_id_type   fund_id;            ///< ID of the SameT Fund object\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return owner_account; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief Update a SameT Fund object\n    * @ingroup operations\n    */\n   struct samet_fund_update_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                fee;                   ///< Operation fee\n      account_id_type      owner_account;         ///< Owner of the fund\n      samet_fund_id_type   fund_id;               ///< ID of the SameT Fund object\n      optional<asset>      delta_amount;          ///< Delta amount, optional\n      optional<uint32_t>   new_fee_rate;          ///< New fee rate, optional\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return owner_account; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief Borrow from a SameT Fund\n    * @ingroup operations\n    */\n   struct samet_fund_borrow_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          borrower;           ///< The account who borrows from the fund\n      samet_fund_id_type       fund_id;            ///< ID of the SameT Fund\n      asset                    borrow_amount;      ///< The amount to borrow\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return borrower; }\n      void            validate()const override;\n   };\n\n   /**\n    * @brief Repay to a SameT Fund\n    * @ingroup operations\n    */\n   struct samet_fund_repay_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                    fee;                ///< Operation fee\n      account_id_type          account;            ///< The account who repays to the SameT Fund\n      samet_fund_id_type       fund_id;            ///< ID of the SameT Fund\n      asset                    repay_amount;       ///< The amount to repay\n      asset                    fund_fee;           ///< Fee for using the fund\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const override;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::samet_fund_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::samet_fund_delete_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::samet_fund_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::samet_fund_borrow_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::samet_fund_repay_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::samet_fund_create_operation,\n            (fee)(owner_account)(asset_type)(balance)(fee_rate)(extensions) )\nFC_REFLECT( graphene::protocol::samet_fund_delete_operation,\n            (fee)(owner_account)(fund_id)(extensions) )\nFC_REFLECT( graphene::protocol::samet_fund_update_operation,\n            (fee)(owner_account)(fund_id)(delta_amount)(new_fee_rate)(extensions) )\nFC_REFLECT( graphene::protocol::samet_fund_borrow_operation,\n            (fee)(borrower)(fund_id)(borrow_amount)(extensions) )\nFC_REFLECT( graphene::protocol::samet_fund_repay_operation,\n            (fee)(account)(fund_id)(repay_amount)(fund_fee)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/special_authority.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace protocol {\n\nstruct no_special_authority {};\n\nstruct top_holders_special_authority\n{\n   asset_id_type asset;\n   uint8_t       num_top_holders = 1;\n};\n\ntypedef static_variant<\n   no_special_authority,\n   top_holders_special_authority\n   > special_authority;\n\nvoid validate_special_authority( const special_authority& auth );\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::no_special_authority, )\nFC_REFLECT( graphene::protocol::top_holders_special_authority, (asset)(num_top_holders) )\nFC_REFLECT_TYPENAME( graphene::protocol::special_authority )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::top_holders_special_authority )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/ticket.hpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\n#include <fc/optional.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /// Type of a ticket\n   enum ticket_type\n   {\n      liquid            = 0,\n      lock_180_days     = 1,\n      lock_360_days     = 2,\n      lock_720_days     = 3,\n      lock_forever      = 4,\n      TICKET_TYPE_COUNT = 5\n   };\n\n   /**\n    * @brief Creates a new ticket\n    * @ingroup operations\n    */\n   struct ticket_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;         ///< Operation fee\n      account_id_type account;     ///< The account who creates the ticket\n      unsigned_int    target_type; ///< The target ticket type, see @ref ticket_type\n      asset           amount;      ///< The amount of the ticket\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Updates an existing ticket\n    * @ingroup operations\n    */\n   struct ticket_update_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset           fee;         ///< Operation fee\n      ticket_id_type  ticket;      ///< The ticket to update\n      account_id_type account;     ///< The account who owns the ticket\n      unsigned_int    target_type; ///< New target ticket type, see @ref ticket_type\n      optional<asset> amount_for_new_target; ///< The amount to be used for the new target\n\n      extensions_type extensions;  ///< Unused. Reserved for future use.\n\n      account_id_type fee_payer()const { return account; }\n      void            validate()const;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT_ENUM( graphene::protocol::ticket_type,\n                 (liquid)(lock_180_days)(lock_360_days)(lock_720_days)(lock_forever)(TICKET_TYPE_COUNT) )\n\nFC_REFLECT( graphene::protocol::ticket_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::ticket_update_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::ticket_create_operation,\n            (fee)(account)(target_type)(amount)(extensions) )\nFC_REFLECT( graphene::protocol::ticket_update_operation,\n            (fee)(ticket)(account)(target_type)(amount_for_new_target)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation::fee_params_t )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/transaction.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/operations.hpp>\n\nnamespace graphene { namespace protocol {\n   struct predicate_result;\n\n   using rejected_predicate = static_variant<predicate_result, fc::exception>;\n   using rejected_predicate_map = map<custom_authority_id_type, rejected_predicate>;\n   using custom_authority_lookup = std::function<vector<authority>(account_id_type, const operation&,\n                                                                   rejected_predicate_map*)>;\n\n   /**\n    * @defgroup transactions Transactions\n    *\n    * All transactions are sets of operations that must be applied atomically. Transactions must refer to a recent\n    * block that defines the context of the operation so that they assert a known binding to the object id's referenced\n    * in the transaction.\n    *\n    * Rather than specify a full block number, we only specify the lower 16 bits of the block number which means you\n    * can reference any block within the last 65,536 blocks which is 3.5 days with a 5 second block interval or 18\n    * hours with a 1 second interval.\n    *\n    * All transactions must expire so that the network does not have to maintain a permanent record of all transactions\n    * ever published.  A transaction may not have an expiration date too far in the future because this would require\n    * keeping too much transaction history in memory.\n    *\n    * The block prefix is the first 4 bytes of the block hash of the reference block number, which is the second 4\n    * bytes of the @ref block_id_type (the first 4 bytes of the block ID are the block number)\n    *\n    * Note: A transaction which selects a reference block cannot be migrated between forks outside the period of\n    * ref_block_num.time to (ref_block_num.time + rel_exp * interval). This fact can be used to protect market orders\n    * which should specify a relatively short re-org window of perhaps less than 1 minute. Normal payments should\n    * probably have a longer re-org window to ensure their transaction can still go through in the event of a momentary\n    * disruption in service.\n    *\n    * @note It is not recommended to set the @ref transaction::ref_block_num, @ref transaction::ref_block_prefix,\n    * and @ref transaction::expiration\n    * fields manually. Call the appropriate overload of @ref transaction::set_expiration instead.\n    *\n    * @{\n    */\n\n   /**\n    *  @brief groups operations that should be applied atomically\n    */\n   class transaction\n   {\n   public:\n      virtual ~transaction() = default;\n      /**\n       * Least significant 16 bits from the reference block number.\n       */\n      uint16_t           ref_block_num    = 0;\n      /**\n       * The first non-block-number 32-bits of the reference block ID. Recall that block IDs have 32 bits of block\n       * number followed by the actual block hash, so this field should be set using the second 32 bits in the\n       * @ref block_id_type\n       */\n      uint32_t           ref_block_prefix = 0;\n\n      /**\n       * This field specifies the absolute expiration for this transaction.\n       */\n      fc::time_point_sec expiration;\n\n      vector<operation>  operations;\n      extensions_type    extensions;\n\n      /// Calculate the digest for a transaction\n      digest_type                        digest()const;\n      virtual const transaction_id_type& id()const;\n      virtual void                       validate() const;\n\n      void set_expiration( fc::time_point_sec expiration_time );\n      void set_reference_block( const block_id_type& reference_block );\n\n      /// visit all operations\n      template<typename Visitor>\n      vector<typename Visitor::result_type> visit( Visitor&& visitor )\n      {\n         vector<typename Visitor::result_type> results;\n         for( auto& op : operations )\n            results.push_back(op.visit( std::forward<Visitor>( visitor ) ));\n         return results;\n      }\n      template<typename Visitor>\n      vector<typename Visitor::result_type> visit( Visitor&& visitor )const\n      {\n         vector<typename Visitor::result_type> results;\n         for( auto& op : operations )\n            results.push_back(op.visit( std::forward<Visitor>( visitor ) ));\n         return results;\n      }\n\n      void get_required_authorities( flat_set<account_id_type>& active,\n                                     flat_set<account_id_type>& owner,\n                                     vector<authority>& other,\n                                     bool ignore_custom_operation_required_auths )const;\n\n      virtual uint64_t get_packed_size()const;\n\n   protected:\n      // Calculate the digest used for signature validation\n      digest_type sig_digest( const chain_id_type& chain_id )const;\n      mutable transaction_id_type _tx_id_buffer;\n   };\n\n   /**\n    *  @brief adds a signature to a transaction\n    */\n   class signed_transaction : public transaction\n   {\n   public:\n      signed_transaction( const transaction& trx = transaction() )\n         : transaction(trx){}\n      virtual ~signed_transaction() = default;\n\n      /** signs and appends to signatures */\n      const signature_type& sign( const private_key_type& key, const chain_id_type& chain_id );\n\n      /** returns signature but does not append */\n      signature_type sign( const private_key_type& key, const chain_id_type& chain_id )const;\n\n      /**\n       *  The purpose of this method is to identify some subset of\n       *  @p available_keys that will produce sufficient signatures\n       *  for a transaction.  The result is not always a minimal set of\n       *  signatures, but any non-minimal result will still pass\n       *  validation.\n       */\n      set<public_key_type> get_required_signatures(\n              const chain_id_type& chain_id,\n              const flat_set<public_key_type>& available_keys,\n              const std::function<const authority*(account_id_type)>& get_active,\n              const std::function<const authority*(account_id_type)>& get_owner,\n              bool allow_non_immediate_owner,\n              bool ignore_custom_operation_required_authorities,\n              uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const;\n\n      /**\n       * Checks whether signatures in this signed transaction are sufficient to authorize the transaction.\n       *   Throws an exception when failed.\n       *\n       * @param chain_id the ID of a block chain\n       * @param get_active callback function to retrieve active authorities of a given account\n       * @param get_owner  callback function to retrieve owner authorities of a given account\n       * @param get_custom callback function to retrieve viable custom authorities for a given account and operation\n       * @param allow_non_immediate_owner whether to allow owner authority of non-immediately\n       *            required accounts to authorize operations in the transaction\n       * @param ignore_custom_operation_required_auths See issue #210; whether to ignore the\n       *            required_auths field of custom_operation or not\n       * @param max_recursion maximum level of recursion when verifying, since an account\n       *            can have another account in active authorities and/or owner authorities\n       */\n      void verify_authority(\n              const chain_id_type& chain_id,\n              const std::function<const authority*(account_id_type)>& get_active,\n              const std::function<const authority*(account_id_type)>& get_owner,\n              const custom_authority_lookup& get_custom,\n              bool allow_non_immediate_owner,\n              bool ignore_custom_operation_required_auths,\n              uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const;\n\n      /**\n       * This is a slower replacement for get_required_signatures()\n       * which returns a minimal set in all cases, including\n       * some cases where get_required_signatures() returns a\n       * non-minimal set.\n       */\n      set<public_key_type> minimize_required_signatures(\n              const chain_id_type& chain_id,\n              const flat_set<public_key_type>& available_keys,\n              const std::function<const authority*(account_id_type)>& get_active,\n              const std::function<const authority*(account_id_type)>& get_owner,\n              const custom_authority_lookup& get_custom,\n              bool allow_non_immediate_owner,\n              bool ignore_custom_operation_required_auths,\n              uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH) const;\n\n      /**\n       * @brief Extract public keys from signatures with given chain ID.\n       * @param chain_id A chain ID\n       * @return Public keys\n       * @note If @ref _signees is empty, E.G. when it's the first time calling\n       *       this function for the signed transaction, public keys will be\n       *       extracted with given chain ID, and be stored into the mutable\n       *       @ref _signees field, then @ref _signees will be returned;\n       *       otherwise, the @p chain_id parameter will be ignored, and\n       *       @ref _signees will be returned directly.\n       */\n      virtual const flat_set<public_key_type>& get_signature_keys( const chain_id_type& chain_id )const;\n\n      /** Signatures */\n      vector<signature_type> signatures;\n\n      /** Removes all operations and signatures */\n      void clear() { operations.clear(); signatures.clear(); }\n\n      /** Removes all signatures */\n      void clear_signatures() { signatures.clear(); }\n   protected:\n      /** Public keys extracted from signatures */\n      mutable flat_set<public_key_type> _signees;\n   };\n\n   /** This represents a signed transaction that will never have its operations,\n    *  signatures etc. modified again, after initial creation. It is therefore\n    *  safe to cache results from various calls.\n    */\n   class precomputable_transaction : public signed_transaction {\n   public:\n      precomputable_transaction() {}\n      precomputable_transaction( const signed_transaction& tx ) : signed_transaction(tx) {};\n      precomputable_transaction( signed_transaction&& tx ) : signed_transaction( std::move(tx) ) {};\n      virtual ~precomputable_transaction() = default;\n\n      virtual const transaction_id_type&       id()const override;\n      virtual void                             validate()const override;\n      virtual const flat_set<public_key_type>& get_signature_keys( const chain_id_type& chain_id )const override;\n      virtual uint64_t                         get_packed_size()const override;\n   protected:\n      mutable bool _validated = false;\n      mutable uint64_t _packed_size = 0;\n   };\n\n   /**\n    * Checks whether given public keys and approvals are sufficient to authorize given operations.\n    *   Throws an exception when failed.\n    *\n    * @param ops a vector of operations\n    * @param sigs a set of public keys\n    * @param get_active callback function to retrieve active authorities of a given account\n    * @param get_owner  callback function to retrieve owner authorities of a given account\n    * @param get_custom callback function to retrieve viable custom authorities for a given account and operation\n    * @param allow_non_immediate_owner whether to allow owner authority of non-immediately\n    *            required accounts to authorize operations\n    * @param ignore_custom_operation_required_auths See issue #210; whether to ignore the\n    *            required_auths field of custom_operation or not\n    * @param max_recursion maximum level of recursion when verifying, since an account\n    *            can have another account in active authorities and/or owner authorities\n    * @param allow_committee whether to allow the special \"committee account\" to authorize the operations\n    * @param active_approvals accounts that approved the operations with their active authories\n    * @param owner_approvals accounts that approved the operations with their owner authories\n    */\n   void verify_authority( const vector<operation>& ops, const flat_set<public_key_type>& sigs,\n                          const std::function<const authority*(account_id_type)>& get_active,\n                          const std::function<const authority*(account_id_type)>& get_owner,\n                          const custom_authority_lookup& get_custom,\n                          bool allow_non_immediate_owner,\n                          bool ignore_custom_operation_required_auths,\n                          uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH,\n                          bool allow_committee = false,\n                          const flat_set<account_id_type>& active_approvals = flat_set<account_id_type>(),\n                          const flat_set<account_id_type>& owner_approvals = flat_set<account_id_type>() );\n\n   /**\n    *  @brief captures the result of evaluating the operations contained in the transaction\n    *\n    *  When processing a transaction some operations generate\n    *  new object IDs and these IDs cannot be known until the\n    *  transaction is actually included into a block.  When a\n    *  block is produced these new ids are captured and included\n    *  with every transaction.  The index in operation_results should\n    *  correspond to the same index in operations.\n    *\n    *  If an operation did not create any new object IDs then 0\n    *  should be returned.\n    */\n   struct processed_transaction : public precomputable_transaction\n   {\n      processed_transaction( const signed_transaction& trx = signed_transaction() )\n         : precomputable_transaction(trx){}\n      virtual ~processed_transaction() = default;\n\n      vector<operation_result> operation_results;\n\n      digest_type merkle_digest()const;\n   };\n\n   /// @} transactions group\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::transaction, (ref_block_num)(ref_block_prefix)(expiration)(operations)(extensions) )\n// Note: not reflecting _signees field for backward compatibility; in addition, it should not be in p2p messages\nFC_REFLECT_DERIVED( graphene::protocol::signed_transaction, (graphene::protocol::transaction), (signatures) )\nFC_REFLECT_DERIVED( graphene::protocol::precomputable_transaction, (graphene::protocol::signed_transaction), )\nFC_REFLECT_DERIVED( graphene::protocol::processed_transaction, (graphene::protocol::precomputable_transaction), (operation_results) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transaction)\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::signed_transaction)\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::precomputable_transaction)\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::processed_transaction)\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/transfer.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/memo.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @ingroup operations\n    *\n    * @brief Transfers an amount of one asset from one account to another\n    *\n    *  Fees are paid by the \"from\" account\n    *\n    *  @pre amount.amount > 0\n    *  @pre fee.amount >= 0\n    *  @pre from != to\n    *  @post from account's balance will be reduced by fee and amount\n    *  @post to account's balance will be increased by amount\n    *  @return n/a\n    */\n   struct transfer_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee       = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos.\n      };\n\n      asset            fee;\n      /// Account to transfer asset from\n      account_id_type  from;\n      /// Account to transfer asset to\n      account_id_type  to;\n      /// The amount of asset to transfer from @ref from to @ref to\n      asset            amount;\n\n      /// User provided data encrypted to the memo key of the \"to\" account\n      optional<memo_data> memo;\n      extensions_type   extensions;\n\n      account_id_type fee_payer()const { return from; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    *  @class override_transfer_operation\n    *  @brief Allows the issuer of an asset to transfer an asset from any account to any account if they have override_authority\n    *  @ingroup operations\n    *\n    *  @pre amount.asset_id->issuer == issuer\n    *  @pre issuer != from  because this is pointless, use a normal transfer operation\n    */\n   struct override_transfer_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee       = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 10; /// only required for large memos.\n      };\n\n      asset           fee;\n      account_id_type issuer;\n      /// Account to transfer asset from\n      account_id_type from;\n      /// Account to transfer asset to\n      account_id_type to;\n      /// The amount of asset to transfer from @ref from to @ref to\n      asset amount;\n\n      /// User provided data encrypted to the memo key of the \"to\" account\n      optional<memo_data> memo;\n      extensions_type   extensions;\n\n      account_id_type fee_payer()const { return issuer; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n}} // graphene::protocol\n\nFC_REFLECT( graphene::protocol::transfer_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::override_transfer_operation::fee_params_t, (fee)(price_per_kbyte) )\n\nFC_REFLECT( graphene::protocol::override_transfer_operation, (fee)(issuer)(from)(to)(amount)(memo)(extensions) )\nFC_REFLECT( graphene::protocol::transfer_operation, (fee)(from)(to)(amount)(memo)(extensions) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/types.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <memory>\n#include <vector>\n#include <deque>\n#include <cstdint>\n\n#include <boost/preprocessor/seq/for_each.hpp>\n#include <boost/preprocessor/seq/transform.hpp>\n#include <boost/preprocessor/seq/elem.hpp>\n#include <boost/preprocessor/seq/enum.hpp>\n#include <boost/preprocessor/tuple/elem.hpp>\n#include <boost/preprocessor/cat.hpp>\n\n#include <boost/rational.hpp>\n\n#include <fc/container/flat_fwd.hpp>\n#include <fc/io/varint.hpp>\n#include <fc/io/enum_type.hpp>\n#include <fc/crypto/ripemd160.hpp>\n#include <fc/crypto/sha1.hpp>\n#include <fc/crypto/sha224.hpp>\n#include <fc/crypto/sha256.hpp>\n#include <fc/crypto/hash160.hpp>\n#include <fc/crypto/elliptic.hpp>\n#include <fc/reflect/reflect.hpp>\n#include <fc/reflect/variant.hpp>\n#include <fc/optional.hpp>\n#include <fc/safe.hpp>\n#include <fc/container/flat.hpp>\n#include <fc/string.hpp>\n\n#include <fc/io/datastream.hpp>\n#include <fc/io/raw_fwd.hpp>\n#include <fc/static_variant.hpp>\n\n#include <graphene/protocol/object_id.hpp>\n#include <graphene/protocol/config.hpp>\n\n#define GRAPHENE_EXTERNAL_SERIALIZATION_VARIANT(ext, type) \\\nnamespace fc { \\\n   ext template void from_variant( const variant& v, type& vo, uint32_t max_depth ); \\\n   ext template void to_variant( const type& v, variant& vo, uint32_t max_depth ); \\\n}\n\n#define GRAPHENE_EXTERNAL_SERIALIZATION_PACK(ext, type) \\\nnamespace fc { namespace raw { \\\n   ext template void pack< datastream<size_t>, type >( \\\n         datastream<size_t>& s, const type& tx, uint32_t _max_depth ); \\\n   ext template void pack< sha256::encoder, type >( \\\n         sha256::encoder& s, const type& tx, uint32_t _max_depth ); \\\n   ext template void pack< datastream<char*>, type >( \\\n         datastream<char*>& s, const type& tx, uint32_t _max_depth ); \\\n   ext template void unpack< datastream<const char*>, type >( \\\n         datastream<const char*>& s, type& tx, uint32_t _max_depth ); \\\n} }\n\n#define GRAPHENE_EXTERNAL_SERIALIZATION(ext, type) \\\n   GRAPHENE_EXTERNAL_SERIALIZATION_VARIANT(ext, type) \\\n   GRAPHENE_EXTERNAL_SERIALIZATION_PACK(ext, type)\n\n#define GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION(type) GRAPHENE_EXTERNAL_SERIALIZATION(extern, type)\n#define GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION(type) GRAPHENE_EXTERNAL_SERIALIZATION(/*not extern*/, type)\n\n#define GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_VARIANT(type) \\\n   GRAPHENE_EXTERNAL_SERIALIZATION_VARIANT(/*not extern*/, type)\n#define GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_PACK(type) \\\n   GRAPHENE_EXTERNAL_SERIALIZATION_PACK(/*not extern*/, type)\n\n#define GRAPHENE_NAME_TO_OBJECT_TYPE(x, prefix, name) BOOST_PP_CAT(prefix, BOOST_PP_CAT(name, _object_type))\n#define GRAPHENE_NAME_TO_ID_TYPE(x, y, name) BOOST_PP_CAT(name, _id_type)\n#define GRAPHENE_DECLARE_ID(x, space_prefix_seq, name) \\\n    using BOOST_PP_CAT(name, _id_type) = object_id<BOOST_PP_TUPLE_ELEM(2, 0, space_prefix_seq), \\\n                            GRAPHENE_NAME_TO_OBJECT_TYPE(x, BOOST_PP_TUPLE_ELEM(2, 1, space_prefix_seq), name)>;\n#define GRAPHENE_REFLECT_ID(x, id_namespace, name) FC_REFLECT_TYPENAME(graphene::id_namespace::name)\n\n#define GRAPHENE_DEFINE_IDS(id_namespace, object_space, object_type_prefix, names_seq) \\\n   namespace graphene { namespace id_namespace { \\\n   \\\n   enum BOOST_PP_CAT(object_type_prefix, object_type) { \\\n      BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(GRAPHENE_NAME_TO_OBJECT_TYPE, object_type_prefix, names_seq)) \\\n   }; \\\n   \\\n   BOOST_PP_SEQ_FOR_EACH(GRAPHENE_DECLARE_ID, (object_space, object_type_prefix), names_seq) \\\n   \\\n   } } \\\n   \\\n   FC_REFLECT_ENUM(graphene::id_namespace::BOOST_PP_CAT(object_type_prefix, object_type), \\\n                   BOOST_PP_SEQ_TRANSFORM(GRAPHENE_NAME_TO_OBJECT_TYPE, object_type_prefix, names_seq)) \\\n   BOOST_PP_SEQ_FOR_EACH(GRAPHENE_REFLECT_ID, id_namespace, BOOST_PP_SEQ_TRANSFORM(GRAPHENE_NAME_TO_ID_TYPE, , names_seq))\n\nnamespace graphene { namespace protocol {\nusing namespace graphene::db;\n\nusing std::map;\nusing std::vector;\nusing std::unordered_map;\nusing std::string;\nusing std::deque;\nusing std::shared_ptr;\nusing std::weak_ptr;\nusing std::unique_ptr;\nusing std::set;\nusing std::pair;\nusing std::enable_shared_from_this;\nusing std::tie;\nusing std::make_pair;\n\nusing fc::variant_object;\nusing fc::variant;\nusing fc::enum_type;\nusing fc::optional;\nusing fc::unsigned_int;\nusing fc::time_point_sec;\nusing fc::time_point;\nusing fc::safe;\nusing fc::flat_map;\nusing fc::flat_set;\nusing fc::static_variant;\nusing fc::ecc::range_proof_type;\nusing fc::ecc::range_proof_info;\nusing fc::ecc::commitment_type;\nstruct void_t{};\n\nusing private_key_type = fc::ecc::private_key;\nusing chain_id_type = fc::sha256;\nusing ratio_type = boost::rational<int32_t>;\n\n/**\n * @note\n * If one of the following bits is set in asset issuer permissions,\n * it means the asset issuer (or owner for bitassets) has the permission to update\n * the corresponding flag, parameters or perform certain actions.\n * - @ref charge_market_fee\n * - @ref white_list\n * - @ref override_authority\n * - @ref transfer_restricted\n * - @ref disable_force_settle\n * - @ref global_settle\n * - @ref disable_confidential\n * - @ref witness_fed_asset\n * - @ref committee_fed_asset\n *\n * @note\n * If one of the following bits is set in asset issuer permissions,\n * it means the asset issuer (or owner for bitassets) does NOT have the permission to update\n * the corresponding flag, parameters or perform certain actions.\n * This is to be compatible with old client software.\n * - @ref lock_max_supply\n * - @ref disable_new_supply\n * - @ref disable_mcr_update\n * - @ref disable_icr_update\n * - @ref disable_mssr_update\n * - @ref disable_bsrm_update\n * - @ref disable_collateral_bidding\n *\n * @note\n * For @ref disable_mcr_update, @ref disable_icr_update and @ref disable_mssr_update,\n * if one of these is set in asset issuer permissions, and\n * - if the bitasset owner has set a value for the corresponding parameter, the value can not be updated,\n * - if the bitasset owner has not set a value for the corresponding parameter, the parameter can still be\n *   updated by the price feed producers.\n *\n */\nenum asset_issuer_permission_flags {\n    // Permission-enabling bits begin\n    // If one of the following bits is set in asset issuer permissions,\n    // it means the asset issuer (or owner for bitassets) has the permission to update\n    // the corresponding flag, parameters or perform certain actions.\n    // Note: This comment is copied and reformatted above for better Doxygen documentation formatting.\n    charge_market_fee    = 0x01, ///< market trades in this asset may be charged\n    white_list           = 0x02, ///< accounts must be whitelisted in order to hold or transact this asset\n    override_authority   = 0x04, ///< issuer may transfer asset back to himself\n    transfer_restricted  = 0x08, ///< require the issuer to be one party to every transfer\n    disable_force_settle = 0x10, ///< disable force settling\n    global_settle        = 0x20, ///< allow the bitasset owner to force a global settlement, permission only\n    disable_confidential = 0x40, ///< disallow the asset to be used with confidential transactions\n    witness_fed_asset    = 0x80, ///< the bitasset is to be fed by witnesses\n    committee_fed_asset  = 0x100, ///< the bitasset is to be fed by the committee\n    // Permission-enabling bits end\n\n    // Permission-disabling bits begin\n    // If one of the following bits is set in asset issuer permissions,\n    // it means the asset issuer (or owner for bitassets) does NOT have the permission to update\n    // the corresponding flag, parameters or perform certain actions.\n    // This is to be compatible with old client software.\n    // Note: This comment is copied and reformatted above for better Doxygen documentation formatting.\n    lock_max_supply      = 0x200, ///< the max supply of the asset can not be updated\n    disable_new_supply   = 0x400, ///< unable to create new supply for the asset\n    // For disable_mcr_update, disable_icr_update and disable_mssr_update,\n    // if one of these is set in asset issuer permissions, and\n    // - if the bitasset owner has set a value for the corresponding parameter, the value can not be updated,\n    // - if the bitasset owner has not set a value for the corresponding parameter, the parameter can still be\n    //   updated by the price feed producers.\n    // Note: This comment is copied and reformatted above for better Doxygen documentation formatting.\n    disable_mcr_update   = 0x800, ///< the bitasset owner can not update MCR, permission only\n    disable_icr_update   = 0x1000, ///< the bitasset owner can not update ICR, permission only\n    disable_mssr_update  = 0x2000, ///< the bitasset owner can not update MSSR, permission only\n    disable_bsrm_update  = 0x4000, ///< the bitasset owner can not update BSRM, permission only\n    disable_collateral_bidding = 0x8000  ///< Can not bid collateral after a global settlement\n    // Permission-disabling bits end\n};\n\n/// The bits that can be used in asset issuer permissions for non-UIA assets\nconst static uint16_t ASSET_ISSUER_PERMISSION_MASK =\n        charge_market_fee\n        | white_list\n        | override_authority\n        | transfer_restricted\n        | disable_force_settle\n        | global_settle\n        | disable_confidential\n        | witness_fed_asset\n        | committee_fed_asset\n        | lock_max_supply\n        | disable_new_supply\n        | disable_mcr_update\n        | disable_icr_update\n        | disable_mssr_update\n        | disable_bsrm_update\n        | disable_collateral_bidding;\n/// The \"enable\" bits for non-UIA assets\nconst static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK =\n        charge_market_fee\n        | white_list\n        | override_authority\n        | transfer_restricted\n        | disable_force_settle\n        | global_settle\n        | disable_confidential\n        | witness_fed_asset\n        | committee_fed_asset;\n/// The \"disable\" bits for non-UIA assets\nconst static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK =\n        lock_max_supply\n        | disable_new_supply\n        | disable_mcr_update\n        | disable_icr_update\n        | disable_mssr_update\n        | disable_bsrm_update\n        | disable_collateral_bidding;\n/// The bits that can be used in asset issuer permissions for UIA assets\nconst static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK =\n        charge_market_fee\n        | white_list\n        | override_authority\n        | transfer_restricted\n        | disable_confidential\n        | lock_max_supply\n        | disable_new_supply;\n/// The bits that can be used in asset issuer permissions for UIA assets before hf48/75\nconst static uint16_t DEFAULT_UIA_ASSET_ISSUER_PERMISSION =\n        charge_market_fee\n        | white_list\n        | override_authority\n        | transfer_restricted\n        | disable_confidential;\n/// The bits that can be used in asset issuer permissions for non-UIA assets but not for UIA assets\nconst static uint16_t NON_UIA_ONLY_ISSUER_PERMISSION_MASK =\n        ASSET_ISSUER_PERMISSION_MASK ^ UIA_ASSET_ISSUER_PERMISSION_MASK;\n/// The bits that can be used in asset issuer permissions but can not be used in flags\nconst static uint16_t PERMISSION_ONLY_MASK =\n        global_settle\n        | disable_mcr_update\n        | disable_icr_update\n        | disable_mssr_update\n        | disable_bsrm_update;\n/// The bits that can be used in flags for non-UIA assets\nconst static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & (uint16_t)(~PERMISSION_ONLY_MASK);\n/// the bits that can be used in flags for UIA assets\nconst static uint16_t UIA_VALID_FLAGS_MASK = UIA_ASSET_ISSUER_PERMISSION_MASK;\n\nenum reserved_spaces {\n    relative_protocol_ids = 0,\n    protocol_ids          = 1,\n    implementation_ids    = 2\n};\n\ninline bool is_relative(object_id_type o) { return o.space() == 0; }\n\nusing block_id_type = fc::ripemd160;\nusing checksum_type = fc::ripemd160;\nusing transaction_id_type = fc::ripemd160;\nusing digest_type = fc::sha256;\nusing signature_type = fc::ecc::compact_signature;\nusing share_type = safe<int64_t>;\nusing weight_type = uint16_t;\n\nstruct public_key_type {\n    struct binary_key {\n        binary_key() = default;\n        uint32_t check = 0;\n        fc::ecc::public_key_data data;\n    };\n    fc::ecc::public_key_data key_data;\n    public_key_type();\n    public_key_type(const fc::ecc::public_key_data& data);\n    public_key_type(const fc::ecc::public_key& pubkey);\n    explicit public_key_type(const std::string& base58str);\n    operator fc::ecc::public_key_data() const;\n    operator fc::ecc::public_key() const;\n    explicit operator std::string() const;\n    friend bool operator == (const public_key_type& p1, const fc::ecc::public_key& p2);\n    friend bool operator == (const public_key_type& p1, const public_key_type& p2);\n    friend bool operator != (const public_key_type& p1, const public_key_type& p2);\n    friend bool operator <  (const public_key_type& p1, const public_key_type& p2);\n};\n\nclass pubkey_comparator {\npublic:\n    inline bool operator()(const public_key_type& a, const public_key_type& b) const {\n        return a.key_data < b.key_data;\n    }\n};\n\nstruct fee_schedule;\n} }  // graphene::protocol\n\nnamespace fc {\nvoid to_variant(const graphene::protocol::public_key_type& var,  fc::variant& vo, uint32_t max_depth = 2);\nvoid from_variant(const fc::variant& var,  graphene::protocol::public_key_type& vo, uint32_t max_depth = 2);\n\n\ntemplate<>\nstruct get_typename<std::shared_ptr<const graphene::protocol::fee_schedule>> { static const char* name() {\n    return \"shared_ptr<const fee_schedule>\";\n} };\ntemplate<>\nstruct get_typename<std::shared_ptr<graphene::protocol::fee_schedule>> { static const char* name() {\n    return \"shared_ptr<fee_schedule>\";\n} };\nvoid from_variant( const fc::variant& var, std::shared_ptr<const graphene::protocol::fee_schedule>& vo,\n                   uint32_t max_depth = 2 );\n\n} // fc::raw\n\n\n/// Object types in the Protocol Space (enum object_type (1.x.x))\nGRAPHENE_DEFINE_IDS(protocol, protocol_ids, /*protocol objects are not prefixed*/,\n                    /* 1.0.x  */ (null) // no data\n                    /* 1.1.x  */ (base) // no data\n                    /* 1.2.x  */ (account)\n                    /* 1.3.x  */ (asset)\n                    /* 1.4.x  */ (force_settlement)\n                    /* 1.5.x  */ (committee_member)\n                    /* 1.6.x  */ (witness)\n                    /* 1.7.x  */ (limit_order)\n                    /* 1.8.x  */ (call_order)\n                    /* 1.9.x  */ (custom) // unused\n                    /* 1.10.x */ (proposal)\n                    /* 1.11.x */ (operation_history) // strictly speaking it is not in protocol\n                    /* 1.12.x */ (withdraw_permission)\n                    /* 1.13.x */ (vesting_balance)\n                    /* 1.14.x */ (worker)\n                    /* 1.15.x */ (balance)\n                    /* 1.16.x */ (htlc)\n                    /* 1.17.x */ (custom_authority)\n                    /* 1.18.x */ (ticket)\n                    /* 1.19.x */ (liquidity_pool)\n                    /* 1.20.x */ (samet_fund)\n                    /* 1.21.x */ (credit_offer)\n                    /* 1.22.x */ (credit_deal)\n                   )\n\nFC_REFLECT(graphene::protocol::public_key_type, (key_data))\nFC_REFLECT(graphene::protocol::public_key_type::binary_key, (data)(check))\n\nFC_REFLECT_TYPENAME(graphene::protocol::share_type)\nFC_REFLECT(graphene::protocol::void_t,)\n\nFC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags,\n                (charge_market_fee)\n                (white_list)\n                (transfer_restricted)\n                (override_authority)\n                (disable_force_settle)\n                (global_settle)\n                (disable_confidential)\n                (witness_fed_asset)\n                (committee_fed_asset)\n                (lock_max_supply)\n                (disable_new_supply)\n                (disable_mcr_update)\n                (disable_icr_update)\n                (disable_mssr_update)\n                (disable_bsrm_update)\n                (disable_collateral_bidding)\n               )\n\nnamespace fc { namespace raw {\n   extern template void pack( datastream<size_t>& s, const graphene::protocol::public_key_type& tx,\n                              uint32_t _max_depth );\n   extern template void pack( datastream<char*>& s, const graphene::protocol::public_key_type& tx,\n                              uint32_t _max_depth );\n   extern template void unpack( datastream<const char*>& s, graphene::protocol::public_key_type& tx,\n                                uint32_t _max_depth );\n} } // fc::raw\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/vesting.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol { \n\n   struct linear_vesting_policy_initializer\n   {\n      /** while vesting begins on begin_timestamp, none may be claimed before vesting_cliff_seconds have passed */\n      fc::time_point_sec begin_timestamp;\n      uint32_t           vesting_cliff_seconds = 0;\n      uint32_t           vesting_duration_seconds = 0;\n   };\n\n   struct cdd_vesting_policy_initializer\n   {\n      /** while coindays may accrue over time, none may be claimed before the start_claim time */\n      fc::time_point_sec start_claim;\n      uint32_t           vesting_seconds = 0;\n      cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){}\n   };\n\n   struct instant_vesting_policy_initializer\n   {\n   };\n\n   typedef fc::static_variant<\n      linear_vesting_policy_initializer,\n      cdd_vesting_policy_initializer,\n      instant_vesting_policy_initializer\n   > vesting_policy_initializer;\n\n\n   /**\n    * @brief Create a vesting balance.\n    * @ingroup operations\n    *\n    *  The chain allows a user to create a vesting balance.\n    *  Normally, vesting balances are created automatically as part\n    *  of cashback and worker operations.  This operation allows\n    *  vesting balances to be created manually as well.\n    *\n    *  Manual creation of vesting balances can be used by a stakeholder\n    *  to publicly demonstrate that they are committed to the chain.\n    *  It can also be used as a building block to create transactions\n    *  that function like public debt.  Finally, it is useful for\n    *  testing vesting balance functionality.\n    *\n    * @return ID of newly created vesting_balance_object\n    */\n   struct vesting_balance_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                       fee;\n      account_id_type             creator; ///< Who provides funds initially\n      account_id_type             owner; ///< Who is able to withdraw the balance\n      asset                       amount;\n      vesting_policy_initializer  policy;\n\n      account_id_type   fee_payer()const { return creator; }\n      void              validate()const\n      {\n         FC_ASSERT( fee.amount >= 0 );\n         FC_ASSERT( amount.amount > 0 );\n      }\n   };\n\n   /**\n    * @brief Withdraw from a vesting balance.\n    * @ingroup operations\n    *\n    * Withdrawal from a not-completely-mature vesting balance\n    * will result in paying fees.\n    *\n    * @return Nothing\n    */\n   struct vesting_balance_withdraw_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                   fee;\n      vesting_balance_id_type vesting_balance;\n      account_id_type         owner; ///< Must be vesting_balance.owner\n      asset                   amount;\n\n      account_id_type   fee_payer()const { return owner; }\n      void              validate()const\n      {\n         FC_ASSERT( fee.amount >= 0 );\n         FC_ASSERT( amount.amount > 0 );\n         FC_ASSERT( owner != GRAPHENE_TEMP_ACCOUNT );\n      }\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::vesting_balance_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::vesting_balance_withdraw_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy) )\nFC_REFLECT( graphene::protocol::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) )\n\nFC_REFLECT(graphene::protocol::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) )\nFC_REFLECT(graphene::protocol::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) )\nFC_REFLECT_EMPTY( graphene::protocol::instant_vesting_policy_initializer )\nFC_REFLECT_TYPENAME( graphene::protocol::vesting_policy_initializer )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/vote.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#pragma once\n\n#include <graphene/protocol/types.hpp>\n\nnamespace graphene { namespace protocol {\n\n/**\n * @brief An ID for some votable object\n *\n * This class stores an ID for a votable object. The ID is comprised of two fields: a type, and an instance. The\n * type field stores which kind of object is being voted on, and the instance stores which specific object of that\n * type is being referenced by this ID.\n *\n * A value of vote_id_type is implicitly convertible to an unsigned 32-bit integer containing only the instance. It\n * may also be implicitly assigned from a uint32_t, which will update the instance. It may not, however, be\n * implicitly constructed from a uint32_t, as in this case, the type would be unknown.\n *\n * On the wire, a vote_id_type is represented as a 32-bit integer with the type in the lower 8 bits and the instance\n * in the upper 24 bits. This means that types may never exceed 8 bits, and instances may never exceed 24 bits.\n *\n * In JSON, a vote_id_type is represented as a string \"type:instance\", i.e. \"1:5\" would be type 1 and instance 5.\n *\n * @note In the BitShares protocol, vote_id_type instances are unique across types; that is to say, if an object of\n * type 1 has instance 4, an object of type 0 may not also have instance 4. In other words, the type is not a\n * namespace for instances; it is only an informational field.\n */\nstruct vote_id_type\n{\n   /// Lower 8 bits are type; upper 24 bits are instance.\n   /// By default type and instance are both set to 0.\n   uint32_t content = 0;\n\n   friend size_t hash_value( vote_id_type v ) { return std::hash<uint32_t>()(v.content); }\n   enum vote_type\n   {\n      committee,\n      witness,\n      worker,\n      VOTE_TYPE_COUNT\n   };\n\n   vote_id_type() = default;\n   /// Construct this vote_id_type with provided type and instance\n   explicit vote_id_type(vote_type type, uint32_t instance = 0)\n      : content(instance<<8 | type)\n   {}\n   /// Construct this vote_id_type from a serial string in the form \"type:instance\"\n   explicit vote_id_type(const std::string& serial)\n   { try {\n      auto colon = serial.find(':');\n      FC_ASSERT( colon != std::string::npos );\n      *this = vote_id_type( vote_type( std::stoul(serial.substr(0, colon)) ),\n                            uint32_t( std::stoul(serial.substr(colon+1)) ) );\n   } FC_CAPTURE_AND_RETHROW( (serial) ) }\n\n   /// Set the type of this vote_id_type\n   void set_type(vote_type type)\n   {\n      content &= 0xffffff00;\n      content |= type & 0xff;\n   }\n   /// Get the type of this vote_id_type\n   vote_type type()const\n   {\n      return vote_type(content & 0xff);\n   }\n\n   /// Set the instance of this vote_id_type\n   void set_instance(uint32_t instance)\n   {\n      assert(instance < 0x01000000);\n      content &= 0xff;\n      content |= instance << 8;\n   }\n   /// Get the instance of this vote_id_type\n   uint32_t instance()const\n   {\n      return content >> 8;\n   }\n\n   vote_id_type& operator =(vote_id_type other)\n   {\n      content = other.content;\n      return *this;\n   }\n   /// Set the instance of this vote_id_type\n   vote_id_type& operator =(uint32_t instance)\n   {\n      set_instance(instance);\n      return *this;\n   }\n   /// Get the instance of this vote_id_type\n   operator uint32_t()const\n   {\n      return instance();\n   }\n\n   /// Convert this vote_id_type to a serial string in the form \"type:instance\"\n   explicit operator std::string()const\n   {\n      return std::to_string(type()) + \":\" + std::to_string(instance());\n   }\n};\n\n} } // graphene::protocol\n\nnamespace fc\n{\n\nclass variant;\n\nvoid to_variant( const graphene::protocol::vote_id_type& var, fc::variant& vo, uint32_t max_depth = 1 );\nvoid from_variant( const fc::variant& var, graphene::protocol::vote_id_type& vo, uint32_t max_depth = 1 );\n\n} // fc\n\nFC_REFLECT_TYPENAME( fc::flat_set<graphene::protocol::vote_id_type> )\n\nFC_REFLECT_ENUM( graphene::protocol::vote_id_type::vote_type, (witness)(committee)(worker)(VOTE_TYPE_COUNT) )\nFC_REFLECT( graphene::protocol::vote_id_type, (content) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vote_id_type )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/withdraw_permission.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/memo.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @brief Create a new withdrawal permission\n    * @ingroup operations\n    *\n    * This operation creates a withdrawal permission, which allows some authorized account to withdraw from an\n    * authorizing account. This operation is primarily useful for scheduling recurring payments.\n    *\n    * Withdrawal permissions define withdrawal periods, which is a span of time during which the authorized account may\n    * make a withdrawal. Any number of withdrawals may be made so long as the total amount withdrawn per period does\n    * not exceed the limit for any given period.\n    *\n    * Withdrawal permissions authorize only a specific pairing, i.e. a permission only authorizes one specified\n    * authorized account to withdraw from one specified authorizing account. Withdrawals are limited and may not exceet\n    * the withdrawal limit. The withdrawal must be made in the same asset as the limit; attempts with withdraw any\n    * other asset type will be rejected.\n    *\n    * The fee for this operation is paid by withdraw_from_account, and this account is required to authorize this\n    * operation.\n    */\n   struct withdraw_permission_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee =  GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset             fee;\n      /// The account authorizing withdrawals from its balances\n      account_id_type   withdraw_from_account;\n      /// The account authorized to make withdrawals from withdraw_from_account\n      account_id_type   authorized_account;\n      /// The maximum amount authorized_account is allowed to withdraw in a given withdrawal period\n      asset             withdrawal_limit;\n      /// Length of the withdrawal period in seconds\n      uint32_t          withdrawal_period_sec = 0;\n      /// The number of withdrawal periods this permission is valid for\n      uint32_t          periods_until_expiration = 0;\n      /// Time at which the first withdrawal period begins; must be in the future\n      time_point_sec    period_start_time;\n\n      account_id_type fee_payer()const { return withdraw_from_account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Update an existing withdraw permission\n    * @ingroup operations\n    *\n    * This oeration is used to update the settings for an existing withdrawal permission. The accounts to withdraw to\n    * and from may never be updated. The fields which may be updated are the withdrawal limit (both amount and asset\n    * type may be updated), the withdrawal period length, the remaining number of periods until expiration, and the\n    * starting time of the new period.\n    *\n    * Fee is paid by withdraw_from_account, which is required to authorize this operation\n    */\n   struct withdraw_permission_update_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee =  GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                         fee;\n      /// This account pays the fee. Must match permission_to_update->withdraw_from_account\n      account_id_type               withdraw_from_account;\n      /// The account authorized to make withdrawals. Must match permission_to_update->authorized_account\n      account_id_type               authorized_account;\n      /// ID of the permission which is being updated\n      withdraw_permission_id_type   permission_to_update;\n      /// New maximum amount the withdrawer is allowed to charge per withdrawal period\n      asset                         withdrawal_limit;\n      /// New length of the period between withdrawals\n      uint32_t                      withdrawal_period_sec = 0;\n      /// New beginning of the next withdrawal period; must be in the future\n      time_point_sec                period_start_time;\n      /// The new number of withdrawal periods for which this permission will be valid\n      uint32_t                      periods_until_expiration = 0;\n\n      account_id_type fee_payer()const { return withdraw_from_account; }\n      void            validate()const;\n   };\n\n   /**\n    * @brief Withdraw from an account which has published a withdrawal permission\n    * @ingroup operations\n    *\n    * This operation is used to withdraw from an account which has authorized such a withdrawal. It may be executed at\n    * most once per withdrawal period for the given permission. On execution, amount_to_withdraw is transferred from\n    * withdraw_from_account to withdraw_to_account, assuming amount_to_withdraw is within the withdrawal limit. The\n    * withdrawal permission will be updated to note that the withdrawal for the current period has occurred, and\n    * further withdrawals will not be permitted until the next withdrawal period, assuming the permission has not\n    * expired. This operation may be executed at any time within the current withdrawal period.\n    *\n    * Fee is paid by withdraw_to_account, which is required to authorize this operation\n    */\n   struct withdraw_permission_claim_operation : public base_operation\n   {\n      struct fee_params_t {\n         uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION;\n         uint32_t price_per_kbyte = 10;\n      };\n\n      /// Paid by withdraw_to_account\n      asset                       fee;\n      /// ID of the permission authorizing this withdrawal\n      withdraw_permission_id_type withdraw_permission;\n      /// Must match withdraw_permission->withdraw_from_account\n      account_id_type             withdraw_from_account;\n      /// Must match withdraw_permision->authorized_account\n      account_id_type             withdraw_to_account;\n      /// Amount to withdraw. Must not exceed withdraw_permission->withdrawal_limit\n      asset                       amount_to_withdraw;\n      /// Memo for withdraw_from_account. Should generally be encrypted with withdraw_from_account->memo_key\n      optional<memo_data>         memo;\n\n      account_id_type fee_payer()const { return withdraw_to_account; }\n      void            validate()const;\n      share_type      calculate_fee(const fee_params_t& k)const;\n   };\n\n   /**\n    * @brief Delete an existing withdrawal permission\n    * @ingroup operations\n    *\n    * This operation cancels a withdrawal permission, thus preventing any future withdrawals using that permission.\n    *\n    * Fee is paid by withdraw_from_account, which is required to authorize this operation\n    */\n   struct withdraw_permission_delete_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 0; };\n\n      asset                         fee;\n      /// Must match withdrawal_permission->withdraw_from_account. This account pays the fee.\n      account_id_type               withdraw_from_account;\n      /// The account previously authorized to make withdrawals. Must match withdrawal_permission->authorized_account\n      account_id_type               authorized_account;\n      /// ID of the permission to be revoked.\n      withdraw_permission_id_type   withdrawal_permission;\n\n      account_id_type fee_payer()const { return withdraw_from_account; }\n      void            validate()const;\n   };\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::withdraw_permission_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::withdraw_permission_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::withdraw_permission_claim_operation::fee_params_t, (fee)(price_per_kbyte) )\nFC_REFLECT( graphene::protocol::withdraw_permission_delete_operation::fee_params_t, (fee) )\n\nFC_REFLECT( graphene::protocol::withdraw_permission_create_operation,\n            (fee)(withdraw_from_account)(authorized_account)\n            (withdrawal_limit)(withdrawal_period_sec)(periods_until_expiration)(period_start_time) )\nFC_REFLECT( graphene::protocol::withdraw_permission_update_operation,\n            (fee)(withdraw_from_account)(authorized_account)\n            (permission_to_update)(withdrawal_limit)(withdrawal_period_sec)\n            (period_start_time)(periods_until_expiration) )\nFC_REFLECT( graphene::protocol::withdraw_permission_claim_operation,\n            (fee)(withdraw_permission)(withdraw_from_account)(withdraw_to_account)(amount_to_withdraw)(memo) )\nFC_REFLECT( graphene::protocol::withdraw_permission_delete_operation,\n            (fee)(withdraw_from_account)(authorized_account)\n            (withdrawal_permission) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/witness.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol { \n\n  /**\n    * @brief Create a witness object, as a bid to hold a witness position on the network.\n    * @ingroup operations\n    *\n    * Accounts which wish to become witnesses may use this operation to create a witness object which stakeholders may\n    * vote on to approve its position as a witness.\n    */\n   struct witness_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset             fee;\n      /// The account which owns the witness. This account pays the fee for this operation.\n      account_id_type   witness_account;\n      string            url;\n      public_key_type   block_signing_key;\n\n      account_id_type fee_payer()const { return witness_account; }\n      void            validate()const;\n   };\n\n  /**\n    * @brief Update a witness object's URL and block signing key.\n    * @ingroup operations\n    */\n   struct witness_update_operation : public base_operation\n   {\n      struct fee_params_t\n      {\n         share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      };\n\n      asset             fee;\n      /// The witness object to update.\n      witness_id_type   witness;\n      /// The account which owns the witness. This account pays the fee for this operation.\n      account_id_type   witness_account;\n      /// The new URL.\n      optional< string > new_url;\n      /// The new block signing key.\n      optional< public_key_type > new_signing_key;\n\n      account_id_type fee_payer()const { return witness_account; }\n      void            validate()const;\n   };\n\n   /// TODO: witness_resign_operation : public base_operation\n\n} } // graphene::protocol\n\nFC_REFLECT( graphene::protocol::witness_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::witness_create_operation, (fee)(witness_account)(url)(block_signing_key) )\n\nFC_REFLECT( graphene::protocol::witness_update_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::witness_update_operation, (fee)(witness)(witness_account)(new_url)(new_signing_key) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation )\n"
  },
  {
    "path": "libraries/protocol/include/graphene/protocol/worker.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n\nnamespace graphene { namespace protocol {\n\n   /**\n    * @defgroup workers The Blockchain Worker System\n    * @ingroup operations\n    *\n    * BitShares blockchains allow the creation of special \"workers\" which are elected positions paid by the blockchain\n    * for services they provide. There may be several types of workers, and the semantics of how and when they are paid\n    * are defined by the @ref graphene::chain::worker_type enumeration.\n    * All workers are elected by core stakeholder approval, by\n    * voting for or against them.\n    *\n    * Workers are paid from the blockchain's daily budget if their total approval (votes for - votes against) is\n    * positive, ordered from most positive approval to least, until the budget is exhausted. Payments are processed at\n    * the blockchain maintenance interval. If a worker does not have positive approval during payment processing, or if\n    * the chain's budget is exhausted before the worker is paid, that worker is simply not paid at that interval.\n    * Payment is not prorated based on percentage of the interval the worker was approved. If the chain attempts to pay\n    * a worker, but the budget is insufficient to cover its entire pay, the worker is paid the remaining budget funds,\n    * even though this does not fulfill his total pay. The worker will not receive extra pay to make up the difference\n    * later. Worker pay is placed in a vesting balance and vests over the number of days specified at the worker's\n    * creation.\n    *\n    * Once created, a worker is immutable and will be kept by the blockchain forever.\n    *\n    * @{\n    */\n\n\n   struct vesting_balance_worker_initializer\n   {\n      vesting_balance_worker_initializer(uint16_t days=0):pay_vesting_period_days(days){}\n      uint16_t pay_vesting_period_days = 0;\n   };\n\n   struct burn_worker_initializer\n   {};\n\n   struct refund_worker_initializer\n   {};\n\n\n   typedef static_variant< \n      refund_worker_initializer,\n      vesting_balance_worker_initializer,\n      burn_worker_initializer > worker_initializer;\n\n\n   /**\n    * @brief Create a new worker object\n    * @ingroup operations\n    */\n   struct worker_create_operation : public base_operation\n   {\n      struct fee_params_t { uint64_t fee = 5000*GRAPHENE_BLOCKCHAIN_PRECISION; };\n\n      asset                fee;\n      account_id_type      owner;\n      time_point_sec       work_begin_date;\n      time_point_sec       work_end_date;\n      share_type           daily_pay;\n      string               name;\n      string               url;\n      /// This should be set to the initializer appropriate for the type of worker to be created.\n      worker_initializer   initializer;\n\n      account_id_type   fee_payer()const { return owner; }\n      void              validate()const;\n   };\n   ///@}\n\n} }\n\nFC_REFLECT( graphene::protocol::vesting_balance_worker_initializer, (pay_vesting_period_days) )\nFC_REFLECT( graphene::protocol::burn_worker_initializer, )\nFC_REFLECT( graphene::protocol::refund_worker_initializer, )\nFC_REFLECT_TYPENAME( graphene::protocol::worker_initializer )\n\nFC_REFLECT( graphene::protocol::worker_create_operation::fee_params_t, (fee) )\nFC_REFLECT( graphene::protocol::worker_create_operation,\n            (fee)(owner)(work_begin_date)(work_end_date)(daily_pay)(name)(url)(initializer) )\n\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation::fee_params_t )\nGRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation )\n"
  },
  {
    "path": "libraries/protocol/liquidity_pool.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/liquidity_pool.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid liquidity_pool_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( asset_a < asset_b, \"ID of the first asset should be smaller than ID of the second asset\" );\n   FC_ASSERT( asset_a != share_asset && asset_b != share_asset,\n              \"Share asset can not be the same as one of the assets in the pool\" );\n   FC_ASSERT( taker_fee_percent <= GRAPHENE_100_PERCENT, \"Taker fee percent should not exceed 100%\" );\n   FC_ASSERT( withdrawal_fee_percent <= GRAPHENE_100_PERCENT, \"Withdrawal fee percent should not exceed 100%\" );\n}\n\nvoid liquidity_pool_delete_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n}\n\nvoid liquidity_pool_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( taker_fee_percent.valid() || withdrawal_fee_percent.valid(), \"Should update something\" );\n   if( taker_fee_percent.valid() )\n      FC_ASSERT( *taker_fee_percent <= GRAPHENE_100_PERCENT, \"Taker fee percent should not exceed 100%\" );\n   if( withdrawal_fee_percent.valid() )\n      FC_ASSERT( 0 == *withdrawal_fee_percent, \"Withdrawal fee percent can only be updated to zero\" );\n}\n\nvoid liquidity_pool_deposit_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( amount_a.amount > 0 && amount_b.amount > 0, \"Both amounts of the assets should be positive\" );\n   FC_ASSERT( amount_a.asset_id < amount_b.asset_id,\n              \"ID of the first asset should be smaller than ID of the second asset\" );\n}\n\nvoid liquidity_pool_withdraw_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( share_amount.amount > 0, \"Amount of the share asset should be positive\" );\n}\n\nvoid liquidity_pool_exchange_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( amount_to_sell.amount > 0, \"Amount to sell should be positive\" );\n   FC_ASSERT( min_to_receive.amount > 0, \"Minimum amount to receive should be positive\" );\n   FC_ASSERT( amount_to_sell.asset_id != min_to_receive.asset_id,\n             \"ID of the two assets should not be the same\" );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation::fee_params_t )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation )\n"
  },
  {
    "path": "libraries/protocol/market.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/market.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid create_take_profit_order_action::validate() const\n{\n   FC_ASSERT( spread_percent > 0, \"The spread percentage must be positive\" );\n   FC_ASSERT( size_percent > 0, \"The size percentage must be positive\" );\n   FC_ASSERT( size_percent <= GRAPHENE_100_PERCENT, \"The size percentage must not exceed 100%\" );\n   FC_ASSERT( expiration_seconds > 0, \"The expiration seconds must be positive\" );\n}\n\nstruct lo_action_validate_visitor\n{\n   using result_type = void;\n\n   template<typename ActionType>\n   result_type operator()( const ActionType& action )const\n   {\n      action.validate();\n   }\n};\n\nvoid limit_order_create_operation::validate()const\n{\n   FC_ASSERT( amount_to_sell.asset_id != min_to_receive.asset_id );\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( amount_to_sell.amount > 0 );\n   FC_ASSERT( min_to_receive.amount > 0 );\n\n   if( extensions.value.on_fill.valid() )\n   {\n      // Note: an empty on_fill action list is allowed\n      for( const auto& action : *extensions.value.on_fill )\n         action.visit( lo_action_validate_visitor() );\n   }\n\n}\n\nvoid limit_order_update_operation::validate() const\n{ try {\n   FC_ASSERT(fee.amount >= 0, \"Fee must not be negative\");\n   FC_ASSERT(new_price || delta_amount_to_sell || new_expiration || on_fill,\n             \"Cannot update limit order if nothing is specified to update\");\n   if (new_price)\n      new_price->validate();\n   if (delta_amount_to_sell)\n      FC_ASSERT(delta_amount_to_sell->amount != 0, \"Cannot change limit order amount by zero\");\n\n   if( on_fill.valid() )\n   {\n      // Note: an empty on_fill action list is allowed\n      for( const auto& action : *on_fill )\n         action.visit( lo_action_validate_visitor() );\n   }\n\n} FC_CAPTURE_AND_RETHROW((*this)) } // GCOVR_EXCL_LINE\n\nvoid limit_order_cancel_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n}\n\nvoid call_order_update_operation::validate()const\n{ try {\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( delta_collateral.asset_id != delta_debt.asset_id );\n   FC_ASSERT( delta_collateral.amount != 0 || delta_debt.amount != 0 );\n\n   // note: no validation is needed for extensions so far: the only attribute inside is target_collateral_ratio\n\n} FC_CAPTURE_AND_RETHROW((*this)) } // GCOVR_EXCL_LINE\n\nvoid bid_collateral_operation::validate()const\n{ try {\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( debt_covered.amount == 0 || (debt_covered.amount > 0 && additional_collateral.amount > 0) );\n} FC_CAPTURE_AND_RETHROW((*this)) } // GCOVR_EXCL_LINE\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::create_take_profit_order_action )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::options_type )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::options_type )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_params_t )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::fill_order_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::execute_bid_operation )\n"
  },
  {
    "path": "libraries/protocol/memo.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/memo.hpp>\n#include <boost/endian/conversion.hpp>\n#include <fc/crypto/aes.hpp>\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::public_key& pub,\n                            const string& msg, uint64_t custom_nonce)\n{\n   if( priv != fc::ecc::private_key() && public_key_type(pub) != public_key_type() )\n   {\n      from = priv.get_public_key();\n      to = pub;\n      if( 0 == custom_nonce )\n      {\n         uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0].value();\n         constexpr uint64_t half_size = 32;\n         constexpr uint64_t high_bits = 0xff00000000000000ULL;\n         constexpr uint64_t  low_bits = 0x00ffffffffffffffULL;\n         entropy <<= half_size;\n         entropy &= high_bits;\n         nonce = ((uint64_t)(fc::time_point::now().time_since_epoch().count()) & low_bits) | entropy;\n      } else\n         nonce = custom_nonce;\n      auto secret = priv.get_shared_secret(pub);\n      auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());\n      string text = memo_message((uint32_t)digest_type::hash(msg)._hash[0].value(), msg).serialize();\n      message = fc::aes_encrypt( nonce_plus_secret, vector<char>(text.begin(), text.end()) );\n   }\n   else\n   {\n      auto text = memo_message(0, msg).serialize();\n      message = vector<char>(text.begin(), text.end());\n   }\n}\n\nstring memo_data::get_message(const fc::ecc::private_key& priv,\n                              const fc::ecc::public_key& pub)const\n{\n   if( from != public_key_type() )\n   {\n      auto secret = priv.get_shared_secret(pub);\n      auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());\n      auto plain_text = fc::aes_decrypt( nonce_plus_secret, message );\n      auto result = memo_message::deserialize(string(plain_text.begin(), plain_text.end()));\n      FC_ASSERT( result.checksum == (uint32_t)digest_type::hash(result.text)._hash[0].value() );\n      return result.text;\n   }\n   else\n   {\n      return memo_message::deserialize(string(message.begin(), message.end())).text;\n   }\n}\n\nstring memo_message::serialize() const\n{\n   auto serial_checksum = string(sizeof(checksum), ' ');\n   (uint32_t&)(*serial_checksum.data()) = boost::endian::native_to_little(checksum);\n   return serial_checksum + text;\n}\n\nmemo_message memo_message::deserialize(const string& serial)\n{\n   memo_message result;\n   FC_ASSERT( serial.size() >= sizeof(result.checksum) );\n   result.checksum = boost::endian::little_to_native((uint32_t&)(*serial.data()));\n   result.text = serial.substr(sizeof(result.checksum));\n   return result;\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::memo_message )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::memo_data )\n"
  },
  {
    "path": "libraries/protocol/operations.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/operations.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n#include <fc/uint128.hpp>\n\nnamespace graphene { namespace protocol {\n\nuint64_t base_operation::calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte )\n{\n   auto result = (fc::uint128_t(bytes) * price_per_kbyte) / 1024;\n   FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );\n   return static_cast<uint64_t>(result);\n}\n\nfc::optional< fc::future<void> > base_operation::validate_parallel( uint32_t skip )const\n{\n   validate();\n   return fc::optional< fc::future<void> >();\n}\n\nvoid balance_claim_operation::validate()const\n{\n   FC_ASSERT( fee == asset() );\n   FC_ASSERT( balance_owner_key != public_key_type() );\n}\n\n/**\n * @brief Used to validate operations in a polymorphic manner\n */\nstruct operation_validator\n{\n   typedef void result_type;\n   template<typename T>\n   void operator()( const T& v )const { v.validate(); }\n};\n\nstruct operation_get_required_auth\n{\n   using result_type = void;\n\n   flat_set<account_id_type>& active;\n   flat_set<account_id_type>& owner;\n   vector<authority>&         other;\n   bool                       ignore_custom_op_reqd_auths;\n\n\n   operation_get_required_auth( flat_set<account_id_type>& a,\n                                flat_set<account_id_type>& own,\n                                vector<authority>& oth,\n                                bool ignore_custom_operation_required_auths )\n       : active( a ), owner( own ), other( oth ),\n         ignore_custom_op_reqd_auths( ignore_custom_operation_required_auths )\n   {}\n\n   template<typename T>\n   void operator()( const T& v ) const {\n      active.insert( v.fee_payer() );\n      v.get_required_active_authorities( active );\n      v.get_required_owner_authorities( owner );\n      v.get_required_authorities( other );\n   }\n\n   void operator()( const custom_operation& op ) const {\n      active.insert( op.fee_payer() );\n      if( !ignore_custom_op_reqd_auths ) {\n         op.get_required_active_authorities( active );\n         op.get_required_owner_authorities( owner );\n         op.get_required_authorities( other );\n      }\n   }\n};\n\nvoid operation_validate( const operation& op )\n{\n   op.visit( operation_validator() );\n}\n\nvoid operation_get_required_authorities( const operation& op,\n                                         flat_set<account_id_type>& active,\n                                         flat_set<account_id_type>& owner,\n                                         vector<authority>& other,\n                                         bool ignore_custom_operation_required_auths )\n{\n   op.visit( operation_get_required_auth( active, owner, other, ignore_custom_operation_required_auths ) );\n}\n\n} } // namespace graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::generic_operation_result )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::generic_exchange_operation_result )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::extendable_operation_result_dtl )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::op_wrapper )\n"
  },
  {
    "path": "libraries/protocol/proposal.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/operations.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nproposal_create_operation proposal_create_operation::committee_proposal(const chain_parameters& global_params, fc::time_point_sec head_block_time )\n{\n   // TODO move this method to unit tests as it is not useful\n   proposal_create_operation op;\n   op.expiration_time = head_block_time + global_params.maximum_proposal_lifetime;\n   op.review_period_seconds = global_params.committee_proposal_review_period;\n   return op;\n}\n\nvoid proposal_create_operation::validate() const\n{\n   FC_ASSERT( !proposed_ops.empty() );\n   for( const auto& op : proposed_ops ) operation_validate( op.op );\n}\n\nshare_type proposal_create_operation::calculate_fee(const fee_params_t& k) const\n{\n   return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );\n}\n\nvoid proposal_update_operation::validate() const\n{\n   FC_ASSERT(fee.amount >= 0);\n   FC_ASSERT(!(active_approvals_to_add.empty() && active_approvals_to_remove.empty() &&\n               owner_approvals_to_add.empty() && owner_approvals_to_remove.empty() &&\n               key_approvals_to_add.empty() && key_approvals_to_remove.empty()));\n   for( auto a : active_approvals_to_add )\n   {\n      FC_ASSERT(active_approvals_to_remove.find(a) == active_approvals_to_remove.end(),\n                \"Cannot add and remove approval at the same time.\");\n   }\n   for( auto a : owner_approvals_to_add )\n   {\n      FC_ASSERT(owner_approvals_to_remove.find(a) == owner_approvals_to_remove.end(),\n                \"Cannot add and remove approval at the same time.\");\n   }\n   for( auto a : key_approvals_to_add )\n   {\n      FC_ASSERT(key_approvals_to_remove.find(a) == key_approvals_to_remove.end(),\n                \"Cannot add and remove approval at the same time.\");\n   }\n}\n\nvoid proposal_delete_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n}\n\nshare_type proposal_update_operation::calculate_fee(const fee_params_t& k) const\n{\n   return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );\n}\n\nvoid proposal_update_operation::get_required_authorities( vector<authority>& o )const\n{\n   authority auth;\n   for( const auto& k : key_approvals_to_add )\n      auth.key_auths[k] = 1;\n   for( const auto& k : key_approvals_to_remove )\n      auth.key_auths[k] = 1;\n   auth.weight_threshold = auth.key_auths.size();\n\n   if( auth.weight_threshold > 0 )\n      o.emplace_back( std::move(auth) );\n}\n\nvoid proposal_update_operation::get_required_active_authorities( flat_set<account_id_type>& a )const\n{\n   for( const auto& i : active_approvals_to_add )    a.insert(i);\n   for( const auto& i : active_approvals_to_remove ) a.insert(i);\n}\n\nvoid proposal_update_operation::get_required_owner_authorities( flat_set<account_id_type>& a )const\n{\n   for( const auto& i : owner_approvals_to_add )    a.insert(i);\n   for( const auto& i : owner_approvals_to_remove ) a.insert(i);\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation )\n"
  },
  {
    "path": "libraries/protocol/pts_address.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/pts_address.hpp>\n\n#include <fc/crypto/base58.hpp>\n#include <fc/crypto/elliptic.hpp>\n#include <fc/crypto/ripemd160.hpp>\n#include <fc/io/raw.hpp>\n#include <algorithm>\n\nnamespace graphene { namespace protocol {\n\n   pts_address::pts_address()\n   {\n      memset( addr.data(), 0, addr.size() );\n   }\n\n   pts_address::pts_address( const std::string& base58str )\n   {\n      std::vector<char> v = fc::from_base58( fc::string(base58str) );\n      if( v.size() )\n         memcpy( addr.data(), v.data(), std::min<size_t>( v.size(), addr.size() ) );\n\n      FC_ASSERT(is_valid(), \"invalid pts_address ${a}\", (\"a\", base58str));\n   }\n\n   pts_address::pts_address( const fc::ecc::public_key& pub, bool compressed, uint8_t version )\n   {\n       fc::sha256 sha2;\n       if( compressed )\n       {\n           auto dat = pub.serialize();\n           sha2     = fc::sha256::hash((char*) dat.data(), dat.size() );\n       }\n       else\n       {\n           auto dat = pub.serialize_ecc_point();\n           sha2     = fc::sha256::hash((char*) dat.data(), dat.size() );\n       }\n       auto rep      = fc::ripemd160::hash((char*)&sha2,sizeof(sha2));\n       addr[0]  = version;\n       memcpy( addr.data() + 1, (char*)&rep, sizeof(rep) );\n       auto check    = fc::sha256::hash( addr.data(), sizeof(rep)+1 );\n       check = fc::sha256::hash(check);\n       memcpy( addr.data() + 1 + sizeof(rep), (char*)&check, 4 );\n   }\n\n   /**\n    *  Checks the address to verify it has a\n    *  valid checksum\n    */\n   bool pts_address::is_valid()const\n   {\n       auto check    = fc::sha256::hash( addr.data(), sizeof(fc::ripemd160)+1 );\n       check = fc::sha256::hash(check);\n       return memcmp( addr.data() + 1 + sizeof(fc::ripemd160), (char*)&check, 4 ) == 0;\n   }\n\n   pts_address::operator std::string()const\n   {\n        return fc::to_base58( addr.data(), addr.size() );\n   }\n\n} } // namespace graphene\n\nnamespace fc\n{\n   void to_variant( const graphene::protocol::pts_address& var,  variant& vo, uint32_t max_depth )\n   {\n        vo = std::string(var);\n   }\n   void from_variant( const variant& var,  graphene::protocol::pts_address& vo, uint32_t max_depth )\n   {\n        vo = graphene::protocol::pts_address( var.as_string() );\n   }\n\nnamespace raw {\n   template void pack( datastream<size_t>& s, const graphene::protocol::pts_address& tx,\n                       uint32_t _max_depth=FC_PACK_MAX_DEPTH );\n   template void pack( datastream<char*>& s, const graphene::protocol::pts_address& tx,\n                       uint32_t _max_depth=FC_PACK_MAX_DEPTH );\n   template void unpack( datastream<const char*>& s, graphene::protocol::pts_address& tx,\n                         uint32_t _max_depth=FC_PACK_MAX_DEPTH );\n} } // fc::raw\n"
  },
  {
    "path": "libraries/protocol/restriction.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/restriction.hpp>\n\nnamespace graphene { namespace protocol {\n\nstruct adder {\n   size_t sum = 0;\n   void operator()(const restriction& r) { sum += r.restriction_count(); }\n   void operator()(const vector<restriction>& r) { sum += std::for_each(r.begin(), r.end(), adder()).sum; }\n};\n\nsize_t restriction::restriction_count(const vector<restriction>& restrictions) {\n   return std::for_each(restrictions.begin(), restrictions.end(), adder()).sum;\n}\n\nsize_t restriction::restriction_count() const {\n   if (argument.is_type<vector<restriction>>()) {\n      const vector<restriction>& rs = argument.get<vector<restriction>>();\n      return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum;\n   } else if (argument.is_type<vector<vector<restriction>>>()) {\n      const vector<vector<restriction>>& rs = argument.get<vector<vector<restriction>>>();\n      return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum;\n   } else if (argument.is_type<variant_assert_argument_type>()) {\n      const variant_assert_argument_type& arg = argument.get<variant_assert_argument_type>();\n      return 1 + std::for_each(arg.second.begin(), arg.second.end(), adder()).sum;\n   }\n   return 1;\n}\n\n} } // namespace graphene::protocol\n"
  },
  {
    "path": "libraries/protocol/samet_fund.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/samet_fund.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid samet_fund_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( balance > 0, \"Balance should be positive\" );\n}\n\nvoid samet_fund_delete_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n}\n\nvoid samet_fund_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( delta_amount.valid() || new_fee_rate.valid(), \"Should change something\" );\n   if( delta_amount.valid() )\n      FC_ASSERT( delta_amount->amount != 0, \"Delta amount should not be zero\" );\n}\n\nvoid samet_fund_borrow_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( borrow_amount.amount > 0, \"Amount to borrow should be positive\" );\n}\n\nvoid samet_fund_repay_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( repay_amount.amount > 0, \"Amount to repay should be positive\" );\n   FC_ASSERT( fund_fee.amount >= 0, \"Fund fee should not be negative\" );\n   FC_ASSERT( repay_amount.asset_id == fund_fee.asset_id,\n             \"Asset type of repay amount and fund fee should be the same\" );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation::fee_params_t )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation )\n"
  },
  {
    "path": "libraries/protocol/small_ops.cpp",
    "content": "/*\n * Copyright (c) 2019 BitShares Blockchain Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/balance.hpp>\n#include <graphene/protocol/buyback.hpp>\n#include <graphene/protocol/exceptions.hpp>\n#include <graphene/protocol/fba.hpp>\n#include <graphene/protocol/vesting.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nFC_IMPLEMENT_EXCEPTION( protocol_exception, 4000000, \"protocol exception\" )\n\nFC_IMPLEMENT_DERIVED_EXCEPTION( transaction_exception,      protocol_exception, 4010000,\n                                \"transaction validation exception\" )\n\nFC_IMPLEMENT_DERIVED_EXCEPTION( tx_missing_active_auth,     transaction_exception, 4010001,\n                                \"missing required active authority\" )\nFC_IMPLEMENT_DERIVED_EXCEPTION( tx_missing_owner_auth,      transaction_exception, 4010002,\n                                \"missing required owner authority\" )\nFC_IMPLEMENT_DERIVED_EXCEPTION( tx_missing_other_auth,      transaction_exception, 4010003,\n                                \"missing required other authority\" )\nFC_IMPLEMENT_DERIVED_EXCEPTION( tx_irrelevant_sig,          transaction_exception, 4010004,\n                                \"irrelevant signature included\" )\nFC_IMPLEMENT_DERIVED_EXCEPTION( tx_duplicate_sig,           transaction_exception, 4010005,\n                                \"duplicate signature included\" )\nFC_IMPLEMENT_DERIVED_EXCEPTION( invalid_committee_approval, transaction_exception, 4010006,\n                                \"committee account cannot directly approve transaction\" )\nFC_IMPLEMENT_DERIVED_EXCEPTION( insufficient_fee,           transaction_exception, 4010007, \"insufficient fee\" )\n\n} } // graphene::protocol\n\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::balance_claim_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::buyback_account_options )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::fba_distribute_operation )\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation )\n"
  },
  {
    "path": "libraries/protocol/special_authority.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/special_authority.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nstruct special_authority_validate_visitor\n{\n   typedef void result_type;\n\n   void operator()( const no_special_authority& a ) {}\n\n   void operator()( const top_holders_special_authority& a )\n   {\n      FC_ASSERT( a.num_top_holders > 0 );\n   }\n};\n\nvoid validate_special_authority( const special_authority& a )\n{\n   special_authority_validate_visitor vtor;\n   a.visit( vtor );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::top_holders_special_authority )\n"
  },
  {
    "path": "libraries/protocol/ticket.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/ticket.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid ticket_create_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( target_type != static_cast<uint64_t>(liquid), \"Target type can not be liquid\" );\n   FC_ASSERT( target_type < static_cast<uint64_t>(TICKET_TYPE_COUNT), \"Invalid target type\" );\n   FC_ASSERT( amount.amount > 0, \"A positive amount is needed for creating a ticket\" );\n   FC_ASSERT( amount.asset_id == asset_id_type(), \"Amount must be in BTS so far\" );\n}\n\nvoid ticket_update_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0, \"Fee should not be negative\" );\n   FC_ASSERT( target_type < static_cast<uint64_t>(TICKET_TYPE_COUNT), \"Invalid target type\" );\n   if( amount_for_new_target.valid() )\n   {\n      FC_ASSERT( amount_for_new_target->amount > 0, \"A positive amount is needed\" );\n      FC_ASSERT( amount_for_new_target->asset_id == asset_id_type(), \"Amount must be in BTS so far\" );\n   }\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation )\n"
  },
  {
    "path": "libraries/protocol/transaction.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/transaction.hpp>\n#include <graphene/protocol/block.hpp>\n#include <graphene/protocol/exceptions.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <graphene/protocol/pts_address.hpp>\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\ndigest_type processed_transaction::merkle_digest()const\n{\n   digest_type::encoder enc;\n   fc::raw::pack( enc, *this );\n   return enc.result();\n}\n\ndigest_type transaction::digest()const\n{\n   digest_type::encoder enc;\n   fc::raw::pack( enc, *this );\n   return enc.result();\n}\n\ndigest_type transaction::sig_digest( const chain_id_type& chain_id )const\n{\n   digest_type::encoder enc;\n   fc::raw::pack( enc, chain_id );\n   fc::raw::pack( enc, *this );\n   return enc.result();\n}\n\nvoid transaction::validate() const\n{\n   FC_ASSERT( operations.size() > 0, \"A transaction must have at least one operation\", (\"trx\",*this) );\n   for( const auto& op : operations )\n      operation_validate(op);\n}\n\nuint64_t transaction::get_packed_size() const\n{\n   return fc::raw::pack_size(*this);\n}\n\nconst transaction_id_type& transaction::id() const\n{\n   auto h = digest();\n   memcpy(_tx_id_buffer._hash, h._hash, std::min(sizeof(_tx_id_buffer), sizeof(h)));\n   return _tx_id_buffer;\n}\n\nconst signature_type& graphene::protocol::signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id)\n{\n   digest_type h = sig_digest( chain_id );\n   signatures.push_back(key.sign_compact(h));\n   return signatures.back();\n}\n\nsignature_type graphene::protocol::signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id)const\n{\n   digest_type::encoder enc;\n   fc::raw::pack( enc, chain_id );\n   fc::raw::pack( enc, *this );\n   return key.sign_compact(enc.result());\n}\n\nvoid transaction::set_expiration( fc::time_point_sec expiration_time )\n{\n    expiration = expiration_time;\n}\n\nvoid transaction::set_reference_block( const block_id_type& reference_block )\n{\n   ref_block_num = boost::endian::endian_reverse(reference_block._hash[0].value());\n   ref_block_prefix = reference_block._hash[1].value();\n}\n\nvoid transaction::get_required_authorities( flat_set<account_id_type>& active,\n                                            flat_set<account_id_type>& owner,\n                                            vector<authority>& other,\n                                            bool ignore_custom_operation_required_auths )const\n{\n   for( const auto& op : operations )\n      operation_get_required_authorities( op, active, owner, other, ignore_custom_operation_required_auths );\n   for( const auto& account : owner )\n      active.erase( account );\n}\n\n\nconst flat_set<public_key_type> empty_keyset;\n\nstruct sign_state\n{\n      /** returns true if we have a signature for this key or can\n       * produce a signature for this key, else returns false.\n       */\n      bool signed_by( const public_key_type& k )\n      {\n         auto itr = provided_signatures.find(k);\n         if( itr == provided_signatures.end() )\n         {\n            auto pk = available_keys.find(k);\n            if( pk  != available_keys.end() )\n               return provided_signatures[k] = true;\n            return false;\n         }\n         return itr->second = true;\n      }\n\n      optional<map<address,public_key_type>> available_address_sigs;\n      optional<map<address,public_key_type>> provided_address_sigs;\n\n      bool signed_by( const address& a ) {\n         if( !available_address_sigs ) {\n            available_address_sigs = std::map<address,public_key_type>();\n            provided_address_sigs = std::map<address,public_key_type>();\n            for( auto& item : available_keys ) {\n             (*available_address_sigs)[ address(pts_address(item, false) ) ] = item; // verison = 56 (default)\n             (*available_address_sigs)[ address(pts_address(item, true) ) ] = item; // verison = 56 (default)\n             (*available_address_sigs)[ address(pts_address(item, false, 0) ) ] = item;\n             (*available_address_sigs)[ address(pts_address(item, true, 0) ) ] = item;\n             (*available_address_sigs)[ address(item) ] = item;\n            }\n            for( auto& item : provided_signatures ) {\n             (*provided_address_sigs)[ address(pts_address(item.first, false) ) ] = item.first; //verison 56 (default)\n             (*provided_address_sigs)[ address(pts_address(item.first, true) ) ] = item.first; // verison 56 (default)\n             (*provided_address_sigs)[ address(pts_address(item.first, false, 0) ) ] = item.first;\n             (*provided_address_sigs)[ address(pts_address(item.first, true, 0) ) ] = item.first;\n             (*provided_address_sigs)[ address(item.first) ] = item.first;\n            }\n         }\n         auto itr = provided_address_sigs->find(a);\n         if( itr == provided_address_sigs->end() )\n         {\n            auto aitr = available_address_sigs->find(a);\n            if( aitr != available_address_sigs->end() ) {\n               auto pk = available_keys.find(aitr->second);\n               if( pk != available_keys.end() )\n                  return provided_signatures[aitr->second] = true;\n               return false;\n            }\n         }\n         return provided_signatures[itr->second] = true;\n      }\n\n      bool check_authority( account_id_type id )\n      {\n         if( approved_by.find(id) != approved_by.end() ) return true;\n         return check_authority( get_active(id) ) || ( allow_non_immediate_owner && check_authority( get_owner(id) ) );\n      }\n\n      /**\n       *  Checks to see if we have signatures of the active authorites of\n       *  the accounts specified in authority or the keys specified.\n       */\n      bool check_authority( const authority* au, uint32_t depth = 0 )\n      {\n         if( au == nullptr ) return false;\n         const authority& auth = *au;\n\n         uint32_t total_weight = 0;\n         for( const auto& k : auth.key_auths )\n            if( signed_by( k.first ) )\n            {\n               total_weight += k.second;\n               if( total_weight >= auth.weight_threshold )\n                  return true;\n            }\n\n         for( const auto& k : auth.address_auths )\n            if( signed_by( k.first ) )\n            {\n               total_weight += k.second;\n               if( total_weight >= auth.weight_threshold )\n                  return true;\n            }\n\n         for( const auto& a : auth.account_auths )\n         {\n            if( approved_by.find(a.first) == approved_by.end() )\n            {\n               if( depth == max_recursion )\n                  continue;\n               if( check_authority( get_active( a.first ), depth+1 )\n                     || ( allow_non_immediate_owner && check_authority( get_owner( a.first ), depth+1 ) ) )\n               {\n                  approved_by.insert( a.first );\n                  total_weight += a.second;\n                  if( total_weight >= auth.weight_threshold )\n                     return true;\n               }\n            }\n            else\n            {\n               total_weight += a.second;\n               if( total_weight >= auth.weight_threshold )\n                  return true;\n            }\n         }\n         return total_weight >= auth.weight_threshold;\n      }\n\n      bool remove_unused_signatures()\n      {\n         vector<public_key_type> remove_sigs;\n         for( const auto& sig : provided_signatures )\n            if( !sig.second ) remove_sigs.push_back( sig.first );\n\n         for( auto& sig : remove_sigs )\n            provided_signatures.erase(sig);\n\n         return remove_sigs.size() != 0;\n      }\n\n      sign_state( const flat_set<public_key_type>& sigs,\n                  const std::function<const authority*(account_id_type)>& active,\n                  const std::function<const authority*(account_id_type)>& owner,\n                  bool allow_owner,\n                  uint32_t max_recursion_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH,\n                  const flat_set<public_key_type>& keys = empty_keyset )\n      :  get_active(active),\n         get_owner(owner),\n         allow_non_immediate_owner(allow_owner),\n         max_recursion(max_recursion_depth),\n         available_keys(keys)\n      {\n         for( const auto& key : sigs )\n            provided_signatures[ key ] = false;\n         approved_by.insert( GRAPHENE_TEMP_ACCOUNT  );\n      }\n\n      const std::function<const authority*(account_id_type)>& get_active;\n      const std::function<const authority*(account_id_type)>& get_owner;\n\n      const bool                       allow_non_immediate_owner;\n      const uint32_t                   max_recursion;\n      const flat_set<public_key_type>& available_keys;\n\n      flat_map<public_key_type,bool>   provided_signatures;\n      flat_set<account_id_type>        approved_by;\n};\n\n\nvoid verify_authority( const vector<operation>& ops, const flat_set<public_key_type>& sigs,\n                       const std::function<const authority*(account_id_type)>& get_active,\n                       const std::function<const authority*(account_id_type)>& get_owner,\n                       const custom_authority_lookup& get_custom,\n                       bool allow_non_immediate_owner,\n                       bool ignore_custom_operation_required_auths,\n                       uint32_t max_recursion_depth,\n                       bool  allow_committee,\n                       const flat_set<account_id_type>& active_aprovals,\n                       const flat_set<account_id_type>& owner_approvals )\n{\n   rejected_predicate_map rejected_custom_auths;\n   try {\n   flat_set<account_id_type> required_active;\n   flat_set<account_id_type> required_owner;\n   vector<authority> other;\n\n   sign_state s( sigs, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth );\n   for( auto& id : active_aprovals )\n      s.approved_by.insert( id );\n   for( auto& id : owner_approvals )\n      s.approved_by.insert( id );\n\n   auto approved_by_custom_authority = [&s, &rejected_custom_auths, get_custom = std::move(get_custom)](\n           account_id_type account,\n           operation op ) mutable {\n      auto viable_custom_auths = get_custom( account, op, &rejected_custom_auths );\n      for( const auto& auth : viable_custom_auths )\n         if( s.check_authority( &auth ) ) return true;\n      return false;\n   };\n\n   for( const auto& op : ops ) {\n      flat_set<account_id_type> operation_required_active;\n      operation_get_required_authorities( op, operation_required_active, required_owner, other,\n                                          ignore_custom_operation_required_auths );\n\n      auto itr = operation_required_active.begin();\n      while ( itr != operation_required_active.end() ) {\n         if ( approved_by_custom_authority( *itr, op ) )\n            itr = operation_required_active.erase( itr );\n         else\n            ++itr;\n      }\n\n      required_active.insert( operation_required_active.begin(), operation_required_active.end() );\n   }\n\n   if( !allow_committee )\n      GRAPHENE_ASSERT( required_active.find(GRAPHENE_COMMITTEE_ACCOUNT) == required_active.end(),\n                       invalid_committee_approval, \"Committee account may only propose transactions\" );\n\n   for( const auto& auth : other )\n   {\n      GRAPHENE_ASSERT( s.check_authority(&auth), tx_missing_other_auth, \"Missing Authority\", (\"auth\",auth)(\"sigs\",sigs) );\n   }\n\n   // fetch all of the top level authorities\n   for( auto id : required_owner )\n   {\n      GRAPHENE_ASSERT( owner_approvals.find(id) != owner_approvals.end() ||\n                       s.check_authority(get_owner(id)),\n                       tx_missing_owner_auth, \"Missing Owner Authority ${id}\", (\"id\",id)(\"auth\",*get_owner(id)) );\n   }\n\n   for( auto id : required_active )\n   {\n      GRAPHENE_ASSERT( s.check_authority(id) ||\n                       s.check_authority(get_owner(id)),\n                       tx_missing_active_auth, \"Missing Active Authority ${id}\",\n                       (\"id\",id)(\"auth\",*get_active(id))(\"owner\",*get_owner(id)) );\n   }\n\n   GRAPHENE_ASSERT(\n      !s.remove_unused_signatures(),\n      tx_irrelevant_sig,\n      \"Unnecessary signature(s) detected\"\n      );\n} FC_CAPTURE_AND_RETHROW( (rejected_custom_auths)(ops)(sigs) ) }\n\n\nconst flat_set<public_key_type>& signed_transaction::get_signature_keys( const chain_id_type& chain_id )const\n{ try {\n   auto d = sig_digest( chain_id );\n   flat_set<public_key_type> result;\n   for( const auto&  sig : signatures )\n   {\n      GRAPHENE_ASSERT(\n         result.insert( fc::ecc::public_key(sig,d) ).second,\n            tx_duplicate_sig,\n            \"Duplicate Signature detected\" );\n   }\n   _signees = std::move( result );\n   return _signees;\n} FC_CAPTURE_AND_RETHROW() }\n\n\nset<public_key_type> signed_transaction::get_required_signatures( const chain_id_type& chain_id,\n                                                                  const flat_set<public_key_type>& available_keys,\n                                                                  const std::function<const authority*(account_id_type)>& get_active,\n                                                                  const std::function<const authority*(account_id_type)>& get_owner,\n                                                                  bool allow_non_immediate_owner,\n                                                                  bool ignore_custom_operation_required_authorities,\n                                                                  uint32_t max_recursion_depth )const\n{\n   flat_set<account_id_type> required_active;\n   flat_set<account_id_type> required_owner;\n   vector<authority> other;\n   get_required_authorities( required_active, required_owner, other, ignore_custom_operation_required_authorities );\n\n   const flat_set<public_key_type>& signature_keys = get_signature_keys(chain_id);\n   sign_state s( signature_keys, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth, available_keys );\n\n   for( const auto& auth : other )\n      s.check_authority( &auth );\n   for( auto& owner : required_owner )\n      s.check_authority( get_owner( owner ) );\n   for( auto& active : required_active )\n      s.check_authority( active ) || s.check_authority( get_owner( active ) );\n\n   s.remove_unused_signatures();\n\n   set<public_key_type> result;\n\n   for( auto& provided_sig : s.provided_signatures )\n      if( available_keys.find( provided_sig.first ) != available_keys.end()\n            && signature_keys.find( provided_sig.first ) == signature_keys.end() )\n         result.insert( provided_sig.first );\n\n   return result;\n}\n\nset<public_key_type> signed_transaction::minimize_required_signatures(\n         const chain_id_type& chain_id,\n         const flat_set<public_key_type>& available_keys,\n         const std::function<const authority*(account_id_type)>& get_active,\n         const std::function<const authority*(account_id_type)>& get_owner,\n         const custom_authority_lookup &get_custom,\n         bool allow_non_immediate_owner,\n         bool ignore_custom_operation_required_auths,\n         uint32_t max_recursion )const\n{\n   set< public_key_type > s = get_required_signatures( chain_id, available_keys, get_active, get_owner,\n                                                       allow_non_immediate_owner,\n                                                       ignore_custom_operation_required_auths, max_recursion );\n   flat_set< public_key_type > result( s.begin(), s.end() );\n\n   for( const public_key_type& k : s )\n   {\n      result.erase( k );\n      try\n      {\n         graphene::protocol::verify_authority( operations, result, get_active, get_owner, get_custom,\n                                               allow_non_immediate_owner,ignore_custom_operation_required_auths,\n                                               max_recursion );\n         continue;  // element stays erased if verify_authority is ok\n      }\n      catch( const tx_missing_owner_auth& e ) {}\n      catch( const tx_missing_active_auth& e ) {}\n      catch( const tx_missing_other_auth& e ) {}\n      result.insert( k );\n   }\n   return set<public_key_type>( result.begin(), result.end() );\n}\n\nconst transaction_id_type& precomputable_transaction::id()const\n{\n   if( 0 == _tx_id_buffer._hash[0].value() )\n      transaction::id();\n   return _tx_id_buffer;\n}\n\nvoid precomputable_transaction::validate() const\n{\n   if( _validated ) return;\n   transaction::validate();\n   _validated = true;\n}\n\nuint64_t precomputable_transaction::get_packed_size()const\n{\n   if( _packed_size == 0 )\n      _packed_size = transaction::get_packed_size();\n   return _packed_size;\n}\n\nconst flat_set<public_key_type>& precomputable_transaction::get_signature_keys( const chain_id_type& chain_id )const\n{\n   // Strictly we should check whether the given chain ID is same as the one used to initialize the `signees` field.\n   // However, we don't pass in another chain ID so far, for better performance, we skip the check.\n   if( _signees.empty() )\n      signed_transaction::get_signature_keys( chain_id );\n   return _signees;\n}\n\nvoid signed_transaction::verify_authority( const chain_id_type& chain_id,\n                                           const std::function<const authority*(account_id_type)>& get_active,\n                                           const std::function<const authority*(account_id_type)>& get_owner,\n                                           const custom_authority_lookup& get_custom,\n                                           bool allow_non_immediate_owner,\n                                           bool ignore_custom_operation_required_auths,\n                                           uint32_t max_recursion )const\n{ try {\n   graphene::protocol::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner,\n                                         get_custom, allow_non_immediate_owner,\n                                         ignore_custom_operation_required_auths, max_recursion );\n} FC_CAPTURE_AND_RETHROW( (*this) ) }\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transaction)\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::signed_transaction)\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::precomputable_transaction)\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::processed_transaction)\n"
  },
  {
    "path": "libraries/protocol/transfer.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/transfer.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nshare_type transfer_operation::calculate_fee( const fee_params_t& schedule )const\n{\n   share_type core_fee_required = schedule.fee;\n   if( memo )\n      core_fee_required += calculate_data_fee( fc::raw::pack_size(memo), schedule.price_per_kbyte );\n   return core_fee_required;\n}\n\n\nvoid transfer_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( from != to );\n   FC_ASSERT( amount.amount > 0 );\n}\n\n\n\nshare_type override_transfer_operation::calculate_fee( const fee_params_t& schedule )const\n{\n   share_type core_fee_required = schedule.fee;\n   if( memo )\n      core_fee_required += calculate_data_fee( fc::raw::pack_size(memo), schedule.price_per_kbyte );\n   return core_fee_required;\n}\n\n\nvoid override_transfer_operation::validate()const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( from != to );\n   FC_ASSERT( amount.amount > 0 );\n   FC_ASSERT( issuer != from );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation )\n"
  },
  {
    "path": "libraries/protocol/types.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/types.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <fc/crypto/base58.hpp>\n#include <fc/crypto/ripemd160.hpp>\n#include <fc/exception/exception.hpp>\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\n    public_key_type::public_key_type():key_data(){};\n\n    public_key_type::public_key_type( const fc::ecc::public_key_data& data )\n        :key_data( data ) {};\n\n    public_key_type::public_key_type( const fc::ecc::public_key& pubkey )\n        :key_data( pubkey ) {};\n\n    public_key_type::public_key_type( const std::string& base58str )\n    {\n      // TODO:  Refactor syntactic checks into static is_valid()\n      //        to make public_key_type API more similar to address API\n       std::string prefix( GRAPHENE_ADDRESS_PREFIX );\n       const size_t prefix_len = prefix.size();\n       FC_ASSERT( base58str.size() > prefix_len );\n       FC_ASSERT( base58str.substr( 0, prefix_len ) ==  prefix , \"\", (\"base58str\", base58str) );\n       auto bin = fc::from_base58( base58str.substr( prefix_len ) );\n       auto bin_key = fc::raw::unpack<binary_key>(bin);\n       key_data = bin_key.data;\n       FC_ASSERT( fc::ripemd160::hash( (char*) key_data.data(), key_data.size() )._hash[0].value() == bin_key.check );\n    };\n\n    public_key_type::operator fc::ecc::public_key_data() const\n    {\n       return key_data;\n    };\n\n    public_key_type::operator fc::ecc::public_key() const\n    {\n       return fc::ecc::public_key( key_data );\n    };\n\n    public_key_type::operator std::string() const\n    {\n       binary_key k;\n       k.data = key_data;\n       k.check = fc::ripemd160::hash( (char*) k.data.data(), k.data.size() )._hash[0].value();\n       auto data = fc::raw::pack( k );\n       return GRAPHENE_ADDRESS_PREFIX + fc::to_base58( data.data(), data.size() );\n    }\n\n    bool operator == ( const public_key_type& p1, const fc::ecc::public_key& p2)\n    {\n       return p1.key_data == p2.serialize();\n    }\n\n    bool operator == ( const public_key_type& p1, const public_key_type& p2)\n    {\n       return p1.key_data == p2.key_data;\n    }\n\n    bool operator != ( const public_key_type& p1, const public_key_type& p2)\n    {\n       return p1.key_data != p2.key_data;\n    }\n    \n    bool operator < ( const public_key_type& p1, const public_key_type& p2)\n    {\n       return address(p1) < address(p2);\n    }\n\n} } // graphene::protocol\n\nnamespace fc\n{\n    using namespace std;\n    void to_variant( const graphene::protocol::public_key_type& var,  fc::variant& vo, uint32_t max_depth )\n    {\n        vo = std::string( var );\n    }\n\n    void from_variant( const fc::variant& var,  graphene::protocol::public_key_type& vo, uint32_t max_depth )\n    {\n        vo = graphene::protocol::public_key_type( var.as_string() );\n    }\n    \n    void from_variant( const fc::variant& var, std::shared_ptr<const graphene::protocol::fee_schedule>& vo,\n                       uint32_t max_depth ) {\n        // If it's null, just make a new one\n        if (!vo) vo = std::make_shared<const graphene::protocol::fee_schedule>();\n        // Convert the non-const shared_ptr<const fee_schedule> to a non-const fee_schedule& so we can write it\n        // Don't decrement max_depth since we're not actually deserializing at this step\n        from_variant(var, const_cast<graphene::protocol::fee_schedule&>(*vo), max_depth);\n    }\n\nnamespace raw {\n   template void pack( datastream<size_t>& s, const graphene::protocol::public_key_type& tx,\n                       uint32_t _max_depth=FC_PACK_MAX_DEPTH );\n   template void pack( datastream<char*>& s, const graphene::protocol::public_key_type& tx,\n                       uint32_t _max_depth=FC_PACK_MAX_DEPTH );\n   template void unpack( datastream<const char*>& s, graphene::protocol::public_key_type& tx,\n                         uint32_t _max_depth=FC_PACK_MAX_DEPTH );\n} } // fc::raw\n"
  },
  {
    "path": "libraries/protocol/vote.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/protocol/vote.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace fc\n{\n\nvoid to_variant( const graphene::protocol::vote_id_type& var, variant& vo, uint32_t max_depth )\n{\n   vo = string(var);\n}\n\nvoid from_variant( const variant& var, graphene::protocol::vote_id_type& vo, uint32_t max_depth )\n{\n   vo = graphene::protocol::vote_id_type(var.as_string());\n}\n\n} // fc\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::vote_id_type )\n"
  },
  {
    "path": "libraries/protocol/withdraw_permission.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/withdraw_permission.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid withdraw_permission_update_operation::validate()const\n{\n   FC_ASSERT( withdrawal_limit.amount > 0 );\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( withdrawal_period_sec > 0 );\n   FC_ASSERT( withdraw_from_account != authorized_account );\n   FC_ASSERT( periods_until_expiration > 0 );\n}\n\nvoid withdraw_permission_claim_operation::validate()const\n{\n   FC_ASSERT( withdraw_to_account != withdraw_from_account );\n   FC_ASSERT( amount_to_withdraw.amount > 0 );\n   FC_ASSERT( fee.amount >= 0 );\n}\n\nshare_type withdraw_permission_claim_operation::calculate_fee(const fee_params_t& k)const\n{\n   share_type core_fee_required = k.fee;\n   if( memo )\n      core_fee_required += calculate_data_fee( fc::raw::pack_size(memo), k.price_per_kbyte );\n   return core_fee_required;\n}\n\nvoid withdraw_permission_create_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( withdraw_from_account != authorized_account );\n   FC_ASSERT( withdrawal_limit.amount > 0 );\n   //TODO: better bounds checking on these values\n   FC_ASSERT( withdrawal_period_sec > 0 );\n   FC_ASSERT( periods_until_expiration > 0 );\n}\n\nvoid withdraw_permission_delete_operation::validate() const\n{\n   FC_ASSERT( fee.amount >= 0 );\n   FC_ASSERT( withdraw_from_account != authorized_account );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation )\n"
  },
  {
    "path": "libraries/protocol/witness.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/witness.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid witness_create_operation::validate() const\n{\n   FC_ASSERT(fee.amount >= 0);\n   FC_ASSERT(url.size() < GRAPHENE_MAX_URL_LENGTH );\n}\n\nvoid witness_update_operation::validate() const\n{\n   FC_ASSERT(fee.amount >= 0);\n   if( new_url.valid() )\n       FC_ASSERT(new_url->size() < GRAPHENE_MAX_URL_LENGTH );\n}\n\n} } // graphene::protocol\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation )\n"
  },
  {
    "path": "libraries/protocol/worker.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/worker.hpp>\n\n#include <fc/io/raw.hpp>\n\nnamespace graphene { namespace protocol {\n\nvoid worker_create_operation::validate() const\n{\n   FC_ASSERT(fee.amount >= 0);\n   FC_ASSERT(work_end_date > work_begin_date);\n   FC_ASSERT(daily_pay > 0);\n   FC_ASSERT(daily_pay < GRAPHENE_MAX_SHARE_SUPPLY);\n   FC_ASSERT(name.size() < GRAPHENE_MAX_WORKER_NAME_LENGTH );\n   FC_ASSERT(url.size() < GRAPHENE_MAX_URL_LENGTH );\n}\n\n} }\n\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation::fee_params_t )\nGRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation )\n"
  },
  {
    "path": "libraries/utilities/CMakeLists.txt",
    "content": "list( APPEND CMAKE_MODULE_PATH \"${CMAKE_SOURCE_DIR}/libraries/fc/GitVersionGen\" )\ninclude( GetGitRevisionDescription )\nget_git_head_revision(GIT_REFSPEC GRAPHENE_GIT_REVISION_SHA)\nget_git_unix_timestamp(GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP)\ngit_describe(GRAPHENE_GIT_REVISION_DESCRIPTION --tags)\nif(NOT GRAPHENE_GIT_REVISION_DESCRIPTION)\n    set(GRAPHENE_GIT_REVISION_DESCRIPTION \"unknown\")\nendif(NOT GRAPHENE_GIT_REVISION_DESCRIPTION)\n\nfile(GLOB HEADERS \"include/graphene/utilities/*.hpp\")\n\nset(sources\n   key_conversion.cpp\n   string_escape.cpp\n   tempdir.cpp\n   words.cpp\n   elasticsearch.cpp\n   ${HEADERS})\n\nconfigure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/git_revision.cpp.in\" \"${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp\" @ONLY)\nlist(APPEND sources \"${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp\")\n\nfind_curl()\n\ninclude_directories(${CURL_INCLUDE_DIRS})\nadd_library( graphene_utilities\n             ${sources}\n             ${HEADERS} )\nif(CURL_STATICLIB)\n  SET_TARGET_PROPERTIES(graphene_utilities PROPERTIES\n  COMPILE_DEFINITIONS \"CURL_STATICLIB\")\nendif(CURL_STATICLIB)\ntarget_link_libraries( graphene_utilities fc ${CURL_LIBRARIES} )\ntarget_include_directories( graphene_utilities\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\nif (USE_PCH)\n  set_target_properties(graphene_utilities PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)\n  cotire(graphene_utilities)\nendif(USE_PCH)\n\ninstall( TARGETS\n   graphene_utilities\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\ninstall( FILES ${HEADERS} DESTINATION \"include/graphene/utilities\" )\n"
  },
  {
    "path": "libraries/utilities/elasticsearch.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/utilities/elasticsearch.hpp>\n\n#include <boost/algorithm/string/join.hpp>\n\n#include <fc/io/json.hpp>\n#include <fc/exception/exception.hpp>\n\nstatic size_t curl_write_function(void *contents, size_t size, size_t nmemb, void *userp)\n{\n   ((std::string*)userp)->append((char*)contents, size * nmemb);\n   return size * nmemb;\n}\n\nnamespace graphene { namespace utilities {\n\nstatic bool handle_bulk_response( uint16_t http_code, const std::string& curl_read_buffer )\n{\n   if( curl_wrapper::http_response_code::HTTP_200 == http_code )\n   {\n      // all good, but check errors in response\n      fc::variant j = fc::json::from_string(curl_read_buffer);\n      bool errors = j[\"errors\"].as_bool();\n      if( errors )\n      {\n         elog( \"ES returned 200 but with errors: ${e}\", (\"e\", curl_read_buffer) );\n         return false;\n      }\n      return true;\n   }\n\n   if( curl_wrapper::http_response_code::HTTP_413 == http_code )\n   {\n      elog( \"413 error: Request too large. Can be low disk space. ${e}\", (\"e\", curl_read_buffer) );\n   }\n   else if( curl_wrapper::http_response_code::HTTP_401 == http_code )\n   {\n      elog( \"401 error: Unauthorized. ${e}\", (\"e\", curl_read_buffer) );\n   }\n   else\n   {\n      elog( \"${code} error: ${e}\", (\"code\", std::to_string(http_code)) (\"e\", curl_read_buffer) );\n   }\n   return false;\n}\n\nstd::vector<std::string> createBulk(const fc::mutable_variant_object& bulk_header, std::string&& data)\n{\n   std::vector<std::string> bulk;\n   fc::mutable_variant_object final_bulk_header;\n   final_bulk_header[\"index\"] = bulk_header;\n   bulk.push_back(fc::json::to_string(final_bulk_header));\n   bulk.emplace_back(std::move(data));\n\n   return bulk;\n}\n\nbool curl_wrapper::http_response::is_200() const\n{\n   return ( http_response_code::HTTP_200 == code );\n}\n\nCURL* curl_wrapper::init_curl()\n{\n   CURL* curl = curl_easy_init();\n   if( curl )\n   {\n      curl_easy_setopt( curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2 );\n      return curl;\n   }\n   FC_THROW( \"Unable to init cURL\" );\n}\n\ncurl_slist* curl_wrapper::init_request_headers()\n{\n   curl_slist* request_headers = curl_slist_append( NULL, \"Content-Type: application/json\" );\n   FC_ASSERT( request_headers, \"Unable to init cURL request headers\" );\n   return request_headers;\n}\n\ncurl_wrapper::curl_wrapper()\n{\n   curl_easy_setopt( curl.get(), CURLOPT_HTTPHEADER, request_headers.get() );\n   curl_easy_setopt( curl.get(), CURLOPT_USERAGENT, \"bitshares-core/6.1\" );\n}\n\nvoid curl_wrapper::curl_deleter::operator()( CURL* p_curl ) const\n{\n   if( p_curl )\n      curl_easy_cleanup( p_curl );\n}\n\nvoid curl_wrapper::curl_slist_deleter::operator()( curl_slist* slist ) const\n{\n   if( slist )\n      curl_slist_free_all( slist );\n}\n\ncurl_wrapper::http_response curl_wrapper::request( curl_wrapper::http_request_method method,\n                                                   const std::string& url,\n                                                   const std::string& auth,\n                                                   const std::string& query ) const\n{\n   curl_wrapper::http_response resp;\n\n   // Note: the variable curl has a long lifetime, it only gets initialized once, then be used many times,\n   //       thus we need to clear old data\n\n   // Note: host and auth are always the same in the program, ideally we don't need to set them every time\n   curl_easy_setopt( curl.get(), CURLOPT_URL, url.c_str() );\n   if( !auth.empty() )\n      curl_easy_setopt( curl.get(), CURLOPT_USERPWD, auth.c_str() );\n\n   // Empty for GET, POST or HEAD, non-empty for DELETE or PUT\n   static const std::vector<std::string> http_request_method_custom_str = {\n      \"\", // GET\n      \"\", // POST\n      \"\", // HEAD\n      \"PUT\",\n      \"DELETE\",\n      \"PATCH\",\n      \"OPTIONS\"\n   };\n   const auto& custom_request = http_request_method_custom_str[static_cast<size_t>(method)];\n   const auto* p_custom_request = custom_request.empty() ? NULL : custom_request.c_str();\n   curl_easy_setopt( curl.get(), CURLOPT_CUSTOMREQUEST, p_custom_request );\n\n   if( curl_wrapper::http_request_method::HTTP_POST == method\n       || curl_wrapper::http_request_method::HTTP_PUT == method )\n   {\n      curl_easy_setopt( curl.get(), CURLOPT_HTTPGET, false );\n      curl_easy_setopt( curl.get(), CURLOPT_POST, true );\n      curl_easy_setopt( curl.get(), CURLOPT_POSTFIELDS, query.c_str() );\n   }\n   else // GET or DELETE (only these are used in this file)\n   {\n      curl_easy_setopt( curl.get(), CURLOPT_POSTFIELDS, NULL );\n      curl_easy_setopt( curl.get(), CURLOPT_POST, false );\n      curl_easy_setopt( curl.get(), CURLOPT_HTTPGET, true );\n   }\n\n   curl_easy_setopt( curl.get(), CURLOPT_WRITEFUNCTION, curl_write_function );\n   curl_easy_setopt( curl.get(), CURLOPT_WRITEDATA, (void *)(&resp.content) );\n   curl_easy_perform( curl.get() );\n\n   long code;\n   curl_easy_getinfo( curl.get(), CURLINFO_RESPONSE_CODE, &code );\n   resp.code = static_cast<uint16_t>( code );\n\n   return resp;\n}\n\ncurl_wrapper::http_response curl_wrapper::get( const std::string& url, const std::string& auth ) const\n{\n   return request( http_request_method::HTTP_GET, url, auth, \"\" );\n}\n\ncurl_wrapper::http_response curl_wrapper::del( const std::string& url, const std::string& auth ) const\n{\n   return request( http_request_method::HTTP_DELETE, url, auth, \"\" );\n}\n\ncurl_wrapper::http_response curl_wrapper::post( const std::string& url, const std::string& auth,\n                                                const std::string& query ) const\n{\n   return request( http_request_method::HTTP_POST, url, auth, query );\n}\n\ncurl_wrapper::http_response curl_wrapper::put( const std::string& url, const std::string& auth,\n                                               const std::string& query ) const\n{\n   return request( http_request_method::HTTP_PUT, url, auth, query );\n}\n\nbool es_client::check_status() const\n{\n   const auto response = curl.get( base_url + \"_nodes\", auth );\n\n   // Note: response.code is ignored here\n   return !response.content.empty();\n}\n\nstd::string es_client::get_version() const\n{ try {\n   const auto response = curl.get( base_url, auth );\n   if( !response.is_200() )\n      FC_THROW( \"Error on es_client::get_version(): code = ${code}, message = ${message} \",\n                (\"code\", response.code) (\"message\", response.content) );\n\n   fc::variant content = fc::json::from_string( response.content );\n   return content[\"version\"][\"number\"].as_string();\n} FC_CAPTURE_LOG_AND_RETHROW( (base_url) ) } // GCOVR_EXCL_LINE\n\nvoid es_client::check_version_7_or_above( bool& result ) const noexcept\n{\n   static const int64_t version_7 = 7;\n   try {\n      const auto es_version = get_version();\n      ilog( \"ES version detected: ${v}\", (\"v\", es_version) );\n      auto dot_pos = es_version.find('.');\n      result = ( std::stoi(es_version.substr(0,dot_pos)) >= version_7 );\n   }\n   catch( ... )\n   {\n      wlog( \"Unable to get ES version, assuming it is 7 or above\" );\n      result = true;\n   }\n}\n\nbool es_client::send_bulk( const std::vector<std::string>& bulk_lines ) const\n{\n   auto bulk_str = boost::algorithm::join( bulk_lines, \"\\n\" ) + \"\\n\";\n   const auto response = curl.post( base_url + \"_bulk\", auth, bulk_str );\n\n   return handle_bulk_response( response.code, response.content );\n}\n\nbool es_client::del( const std::string& path ) const\n{\n   const auto response = curl.del( base_url + path, auth );\n\n   // Note: response.code is ignored here\n   return !response.content.empty();\n}\n\nstd::string es_client::get( const std::string& path ) const\n{\n   const auto response = curl.get( base_url + path, auth );\n\n   // Note: response.code is ignored here\n   return response.content;\n}\n\nstd::string es_client::query( const std::string& path, const std::string& query ) const\n{\n   const auto response = curl.post( base_url + path, auth, query );\n\n   // Note: response.code is ignored here\n   return response.content;\n}\n\nfc::variant es_data_adaptor::adapt( const fc::variant_object& op, uint16_t max_depth )\n{\n   if( 0 == max_depth )\n   {\n      fc::variant v;\n      fc::to_variant(fc::json::to_string(op), v, FC_PACK_MAX_DEPTH);\n      return v;\n   }\n\n   fc::mutable_variant_object o(op);\n\n   // Note:\n   // These fields are maps, they are stored redundantly in ES,\n   //   one instance is a nested string array using the original field names (for backward compatibility, although\n   //     ES queries return results in JSON format a little differently than node APIs),\n   //   and a new instance is an object array with \"_object\" suffix added to the field name.\n   static const std::unordered_set<std::string> to_string_array_fields = { \"account_auths\", \"address_auths\",\n                                                                           \"key_auths\" };\n\n   // Note:\n   // These fields are stored redundantly in ES,\n   //   one instance is a string using the original field names (originally for backward compatibility,\n   //     but new fields are added here as well),\n   //   and a new instance is a nested object or nested object array with \"_object\" suffix added to the field name.\n   //\n   // Why do we add new fields here?\n   // Because we want to keep the JSON format made by node (stored in ES as a string), and store the object format\n   //   at the same time for more flexible query.\n   //\n   // Object arrays not listed in this map (if any) are stored as nested objects only.\n   static const std::unordered_map<std::string, data_type> to_string_fields = {\n      { \"parameters\",               data_type::array_type }, // in committee proposals, current_fees.parameters\n      { \"op\",                       data_type::static_variant_type }, // proposal_create_op.proposed_ops[*].op\n      { \"proposed_ops\",             data_type::array_type }, // proposal_create_op.proposed_ops\n      { \"operations\",               data_type::array_type }, // proposal_object.operations\n      { \"initializer\",              data_type::static_variant_type }, // for workers\n      { \"policy\",                   data_type::static_variant_type }, // for vesting balances\n      { \"predicates\",               data_type::array_type }, // for assert_operation\n      { \"active_special_authority\", data_type::static_variant_type }, // for accounts\n      { \"owner_special_authority\",  data_type::static_variant_type }, // for accounts\n      { \"htlc_preimage_hash\",       data_type::static_variant_type }, // for HTLCs\n      { \"argument\",                 data_type::static_variant_type }, // for custom authority, restriction.argument\n      { \"feeds\",                    data_type::map_type }, // asset_bitasset_data_object.feeds\n      { \"acceptable_collateral\",    data_type::map_type }, // for credit offers\n      { \"acceptable_borrowers\",     data_type::map_type }, // for credit offers\n      { \"on_fill\",                  data_type::array_type } // for limit orders\n   };\n   std::vector<std::pair<std::string, fc::variants>> original_arrays;\n   std::vector<std::string> keys_to_rename;\n   for( auto& i : o )\n   {\n      const std::string& name = i.key();\n      auto& element = i.value();\n      if( element.is_object() )\n      {\n         const auto& vo = element.get_object();\n         if( vo.contains(name.c_str()) ) // transfer_operation.amount.amount\n            keys_to_rename.emplace_back(name);\n         element = adapt( vo, max_depth - 1 );\n         continue;\n      }\n\n      if( !element.is_array() )\n         continue;\n\n      auto& array = element.get_array();\n      if( to_string_fields.find(name) != to_string_fields.end() )\n      {\n         // make a backup (only if depth is sufficient) and convert to string\n         if( max_depth > 1 )\n            original_arrays.emplace_back( name, array );\n         element = fc::json::to_string(element);\n      }\n      else if( to_string_array_fields.find(name) != to_string_array_fields.end() )\n      {\n         // make a backup (only if depth is sufficient) and adapt the original\n         if( max_depth > 1 )\n         {\n            auto backup = array;\n            original_arrays.emplace_back( name, std::move( backup ) );\n         }\n         in_situ_adapt( array, max_depth - 1 );\n      }\n      else\n         in_situ_adapt( array, max_depth - 1 );\n   }\n\n   for( const auto& i : keys_to_rename ) // transfer_operation.amount\n   {\n      std::string new_name = i + \"_\";\n      o[new_name] = fc::variant(o[i]);\n      o.erase(i);\n   }\n\n   if( o.find(\"nonce\") != o.end() )\n   {\n      o[\"nonce\"] = o[\"nonce\"].as_string();\n   }\n\n   if( o.find(\"owner\") != o.end() && o[\"owner\"].is_string() ) // vesting_balance_*_operation.owner\n   {\n      o[\"owner_\"] = o[\"owner\"].as_string();\n      o.erase(\"owner\");\n   }\n\n   for( const auto& pair : original_arrays )\n   {\n      const auto& name = pair.first;\n      auto& value = pair.second;\n      auto type = data_type::map_type;\n      if( to_string_fields.find(name) != to_string_fields.end() )\n         type = to_string_fields.at(name);\n      o[name + \"_object\"] = adapt( value, type, max_depth - 1 );\n   }\n\n   fc::variant v;\n   fc::to_variant(o, v, FC_PACK_MAX_DEPTH);\n   return v;\n}\n\nfc::variant es_data_adaptor::adapt( const fc::variants& v, data_type type, uint16_t max_depth )\n{\n   if( data_type::static_variant_type == type )\n      return adapt_static_variant( v, max_depth );\n\n   // map_type or array_type\n   fc::variants vs;\n   vs.reserve( v.size() );\n   for( const auto& item : v )\n   {\n      if( item.is_array() )\n      {\n         if( data_type::map_type == type )\n            vs.push_back( adapt_map_item( item.get_array(), max_depth ) );\n         else // assume it is a static_variant array\n            vs.push_back( adapt_static_variant( item.get_array(), max_depth ) );\n      }\n      else if( item.is_object() ) // object array\n         vs.push_back( adapt( item.get_object(), max_depth ) );\n      else\n         wlog( \"Type of item is unexpected: ${item}\", (\"item\", item) );\n   }\n\n   fc::variant nv;\n   fc::to_variant(vs, nv, FC_PACK_MAX_DEPTH);\n   return nv;\n}\n\nvoid es_data_adaptor::extract_data_from_variant(\n      const fc::variant& v, fc::mutable_variant_object& mv, const std::string& prefix, uint16_t max_depth )\n{\n   FC_ASSERT( max_depth > 0, \"Internal error\" );\n   if( v.is_object() )\n      mv[prefix + \"_object\"] = adapt( v.get_object(), max_depth - 1 );\n   else if( v.is_int64() || v.is_uint64() )\n      mv[prefix + \"_int\"] = v;\n   else if( v.is_bool() )\n      mv[prefix + \"_bool\"] = v;\n   else if( v.is_string() )\n      mv[prefix + \"_string\"] = v.get_string();\n   else\n      mv[prefix + \"_string\"] = fc::json::to_string( v );\n   // Note: we don't use double here, and we convert nulls and blobs to strings,\n   //       arrays and pairs (i.e. in custom authorities) are converted to strings,\n   //       static_variants and maps (if any) are converted to strings too.\n}\n\nfc::variant es_data_adaptor::adapt_map_item( const fc::variants& v, uint16_t max_depth )\n{\n   if( 0 == max_depth )\n   {\n      fc::variant nv;\n      fc::to_variant(fc::json::to_string(v), nv, FC_PACK_MAX_DEPTH);\n      return nv;\n   }\n\n   FC_ASSERT( v.size() == 2, \"Internal error\" );\n   fc::mutable_variant_object mv;\n\n   extract_data_from_variant( v[0], mv, \"key\", max_depth );\n   extract_data_from_variant( v[1], mv, \"data\", max_depth );\n\n   fc::variant nv;\n   fc::to_variant( mv, nv, FC_PACK_MAX_DEPTH );\n   return nv;\n}\n\nfc::variant es_data_adaptor::adapt_static_variant( const fc::variants& v, uint16_t max_depth )\n{\n   if( 0 == max_depth )\n   {\n      fc::variant nv;\n      fc::to_variant(fc::json::to_string(v), nv, FC_PACK_MAX_DEPTH);\n      return nv;\n   }\n\n   FC_ASSERT( v.size() == 2, \"Internal error\" );\n   fc::mutable_variant_object mv;\n\n   mv[\"which\"] = v[0];\n   extract_data_from_variant( v[1], mv, \"data\", max_depth );\n\n   fc::variant nv;\n   fc::to_variant( mv, nv, FC_PACK_MAX_DEPTH );\n   return nv;\n}\n\nvoid es_data_adaptor::in_situ_adapt( fc::variants& v, uint16_t max_depth )\n{\n   for( auto& array_element : v )\n   {\n      if( array_element.is_object() )\n         array_element = adapt( array_element.get_object(), max_depth );\n      else if( array_element.is_array() )\n         in_situ_adapt( array_element.get_array(), max_depth );\n      else\n         array_element = array_element.as_string();\n   }\n}\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/git_revision.cpp.in",
    "content": "#include <stdint.h>\n#include <graphene/utilities/git_revision.hpp>\n\n#define GRAPHENE_GIT_REVISION_SHA \"@GRAPHENE_GIT_REVISION_SHA@\"\n#define GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP @GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP@\n#define GRAPHENE_GIT_REVISION_DESCRIPTION \"@GRAPHENE_GIT_REVISION_DESCRIPTION@\"\n\nnamespace graphene { namespace utilities {\n\nconst char* const git_revision_sha = GRAPHENE_GIT_REVISION_SHA;\nconst uint32_t git_revision_unix_timestamp = GRAPHENE_GIT_REVISION_UNIX_TIMESTAMP;\nconst char* const git_revision_description = GRAPHENE_GIT_REVISION_DESCRIPTION;\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/boost_program_options.hpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <boost/program_options.hpp>\n\nnamespace graphene { namespace utilities {\n\ntemplate<typename T>\nvoid get_program_option( const boost::program_options::variables_map& from, const std::string& key, T& to )\n{\n   if( from.count( key ) > 0 )\n   {\n      to = from[key].as<T>();\n   }\n}\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/elasticsearch.hpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <cstddef>\n#include <string>\n#include <vector>\n\n#include <curl/curl.h>\n\n#include <fc/variant_object.hpp>\n\nnamespace graphene { namespace utilities {\n\nclass curl_wrapper\n{\npublic:\n   curl_wrapper();\n\n   // Note: the numbers are used in the request() function. If we need to update or add, please check the function\n   enum class http_request_method\n   {\n      HTTP_GET     = 0,\n      HTTP_POST    = 1,\n      HTTP_HEAD    = 2,\n      HTTP_PUT     = 3,\n      HTTP_DELETE  = 4,\n      HTTP_PATCH   = 5,\n      HTTP_OPTIONS = 6\n   };\n\n   struct http_response_code\n   {\n      static constexpr uint16_t HTTP_200 = 200;\n      static constexpr uint16_t HTTP_401 = 401;\n      static constexpr uint16_t HTTP_413 = 413;\n   };\n\n   struct http_response\n   {\n      uint16_t    code;\n      std::string content;\n      bool is_200() const; ///< @return if @ref code is 200\n   };\n\n   http_response request( http_request_method method,\n                          const std::string& url,\n                          const std::string& auth,\n                          const std::string& query ) const;\n\n   http_response get( const std::string& url, const std::string& auth ) const;\n   http_response del( const std::string& url, const std::string& auth ) const;\n   http_response post( const std::string& url, const std::string& auth, const std::string& query ) const;\n   http_response put( const std::string& url, const std::string& auth, const std::string& query ) const;\n\nprivate:\n\n   static CURL* init_curl();\n   static curl_slist* init_request_headers();\n\n   struct curl_deleter\n   {\n      void operator()( CURL* p_curl ) const;\n   };\n\n   struct curl_slist_deleter\n   {\n      void operator()( curl_slist* slist ) const;\n   };\n\n   std::unique_ptr<CURL, curl_deleter> curl { init_curl() };\n   std::unique_ptr<curl_slist, curl_slist_deleter> request_headers { init_request_headers() };\n};\n\nclass es_client\n{\npublic:\n   es_client( const std::string& p_base_url, const std::string& p_auth ) : base_url(p_base_url), auth(p_auth) {}\n\n   bool check_status() const;\n   std::string get_version() const;\n   void check_version_7_or_above( bool& result ) const noexcept;\n\n   bool send_bulk( const std::vector<std::string>& bulk_lines ) const;\n   bool del( const std::string& path ) const;\n   std::string get( const std::string& path ) const;\n   std::string query( const std::string& path, const std::string& query ) const;\n\n   /// When doing bulk operations, call @ref send_bulk when the approximate size of pending data reaches this value.\n   static constexpr size_t request_size_threshold = 4 * 1024 * 1024; // 4MB\nprivate:\n   std::string base_url;\n   std::string auth;\n   curl_wrapper curl;\n};\n\nstd::vector<std::string> createBulk(const fc::mutable_variant_object& bulk_header, std::string&& data);\n\nstruct es_data_adaptor\n{\n   enum class data_type\n   {\n      static_variant_type,\n      map_type,\n      array_type // can be simple arrays, object arrays, static_variant arrays, or even nested arrays\n   };\n\n   static fc::variant adapt( const fc::variant_object& op, uint16_t max_depth );\n\n   static fc::variant adapt( const fc::variants& v, data_type type, uint16_t max_depth );\n\n   static fc::variant adapt_map_item( const fc::variants& v, uint16_t max_depth );\n\n   static fc::variant adapt_static_variant( const fc::variants& v, uint16_t max_depth );\n\n   /// Update directly, no return\n   static void in_situ_adapt( fc::variants& v, uint16_t max_depth );\n\n   /// Extract data from @p v into @p mv\n   static void extract_data_from_variant( const fc::variant& v,\n                                          fc::mutable_variant_object& mv,\n                                          const std::string& prefix,\n                                          uint16_t max_depth );\n\n};\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/git_revision.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n#include <stdint.h>\n\nnamespace graphene { namespace utilities {\n\nextern const char* const git_revision_sha;\nextern const uint32_t git_revision_unix_timestamp;\nextern const char* const git_revision_description;\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/key_conversion.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <string>\n#include <fc/crypto/elliptic.hpp>\n#include <fc/optional.hpp>\n\nnamespace graphene { namespace utilities {\n\nstd::string                        key_to_wif(const fc::sha256& private_secret );\nstd::string                        key_to_wif(const fc::ecc::private_key& key);\nfc::optional<fc::ecc::private_key> wif_to_key( const std::string& wif_key );\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/padding_ostream.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\nnamespace graphene { namespace utilities {\n\ntemplate<size_t BlockSize=16, char PaddingChar=' '>\nclass padding_ostream : public fc::buffered_ostream {\npublic:\n   padding_ostream( fc::ostream_ptr o, size_t bufsize = 4096 ) : buffered_ostream(o, bufsize) {}\n   virtual ~padding_ostream() {}\n\n   virtual size_t writesome( const char* buffer, size_t len ) {\n      auto out = buffered_ostream::writesome(buffer, len);\n      bytes_out += out;\n      bytes_out %= BlockSize;\n      return out;\n   }\n   virtual size_t writesome( const std::shared_ptr<const char>& buf, size_t len, size_t offset ) {\n      auto out = buffered_ostream::writesome(buf, len, offset);\n      bytes_out += out;\n      bytes_out %= BlockSize;\n      return out;\n   }\n   virtual void flush() {\n      static const char pad = PaddingChar;\n      while( bytes_out % BlockSize )\n         writesome(&pad, 1);\n      buffered_ostream::flush();\n   }\n\nprivate:\n   size_t bytes_out = 0;\n};\n\n} } //graphene::utilities\n\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/string_escape.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <string>\n\nnamespace graphene { namespace utilities {\n\n  std::string escape_string_for_c_source_code(const std::string& input);\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/tempdir.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <cstdlib>\n\n#include <fc/filesystem.hpp>\n\nnamespace graphene { namespace utilities {\n\nfc::path temp_directory_path();\n\n} } // graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/include/graphene/utilities/words.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\nnamespace graphene { namespace words {\n\ntypedef const char* const_char_ptr;\nextern const const_char_ptr word_list[];\nextern const uint32_t word_list_size;\n\n} }\n"
  },
  {
    "path": "libraries/utilities/key_conversion.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/utilities/key_conversion.hpp>\n#include <fc/crypto/base58.hpp>\n#include <fc/variant.hpp>\n\nnamespace graphene { namespace utilities {\n\nstd::string key_to_wif(const fc::sha256& secret )\n{\n  const size_t size_of_data_to_hash = sizeof(secret) + 1;\n  const size_t size_of_hash_bytes = 4;\n  char data[size_of_data_to_hash + size_of_hash_bytes];\n  data[0] = (char)0x80;\n  memcpy(&data[1], (char*)&secret, sizeof(secret));\n  fc::sha256 digest = fc::sha256::hash(data, size_of_data_to_hash);\n  digest = fc::sha256::hash(digest);\n  memcpy(data + size_of_data_to_hash, (char*)&digest, size_of_hash_bytes);\n  return fc::to_base58(data, sizeof(data));\n}\nstd::string key_to_wif(const fc::ecc::private_key& key)\n{\n  return key_to_wif( key.get_secret() );\n}\n\nfc::optional<fc::ecc::private_key> wif_to_key( const std::string& wif_key )\n{\n  std::vector<char> wif_bytes;\n  try\n  {\n    wif_bytes = fc::from_base58(wif_key);\n  }\n  catch (const fc::parse_error_exception&)\n  {\n    return fc::optional<fc::ecc::private_key>();\n  }\n  if (wif_bytes.size() < 5)\n    return fc::optional<fc::ecc::private_key>();\n  std::vector<char> key_bytes(wif_bytes.begin() + 1, wif_bytes.end() - 4);\n  fc::ecc::private_key key = fc::variant( key_bytes, 1 ).as<fc::ecc::private_key>( 1 );\n  fc::sha256 check = fc::sha256::hash(wif_bytes.data(), wif_bytes.size() - 4);\n  fc::sha256 check2 = fc::sha256::hash(check);\n    \n  if( memcmp( (char*)&check, wif_bytes.data() + wif_bytes.size() - 4, 4 ) == 0 || \n      memcmp( (char*)&check2, wif_bytes.data() + wif_bytes.size() - 4, 4 ) == 0 )\n    return key;\n\n  return fc::optional<fc::ecc::private_key>();\n}\n\n} } // end namespace graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/string_escape.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/utilities/string_escape.hpp>\n#include <sstream>\n\nnamespace graphene { namespace utilities {\n\n  std::string escape_string_for_c_source_code(const std::string& input)\n  {\n    std::ostringstream escaped_string;\n    escaped_string << \"\\\"\";\n    for (unsigned i = 0; i < input.size(); ++i)\n    {\n      switch (input[i])\n      {\n      case '\\a': \n        escaped_string << \"\\\\a\";\n        break;\n      case '\\b': \n        escaped_string << \"\\\\b\";\n        break;\n      case '\\t': \n        escaped_string << \"\\\\t\";\n        break;\n      case '\\n': \n        escaped_string << \"\\\\n\";\n        break;\n      case '\\v': \n        escaped_string << \"\\\\v\";\n        break;\n      case '\\f': \n        escaped_string << \"\\\\f\";\n        break;\n      case '\\r': \n        escaped_string << \"\\\\r\";\n        break;\n      case '\\\\': \n        escaped_string << \"\\\\\\\\\";\n        break;\n      case '\\\"': \n        escaped_string << \"\\\\\\\"\";\n        break;\n      default:\n        escaped_string << input[i];\n      }\n    }\n    escaped_string << \"\\\"\";\n    return escaped_string.str();\n  }\n\n} } // end namespace graphene::utilities\n\n"
  },
  {
    "path": "libraries/utilities/tempdir.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/utilities/tempdir.hpp>\n\n#include <cstdlib>\n\nnamespace graphene { namespace utilities {\n\nfc::path temp_directory_path()\n{\n   const char* graphene_tempdir = getenv(\"GRAPHENE_TEMPDIR\");\n   if( graphene_tempdir != nullptr )\n      return fc::path( graphene_tempdir );\n   return fc::temp_directory_path() / \"graphene-tmp\";\n}\n\n} } // graphene::utilities\n"
  },
  {
    "path": "libraries/utilities/words.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <stdint.h>\n#include <graphene/utilities/words.hpp>\n\nnamespace graphene { namespace words {\n\nconst const_char_ptr word_list[] = { \n\"a\",\n\"aa\",\n\"aal\",\n\"aalii\",\n\"aam\",\n\"aba\",\n\"abac\",\n\"abaca\",\n\"abacate\",\n\"abacay\",\n\"abacist\",\n\"aback\",\n\"abactor\",\n\"abacus\",\n\"abaff\",\n\"abaft\",\n\"abaiser\",\n\"abalone\",\n\"abandon\",\n\"abas\",\n\"abase\",\n\"abased\",\n\"abaser\",\n\"abash\",\n\"abashed\",\n\"abasia\",\n\"abasic\",\n\"abask\",\n\"abate\",\n\"abater\",\n\"abatis\",\n\"abaton\",\n\"abator\",\n\"abature\",\n\"abave\",\n\"abaxial\",\n\"abaxile\",\n\"abaze\",\n\"abb\",\n\"abbacy\",\n\"abbas\",\n\"abbasi\",\n\"abbassi\",\n\"abbess\",\n\"abbey\",\n\"abbot\",\n\"abbotcy\",\n\"abdal\",\n\"abdat\",\n\"abdest\",\n\"abdomen\",\n\"abduce\",\n\"abduct\",\n\"abeam\",\n\"abear\",\n\"abed\",\n\"abeigh\",\n\"abele\",\n\"abelite\",\n\"abet\",\n\"abettal\",\n\"abettor\",\n\"abey\",\n\"abeyant\",\n\"abfarad\",\n\"abhenry\",\n\"abhor\",\n\"abidal\",\n\"abide\",\n\"abider\",\n\"abidi\",\n\"abiding\",\n\"abietic\",\n\"abietin\",\n\"abigail\",\n\"abigeat\",\n\"abigeus\",\n\"abilao\",\n\"ability\",\n\"abilla\",\n\"abilo\",\n\"abiosis\",\n\"abiotic\",\n\"abir\",\n\"abiston\",\n\"abiuret\",\n\"abject\",\n\"abjoint\",\n\"abjudge\",\n\"abjure\",\n\"abjurer\",\n\"abkar\",\n\"abkari\",\n\"ablach\",\n\"ablare\",\n\"ablate\",\n\"ablator\",\n\"ablaut\",\n\"ablaze\",\n\"able\",\n\"ableeze\",\n\"abler\",\n\"ablest\",\n\"ablins\",\n\"abloom\",\n\"ablow\",\n\"ablude\",\n\"abluent\",\n\"ablush\",\n\"ably\",\n\"abmho\",\n\"abnet\",\n\"aboard\",\n\"abode\",\n\"abody\",\n\"abohm\",\n\"aboil\",\n\"abolish\",\n\"abolla\",\n\"aboma\",\n\"abomine\",\n\"aboon\",\n\"aborad\",\n\"aboral\",\n\"abord\",\n\"abort\",\n\"aborted\",\n\"abortin\",\n\"abortus\",\n\"abound\",\n\"about\",\n\"abouts\",\n\"above\",\n\"abox\",\n\"abrade\",\n\"abrader\",\n\"abraid\",\n\"abrasax\",\n\"abrase\",\n\"abrash\",\n\"abraum\",\n\"abraxas\",\n\"abreact\",\n\"abreast\",\n\"abret\",\n\"abrico\",\n\"abridge\",\n\"abrim\",\n\"abrin\",\n\"abroach\",\n\"abroad\",\n\"abrook\",\n\"abrupt\",\n\"abscess\",\n\"abscind\",\n\"abscise\",\n\"absciss\",\n\"abscond\",\n\"absence\",\n\"absent\",\n\"absit\",\n\"absmho\",\n\"absohm\",\n\"absolve\",\n\"absorb\",\n\"absorpt\",\n\"abstain\",\n\"absume\",\n\"absurd\",\n\"absvolt\",\n\"abthain\",\n\"abu\",\n\"abucco\",\n\"abulia\",\n\"abulic\",\n\"abuna\",\n\"abura\",\n\"aburban\",\n\"aburst\",\n\"aburton\",\n\"abuse\",\n\"abusee\",\n\"abuser\",\n\"abusion\",\n\"abusive\",\n\"abut\",\n\"abuttal\",\n\"abutter\",\n\"abuzz\",\n\"abvolt\",\n\"abwab\",\n\"aby\",\n\"abysm\",\n\"abysmal\",\n\"abyss\",\n\"abyssal\",\n\"acaciin\",\n\"acacin\",\n\"academe\",\n\"academy\",\n\"acajou\",\n\"acaleph\",\n\"acana\",\n\"acanth\",\n\"acantha\",\n\"acapnia\",\n\"acapu\",\n\"acara\",\n\"acardia\",\n\"acari\",\n\"acarian\",\n\"acarid\",\n\"acarine\",\n\"acaroid\",\n\"acarol\",\n\"acate\",\n\"acatery\",\n\"acaudal\",\n\"acca\",\n\"accede\",\n\"acceder\",\n\"accend\",\n\"accent\",\n\"accept\",\n\"accerse\",\n\"access\",\n\"accidia\",\n\"accidie\",\n\"accinge\",\n\"accite\",\n\"acclaim\",\n\"accloy\",\n\"accoast\",\n\"accoil\",\n\"accolle\",\n\"accompt\",\n\"accord\",\n\"accost\",\n\"account\",\n\"accoy\",\n\"accrete\",\n\"accrual\",\n\"accrue\",\n\"accruer\",\n\"accurse\",\n\"accusal\",\n\"accuse\",\n\"accused\",\n\"accuser\",\n\"ace\",\n\"acedia\",\n\"acedy\",\n\"acephal\",\n\"acerate\",\n\"acerb\",\n\"acerbic\",\n\"acerdol\",\n\"acerin\",\n\"acerose\",\n\"acerous\",\n\"acerra\",\n\"aceship\",\n\"acetal\",\n\"acetate\",\n\"acetic\",\n\"acetify\",\n\"acetin\",\n\"acetize\",\n\"acetoin\",\n\"acetol\",\n\"acetone\",\n\"acetose\",\n\"acetous\",\n\"acetum\",\n\"acetyl\",\n\"ach\",\n\"achage\",\n\"achar\",\n\"achate\",\n\"ache\",\n\"achene\",\n\"acher\",\n\"achete\",\n\"achieve\",\n\"achigan\",\n\"achill\",\n\"achime\",\n\"aching\",\n\"achira\",\n\"acholia\",\n\"acholic\",\n\"achor\",\n\"achree\",\n\"achroma\",\n\"achtel\",\n\"achy\",\n\"achylia\",\n\"achymia\",\n\"acicula\",\n\"acid\",\n\"acider\",\n\"acidic\",\n\"acidify\",\n\"acidite\",\n\"acidity\",\n\"acidize\",\n\"acidly\",\n\"acidoid\",\n\"acidyl\",\n\"acier\",\n\"aciform\",\n\"acinar\",\n\"acinary\",\n\"acinic\",\n\"acinose\",\n\"acinous\",\n\"acinus\",\n\"aciurgy\",\n\"acker\",\n\"ackey\",\n\"ackman\",\n\"acknow\",\n\"acle\",\n\"aclinal\",\n\"aclinic\",\n\"acloud\",\n\"aclys\",\n\"acmatic\",\n\"acme\",\n\"acmic\",\n\"acmite\",\n\"acne\",\n\"acnemia\",\n\"acnodal\",\n\"acnode\",\n\"acock\",\n\"acocotl\",\n\"acoin\",\n\"acoine\",\n\"acold\",\n\"acology\",\n\"acolous\",\n\"acolyte\",\n\"acoma\",\n\"acomia\",\n\"acomous\",\n\"acone\",\n\"aconic\",\n\"aconin\",\n\"aconine\",\n\"aconite\",\n\"acopic\",\n\"acopon\",\n\"acor\",\n\"acorea\",\n\"acoria\",\n\"acorn\",\n\"acorned\",\n\"acosmic\",\n\"acouasm\",\n\"acouchi\",\n\"acouchy\",\n\"acoupa\",\n\"acquest\",\n\"acquire\",\n\"acquist\",\n\"acquit\",\n\"acracy\",\n\"acraein\",\n\"acrasia\",\n\"acratia\",\n\"acrawl\",\n\"acraze\",\n\"acre\",\n\"acreage\",\n\"acreak\",\n\"acream\",\n\"acred\",\n\"acreman\",\n\"acrid\",\n\"acridan\",\n\"acridic\",\n\"acridly\",\n\"acridyl\",\n\"acrinyl\",\n\"acrisia\",\n\"acritan\",\n\"acrite\",\n\"acritol\",\n\"acroama\",\n\"acrobat\",\n\"acrogen\",\n\"acron\",\n\"acronyc\",\n\"acronym\",\n\"acronyx\",\n\"acrook\",\n\"acrose\",\n\"across\",\n\"acrotic\",\n\"acryl\",\n\"acrylic\",\n\"acrylyl\",\n\"act\",\n\"acta\",\n\"actable\",\n\"actify\",\n\"actin\",\n\"actinal\",\n\"actine\",\n\"acting\",\n\"actinic\",\n\"actinon\",\n\"action\",\n\"active\",\n\"activin\",\n\"actless\",\n\"acton\",\n\"actor\",\n\"actress\",\n\"actu\",\n\"actual\",\n\"actuary\",\n\"acture\",\n\"acuate\",\n\"acuity\",\n\"aculea\",\n\"aculeus\",\n\"acumen\",\n\"acushla\",\n\"acutate\",\n\"acute\",\n\"acutely\",\n\"acutish\",\n\"acyclic\",\n\"acyesis\",\n\"acyetic\",\n\"acyl\",\n\"acylate\",\n\"acyloin\",\n\"acyloxy\",\n\"acystia\",\n\"ad\",\n\"adactyl\",\n\"adad\",\n\"adage\",\n\"adagial\",\n\"adagio\",\n\"adamant\",\n\"adamas\",\n\"adamine\",\n\"adamite\",\n\"adance\",\n\"adangle\",\n\"adapid\",\n\"adapt\",\n\"adapter\",\n\"adaptor\",\n\"adarme\",\n\"adat\",\n\"adati\",\n\"adatom\",\n\"adaunt\",\n\"adaw\",\n\"adawe\",\n\"adawlut\",\n\"adawn\",\n\"adaxial\",\n\"aday\",\n\"adays\",\n\"adazzle\",\n\"adcraft\",\n\"add\",\n\"adda\",\n\"addable\",\n\"addax\",\n\"added\",\n\"addedly\",\n\"addend\",\n\"addenda\",\n\"adder\",\n\"addible\",\n\"addict\",\n\"addle\",\n\"addlins\",\n\"address\",\n\"addrest\",\n\"adduce\",\n\"adducer\",\n\"adduct\",\n\"ade\",\n\"adead\",\n\"adeem\",\n\"adeep\",\n\"adeling\",\n\"adelite\",\n\"adenase\",\n\"adenia\",\n\"adenine\",\n\"adenoid\",\n\"adenoma\",\n\"adenose\",\n\"adenyl\",\n\"adept\",\n\"adermia\",\n\"adermin\",\n\"adet\",\n\"adevism\",\n\"adfix\",\n\"adhaka\",\n\"adharma\",\n\"adhere\",\n\"adherer\",\n\"adhibit\",\n\"adiate\",\n\"adicity\",\n\"adieu\",\n\"adieux\",\n\"adinole\",\n\"adion\",\n\"adipate\",\n\"adipic\",\n\"adipoid\",\n\"adipoma\",\n\"adipose\",\n\"adipous\",\n\"adipsia\",\n\"adipsic\",\n\"adipsy\",\n\"adipyl\",\n\"adit\",\n\"adital\",\n\"aditus\",\n\"adjag\",\n\"adject\",\n\"adjiger\",\n\"adjoin\",\n\"adjoint\",\n\"adjourn\",\n\"adjudge\",\n\"adjunct\",\n\"adjure\",\n\"adjurer\",\n\"adjust\",\n\"adlay\",\n\"adless\",\n\"adlet\",\n\"adman\",\n\"admi\",\n\"admiral\",\n\"admire\",\n\"admired\",\n\"admirer\",\n\"admit\",\n\"admix\",\n\"adnate\",\n\"adnex\",\n\"adnexal\",\n\"adnexed\",\n\"adnoun\",\n\"ado\",\n\"adobe\",\n\"adonin\",\n\"adonite\",\n\"adonize\",\n\"adopt\",\n\"adopted\",\n\"adoptee\",\n\"adopter\",\n\"adoral\",\n\"adorant\",\n\"adore\",\n\"adorer\",\n\"adorn\",\n\"adorner\",\n\"adossed\",\n\"adoulie\",\n\"adown\",\n\"adoxy\",\n\"adoze\",\n\"adpao\",\n\"adpress\",\n\"adread\",\n\"adream\",\n\"adreamt\",\n\"adrenal\",\n\"adrenin\",\n\"adrift\",\n\"adrip\",\n\"adroit\",\n\"adroop\",\n\"adrop\",\n\"adrowse\",\n\"adrue\",\n\"adry\",\n\"adsbud\",\n\"adsmith\",\n\"adsorb\",\n\"adtevac\",\n\"adular\",\n\"adulate\",\n\"adult\",\n\"adulter\",\n\"adunc\",\n\"adusk\",\n\"adust\",\n\"advance\",\n\"advene\",\n\"adverb\",\n\"adverse\",\n\"advert\",\n\"advice\",\n\"advisal\",\n\"advise\",\n\"advised\",\n\"advisee\",\n\"adviser\",\n\"advisor\",\n\"advowee\",\n\"ady\",\n\"adynamy\",\n\"adyta\",\n\"adyton\",\n\"adytum\",\n\"adz\",\n\"adze\",\n\"adzer\",\n\"adzooks\",\n\"ae\",\n\"aecial\",\n\"aecium\",\n\"aedile\",\n\"aedilic\",\n\"aefald\",\n\"aefaldy\",\n\"aefauld\",\n\"aegis\",\n\"aenach\",\n\"aenean\",\n\"aeneous\",\n\"aeolid\",\n\"aeolina\",\n\"aeoline\",\n\"aeon\",\n\"aeonial\",\n\"aeonian\",\n\"aeonist\",\n\"aer\",\n\"aerage\",\n\"aerate\",\n\"aerator\",\n\"aerial\",\n\"aeric\",\n\"aerical\",\n\"aerie\",\n\"aeried\",\n\"aerify\",\n\"aero\",\n\"aerobe\",\n\"aerobic\",\n\"aerobus\",\n\"aerogel\",\n\"aerogen\",\n\"aerogun\",\n\"aeronat\",\n\"aeronef\",\n\"aerose\",\n\"aerosol\",\n\"aerugo\",\n\"aery\",\n\"aes\",\n\"aevia\",\n\"aface\",\n\"afaint\",\n\"afar\",\n\"afara\",\n\"afear\",\n\"afeard\",\n\"afeared\",\n\"afernan\",\n\"afetal\",\n\"affa\",\n\"affable\",\n\"affably\",\n\"affair\",\n\"affaite\",\n\"affect\",\n\"affeer\",\n\"affeir\",\n\"affiant\",\n\"affinal\",\n\"affine\",\n\"affined\",\n\"affirm\",\n\"affix\",\n\"affixal\",\n\"affixer\",\n\"afflict\",\n\"afflux\",\n\"afforce\",\n\"afford\",\n\"affray\",\n\"affront\",\n\"affuse\",\n\"affy\",\n\"afghani\",\n\"afield\",\n\"afire\",\n\"aflame\",\n\"aflare\",\n\"aflat\",\n\"aflaunt\",\n\"aflight\",\n\"afloat\",\n\"aflow\",\n\"aflower\",\n\"aflush\",\n\"afoam\",\n\"afoot\",\n\"afore\",\n\"afoul\",\n\"afraid\",\n\"afreet\",\n\"afresh\",\n\"afret\",\n\"afront\",\n\"afrown\",\n\"aft\",\n\"aftaba\",\n\"after\",\n\"aftergo\",\n\"aftmost\",\n\"aftosa\",\n\"aftward\",\n\"aga\",\n\"again\",\n\"against\",\n\"agal\",\n\"agalaxy\",\n\"agalite\",\n\"agallop\",\n\"agalma\",\n\"agama\",\n\"agamete\",\n\"agami\",\n\"agamian\",\n\"agamic\",\n\"agamid\",\n\"agamoid\",\n\"agamont\",\n\"agamous\",\n\"agamy\",\n\"agape\",\n\"agapeti\",\n\"agar\",\n\"agaric\",\n\"agarita\",\n\"agarwal\",\n\"agasp\",\n\"agate\",\n\"agathin\",\n\"agatine\",\n\"agatize\",\n\"agatoid\",\n\"agaty\",\n\"agavose\",\n\"agaze\",\n\"agazed\",\n\"age\",\n\"aged\",\n\"agedly\",\n\"agee\",\n\"ageless\",\n\"agelong\",\n\"agen\",\n\"agency\",\n\"agenda\",\n\"agendum\",\n\"agent\",\n\"agentry\",\n\"ager\",\n\"ageusia\",\n\"ageusic\",\n\"agger\",\n\"aggrade\",\n\"aggrate\",\n\"aggress\",\n\"aggroup\",\n\"aggry\",\n\"aggur\",\n\"agha\",\n\"aghanee\",\n\"aghast\",\n\"agile\",\n\"agilely\",\n\"agility\",\n\"aging\",\n\"agio\",\n\"agist\",\n\"agistor\",\n\"agitant\",\n\"agitate\",\n\"agla\",\n\"aglance\",\n\"aglare\",\n\"agleaf\",\n\"agleam\",\n\"aglet\",\n\"agley\",\n\"aglint\",\n\"aglow\",\n\"aglucon\",\n\"agnail\",\n\"agname\",\n\"agnamed\",\n\"agnate\",\n\"agnatic\",\n\"agnel\",\n\"agnize\",\n\"agnomen\",\n\"agnosia\",\n\"agnosis\",\n\"agnosy\",\n\"agnus\",\n\"ago\",\n\"agog\",\n\"agoge\",\n\"agogic\",\n\"agogics\",\n\"agoho\",\n\"agoing\",\n\"agon\",\n\"agonal\",\n\"agone\",\n\"agonic\",\n\"agonied\",\n\"agonist\",\n\"agonium\",\n\"agonize\",\n\"agony\",\n\"agora\",\n\"agouara\",\n\"agouta\",\n\"agouti\",\n\"agpaite\",\n\"agrah\",\n\"agral\",\n\"agre\",\n\"agree\",\n\"agreed\",\n\"agreer\",\n\"agrege\",\n\"agria\",\n\"agrin\",\n\"agrise\",\n\"agrito\",\n\"agroan\",\n\"agrom\",\n\"agroof\",\n\"agrope\",\n\"aground\",\n\"agrufe\",\n\"agruif\",\n\"agsam\",\n\"agua\",\n\"ague\",\n\"aguey\",\n\"aguish\",\n\"agunah\",\n\"agush\",\n\"agust\",\n\"agy\",\n\"agynary\",\n\"agynous\",\n\"agyrate\",\n\"agyria\",\n\"ah\",\n\"aha\",\n\"ahaaina\",\n\"ahaunch\",\n\"ahead\",\n\"aheap\",\n\"ahem\",\n\"ahey\",\n\"ahimsa\",\n\"ahind\",\n\"ahint\",\n\"ahmadi\",\n\"aho\",\n\"ahong\",\n\"ahorse\",\n\"ahoy\",\n\"ahsan\",\n\"ahu\",\n\"ahuatle\",\n\"ahull\",\n\"ahum\",\n\"ahungry\",\n\"ahunt\",\n\"ahura\",\n\"ahush\",\n\"ahwal\",\n\"ahypnia\",\n\"ai\",\n\"aid\",\n\"aidable\",\n\"aidance\",\n\"aidant\",\n\"aide\",\n\"aider\",\n\"aidful\",\n\"aidless\",\n\"aiel\",\n\"aiglet\",\n\"ail\",\n\"ailanto\",\n\"aile\",\n\"aileron\",\n\"ailette\",\n\"ailing\",\n\"aillt\",\n\"ailment\",\n\"ailsyte\",\n\"ailuro\",\n\"ailweed\",\n\"aim\",\n\"aimara\",\n\"aimer\",\n\"aimful\",\n\"aiming\",\n\"aimless\",\n\"ainaleh\",\n\"ainhum\",\n\"ainoi\",\n\"ainsell\",\n\"aint\",\n\"aion\",\n\"aionial\",\n\"air\",\n\"airable\",\n\"airampo\",\n\"airan\",\n\"aircrew\",\n\"airdock\",\n\"airdrop\",\n\"aire\",\n\"airer\",\n\"airfoil\",\n\"airhead\",\n\"airily\",\n\"airing\",\n\"airish\",\n\"airless\",\n\"airlift\",\n\"airlike\",\n\"airmail\",\n\"airman\",\n\"airmark\",\n\"airpark\",\n\"airport\",\n\"airship\",\n\"airsick\",\n\"airt\",\n\"airward\",\n\"airway\",\n\"airy\",\n\"aisle\",\n\"aisled\",\n\"aisling\",\n\"ait\",\n\"aitch\",\n\"aitesis\",\n\"aition\",\n\"aiwan\",\n\"aizle\",\n\"ajaja\",\n\"ajangle\",\n\"ajar\",\n\"ajari\",\n\"ajava\",\n\"ajhar\",\n\"ajivika\",\n\"ajog\",\n\"ajoint\",\n\"ajowan\",\n\"ak\",\n\"aka\",\n\"akala\",\n\"akaroa\",\n\"akasa\",\n\"akazga\",\n\"akcheh\",\n\"ake\",\n\"akeake\",\n\"akebi\",\n\"akee\",\n\"akeki\",\n\"akeley\",\n\"akepiro\",\n\"akerite\",\n\"akey\",\n\"akhoond\",\n\"akhrot\",\n\"akhyana\",\n\"akia\",\n\"akimbo\",\n\"akin\",\n\"akindle\",\n\"akinete\",\n\"akmudar\",\n\"aknee\",\n\"ako\",\n\"akoasm\",\n\"akoasma\",\n\"akonge\",\n\"akov\",\n\"akpek\",\n\"akra\",\n\"aku\",\n\"akule\",\n\"akund\",\n\"al\",\n\"ala\",\n\"alacha\",\n\"alack\",\n\"alada\",\n\"alaihi\",\n\"alaite\",\n\"alala\",\n\"alalite\",\n\"alalus\",\n\"alameda\",\n\"alamo\",\n\"alamoth\",\n\"alan\",\n\"aland\",\n\"alangin\",\n\"alani\",\n\"alanine\",\n\"alannah\",\n\"alantic\",\n\"alantin\",\n\"alantol\",\n\"alanyl\",\n\"alar\",\n\"alares\",\n\"alarm\",\n\"alarmed\",\n\"alarum\",\n\"alary\",\n\"alas\",\n\"alate\",\n\"alated\",\n\"alatern\",\n\"alation\",\n\"alb\",\n\"alba\",\n\"alban\",\n\"albarco\",\n\"albata\",\n\"albe\",\n\"albedo\",\n\"albee\",\n\"albeit\",\n\"albetad\",\n\"albify\",\n\"albinal\",\n\"albinic\",\n\"albino\",\n\"albite\",\n\"albitic\",\n\"albugo\",\n\"album\",\n\"albumen\",\n\"albumin\",\n\"alburn\",\n\"albus\",\n\"alcaide\",\n\"alcalde\",\n\"alcanna\",\n\"alcazar\",\n\"alchemy\",\n\"alchera\",\n\"alchimy\",\n\"alchymy\",\n\"alcine\",\n\"alclad\",\n\"alco\",\n\"alcoate\",\n\"alcogel\",\n\"alcohol\",\n\"alcosol\",\n\"alcove\",\n\"alcyon\",\n\"aldane\",\n\"aldazin\",\n\"aldehol\",\n\"alder\",\n\"aldern\",\n\"aldim\",\n\"aldime\",\n\"aldine\",\n\"aldol\",\n\"aldose\",\n\"ale\",\n\"aleak\",\n\"alec\",\n\"alecize\",\n\"alecost\",\n\"alecup\",\n\"alee\",\n\"alef\",\n\"aleft\",\n\"alegar\",\n\"alehoof\",\n\"alem\",\n\"alemana\",\n\"alembic\",\n\"alemite\",\n\"alemmal\",\n\"alen\",\n\"aleph\",\n\"alephs\",\n\"alepole\",\n\"alepot\",\n\"alerce\",\n\"alerse\",\n\"alert\",\n\"alertly\",\n\"alesan\",\n\"aletap\",\n\"alette\",\n\"alevin\",\n\"alewife\",\n\"alexia\",\n\"alexic\",\n\"alexin\",\n\"aleyard\",\n\"alf\",\n\"alfa\",\n\"alfaje\",\n\"alfalfa\",\n\"alfaqui\",\n\"alfet\",\n\"alfiona\",\n\"alfonso\",\n\"alforja\",\n\"alga\",\n\"algae\",\n\"algal\",\n\"algalia\",\n\"algate\",\n\"algebra\",\n\"algedo\",\n\"algesia\",\n\"algesic\",\n\"algesis\",\n\"algetic\",\n\"algic\",\n\"algid\",\n\"algific\",\n\"algin\",\n\"algine\",\n\"alginic\",\n\"algist\",\n\"algoid\",\n\"algor\",\n\"algosis\",\n\"algous\",\n\"algum\",\n\"alhenna\",\n\"alias\",\n\"alibi\",\n\"alible\",\n\"alichel\",\n\"alidade\",\n\"alien\",\n\"aliency\",\n\"alienee\",\n\"aliener\",\n\"alienor\",\n\"alif\",\n\"aliform\",\n\"alight\",\n\"align\",\n\"aligner\",\n\"aliipoe\",\n\"alike\",\n\"alima\",\n\"aliment\",\n\"alimony\",\n\"alin\",\n\"aliofar\",\n\"alipata\",\n\"aliped\",\n\"aliptes\",\n\"aliptic\",\n\"aliquot\",\n\"alish\",\n\"alisier\",\n\"alismad\",\n\"alismal\",\n\"aliso\",\n\"alison\",\n\"alisp\",\n\"alist\",\n\"alit\",\n\"alite\",\n\"aliunde\",\n\"alive\",\n\"aliyah\",\n\"alizari\",\n\"aljoba\",\n\"alk\",\n\"alkali\",\n\"alkalic\",\n\"alkamin\",\n\"alkane\",\n\"alkanet\",\n\"alkene\",\n\"alkenna\",\n\"alkenyl\",\n\"alkide\",\n\"alkine\",\n\"alkool\",\n\"alkoxy\",\n\"alkoxyl\",\n\"alky\",\n\"alkyd\",\n\"alkyl\",\n\"alkylic\",\n\"alkyne\",\n\"all\",\n\"allan\",\n\"allay\",\n\"allayer\",\n\"allbone\",\n\"allege\",\n\"alleger\",\n\"allegro\",\n\"allele\",\n\"allelic\",\n\"allene\",\n\"aller\",\n\"allergy\",\n\"alley\",\n\"alleyed\",\n\"allgood\",\n\"allheal\",\n\"allice\",\n\"allied\",\n\"allies\",\n\"allness\",\n\"allonym\",\n\"alloquy\",\n\"allose\",\n\"allot\",\n\"allotee\",\n\"allover\",\n\"allow\",\n\"allower\",\n\"alloxan\",\n\"alloy\",\n\"allseed\",\n\"alltud\",\n\"allude\",\n\"allure\",\n\"allurer\",\n\"alluvia\",\n\"allwork\",\n\"ally\",\n\"allyl\",\n\"allylic\",\n\"alma\",\n\"almadia\",\n\"almadie\",\n\"almagra\",\n\"almanac\",\n\"alme\",\n\"almemar\",\n\"almique\",\n\"almirah\",\n\"almoign\",\n\"almon\",\n\"almond\",\n\"almondy\",\n\"almoner\",\n\"almonry\",\n\"almost\",\n\"almous\",\n\"alms\",\n\"almsful\",\n\"almsman\",\n\"almuce\",\n\"almud\",\n\"almude\",\n\"almug\",\n\"almuten\",\n\"aln\",\n\"alnage\",\n\"alnager\",\n\"alnein\",\n\"alnico\",\n\"alnoite\",\n\"alnuin\",\n\"alo\",\n\"alochia\",\n\"alod\",\n\"alodial\",\n\"alodian\",\n\"alodium\",\n\"alody\",\n\"aloe\",\n\"aloed\",\n\"aloesol\",\n\"aloetic\",\n\"aloft\",\n\"alogia\",\n\"alogism\",\n\"alogy\",\n\"aloid\",\n\"aloin\",\n\"aloma\",\n\"alone\",\n\"along\",\n\"alongst\",\n\"aloof\",\n\"aloofly\",\n\"aloose\",\n\"alop\",\n\"alopeke\",\n\"alose\",\n\"aloud\",\n\"alow\",\n\"alowe\",\n\"alp\",\n\"alpaca\",\n\"alpeen\",\n\"alpha\",\n\"alphol\",\n\"alphorn\",\n\"alphos\",\n\"alphyl\",\n\"alpieu\",\n\"alpine\",\n\"alpist\",\n\"alquier\",\n\"alraun\",\n\"already\",\n\"alright\",\n\"alroot\",\n\"alruna\",\n\"also\",\n\"alsoon\",\n\"alt\",\n\"altaite\",\n\"altar\",\n\"altared\",\n\"alter\",\n\"alterer\",\n\"altern\",\n\"alterne\",\n\"althea\",\n\"althein\",\n\"altho\",\n\"althorn\",\n\"altilik\",\n\"altin\",\n\"alto\",\n\"altoun\",\n\"altrose\",\n\"altun\",\n\"aludel\",\n\"alula\",\n\"alular\",\n\"alulet\",\n\"alum\",\n\"alumic\",\n\"alumina\",\n\"alumine\",\n\"alumish\",\n\"alumite\",\n\"alumium\",\n\"alumna\",\n\"alumnae\",\n\"alumnal\",\n\"alumni\",\n\"alumnus\",\n\"alunite\",\n\"alupag\",\n\"alure\",\n\"aluta\",\n\"alvar\",\n\"alveary\",\n\"alveloz\",\n\"alveola\",\n\"alveole\",\n\"alveoli\",\n\"alveus\",\n\"alvine\",\n\"alvite\",\n\"alvus\",\n\"alway\",\n\"always\",\n\"aly\",\n\"alypin\",\n\"alysson\",\n\"am\",\n\"ama\",\n\"amaas\",\n\"amadou\",\n\"amaga\",\n\"amah\",\n\"amain\",\n\"amakebe\",\n\"amala\",\n\"amalaka\",\n\"amalgam\",\n\"amaltas\",\n\"amamau\",\n\"amandin\",\n\"amang\",\n\"amani\",\n\"amania\",\n\"amanori\",\n\"amanous\",\n\"amapa\",\n\"amar\",\n\"amarin\",\n\"amarine\",\n\"amarity\",\n\"amaroid\",\n\"amass\",\n\"amasser\",\n\"amastia\",\n\"amasty\",\n\"amateur\",\n\"amative\",\n\"amatol\",\n\"amatory\",\n\"amaze\",\n\"amazed\",\n\"amazia\",\n\"amazing\",\n\"amba\",\n\"ambage\",\n\"ambalam\",\n\"amban\",\n\"ambar\",\n\"ambaree\",\n\"ambary\",\n\"ambash\",\n\"ambassy\",\n\"ambatch\",\n\"ambay\",\n\"ambeer\",\n\"amber\",\n\"ambery\",\n\"ambiens\",\n\"ambient\",\n\"ambier\",\n\"ambit\",\n\"ambital\",\n\"ambitty\",\n\"ambitus\",\n\"amble\",\n\"ambler\",\n\"ambling\",\n\"ambo\",\n\"ambon\",\n\"ambos\",\n\"ambrain\",\n\"ambrein\",\n\"ambrite\",\n\"ambroid\",\n\"ambrose\",\n\"ambry\",\n\"ambsace\",\n\"ambury\",\n\"ambush\",\n\"amchoor\",\n\"ame\",\n\"ameed\",\n\"ameen\",\n\"amelia\",\n\"amellus\",\n\"amelu\",\n\"amelus\",\n\"amen\",\n\"amend\",\n\"amende\",\n\"amender\",\n\"amends\",\n\"amene\",\n\"amenia\",\n\"amenity\",\n\"ament\",\n\"amental\",\n\"amentia\",\n\"amentum\",\n\"amerce\",\n\"amercer\",\n\"amerism\",\n\"amesite\",\n\"ametria\",\n\"amgarn\",\n\"amhar\",\n\"amhran\",\n\"ami\",\n\"amiable\",\n\"amiably\",\n\"amianth\",\n\"amic\",\n\"amical\",\n\"amice\",\n\"amiced\",\n\"amicron\",\n\"amid\",\n\"amidase\",\n\"amidate\",\n\"amide\",\n\"amidic\",\n\"amidid\",\n\"amidide\",\n\"amidin\",\n\"amidine\",\n\"amido\",\n\"amidol\",\n\"amidon\",\n\"amidoxy\",\n\"amidst\",\n\"amil\",\n\"amimia\",\n\"amimide\",\n\"amin\",\n\"aminate\",\n\"amine\",\n\"amini\",\n\"aminic\",\n\"aminity\",\n\"aminize\",\n\"amino\",\n\"aminoid\",\n\"amir\",\n\"amiray\",\n\"amiss\",\n\"amity\",\n\"amixia\",\n\"amla\",\n\"amli\",\n\"amlikar\",\n\"amlong\",\n\"amma\",\n\"amman\",\n\"ammelin\",\n\"ammer\",\n\"ammeter\",\n\"ammine\",\n\"ammo\",\n\"ammonal\",\n\"ammonia\",\n\"ammonic\",\n\"ammono\",\n\"ammu\",\n\"amnesia\",\n\"amnesic\",\n\"amnesty\",\n\"amnia\",\n\"amniac\",\n\"amnic\",\n\"amnion\",\n\"amniote\",\n\"amober\",\n\"amobyr\",\n\"amoeba\",\n\"amoebae\",\n\"amoeban\",\n\"amoebic\",\n\"amoebid\",\n\"amok\",\n\"amoke\",\n\"amole\",\n\"amomal\",\n\"amomum\",\n\"among\",\n\"amongst\",\n\"amor\",\n\"amorado\",\n\"amoraic\",\n\"amoraim\",\n\"amoral\",\n\"amoret\",\n\"amorism\",\n\"amorist\",\n\"amoroso\",\n\"amorous\",\n\"amorphy\",\n\"amort\",\n\"amotion\",\n\"amotus\",\n\"amount\",\n\"amour\",\n\"amove\",\n\"ampalea\",\n\"amper\",\n\"ampere\",\n\"ampery\",\n\"amphid\",\n\"amphide\",\n\"amphora\",\n\"amphore\",\n\"ample\",\n\"amplify\",\n\"amply\",\n\"ampoule\",\n\"ampul\",\n\"ampulla\",\n\"amputee\",\n\"ampyx\",\n\"amra\",\n\"amreeta\",\n\"amrita\",\n\"amsath\",\n\"amsel\",\n\"amt\",\n\"amtman\",\n\"amuck\",\n\"amuguis\",\n\"amula\",\n\"amulet\",\n\"amulla\",\n\"amunam\",\n\"amurca\",\n\"amuse\",\n\"amused\",\n\"amusee\",\n\"amuser\",\n\"amusia\",\n\"amusing\",\n\"amusive\",\n\"amutter\",\n\"amuyon\",\n\"amuyong\",\n\"amuze\",\n\"amvis\",\n\"amy\",\n\"amyelia\",\n\"amyelic\",\n\"amygdal\",\n\"amyl\",\n\"amylan\",\n\"amylase\",\n\"amylate\",\n\"amylene\",\n\"amylic\",\n\"amylin\",\n\"amylo\",\n\"amyloid\",\n\"amylom\",\n\"amylon\",\n\"amylose\",\n\"amylum\",\n\"amyous\",\n\"amyrin\",\n\"amyrol\",\n\"amyroot\",\n\"an\",\n\"ana\",\n\"anabata\",\n\"anabo\",\n\"anabong\",\n\"anacara\",\n\"anacard\",\n\"anacid\",\n\"anadem\",\n\"anadrom\",\n\"anaemia\",\n\"anaemic\",\n\"anagap\",\n\"anagep\",\n\"anagoge\",\n\"anagogy\",\n\"anagram\",\n\"anagua\",\n\"anahau\",\n\"anal\",\n\"analav\",\n\"analgen\",\n\"analgia\",\n\"analgic\",\n\"anally\",\n\"analogy\",\n\"analyse\",\n\"analyst\",\n\"analyze\",\n\"anam\",\n\"anama\",\n\"anamite\",\n\"anan\",\n\"anana\",\n\"ananas\",\n\"ananda\",\n\"ananym\",\n\"anaphia\",\n\"anapnea\",\n\"anapsid\",\n\"anaqua\",\n\"anarch\",\n\"anarchy\",\n\"anareta\",\n\"anarya\",\n\"anatase\",\n\"anatifa\",\n\"anatine\",\n\"anatomy\",\n\"anatox\",\n\"anatron\",\n\"anaudia\",\n\"anaxial\",\n\"anaxon\",\n\"anaxone\",\n\"anay\",\n\"anba\",\n\"anbury\",\n\"anchor\",\n\"anchovy\",\n\"ancient\",\n\"ancile\",\n\"ancilla\",\n\"ancon\",\n\"anconad\",\n\"anconal\",\n\"ancone\",\n\"ancony\",\n\"ancora\",\n\"ancoral\",\n\"and\",\n\"anda\",\n\"andante\",\n\"andirin\",\n\"andiron\",\n\"andric\",\n\"android\",\n\"androl\",\n\"andron\",\n\"anear\",\n\"aneath\",\n\"anele\",\n\"anemia\",\n\"anemic\",\n\"anemone\",\n\"anemony\",\n\"anend\",\n\"anenst\",\n\"anent\",\n\"anepia\",\n\"anergia\",\n\"anergic\",\n\"anergy\",\n\"anerly\",\n\"aneroid\",\n\"anes\",\n\"anesis\",\n\"aneuria\",\n\"aneuric\",\n\"aneurin\",\n\"anew\",\n\"angaria\",\n\"angary\",\n\"angekok\",\n\"angel\",\n\"angelet\",\n\"angelic\",\n\"angelin\",\n\"angelot\",\n\"anger\",\n\"angerly\",\n\"angeyok\",\n\"angico\",\n\"angild\",\n\"angili\",\n\"angina\",\n\"anginal\",\n\"angioid\",\n\"angioma\",\n\"angle\",\n\"angled\",\n\"angler\",\n\"angling\",\n\"angloid\",\n\"ango\",\n\"angolar\",\n\"angor\",\n\"angrily\",\n\"angrite\",\n\"angry\",\n\"angst\",\n\"angster\",\n\"anguid\",\n\"anguine\",\n\"anguis\",\n\"anguish\",\n\"angula\",\n\"angular\",\n\"anguria\",\n\"anhang\",\n\"anhima\",\n\"anhinga\",\n\"ani\",\n\"anicut\",\n\"anidian\",\n\"aniente\",\n\"anigh\",\n\"anight\",\n\"anights\",\n\"anil\",\n\"anilao\",\n\"anilau\",\n\"anile\",\n\"anilic\",\n\"anilid\",\n\"anilide\",\n\"aniline\",\n\"anility\",\n\"anilla\",\n\"anima\",\n\"animal\",\n\"animate\",\n\"anime\",\n\"animi\",\n\"animism\",\n\"animist\",\n\"animize\",\n\"animous\",\n\"animus\",\n\"anion\",\n\"anionic\",\n\"anis\",\n\"anisal\",\n\"anisate\",\n\"anise\",\n\"aniseed\",\n\"anisic\",\n\"anisil\",\n\"anisoin\",\n\"anisole\",\n\"anisoyl\",\n\"anisum\",\n\"anisyl\",\n\"anither\",\n\"anjan\",\n\"ankee\",\n\"anker\",\n\"ankh\",\n\"ankle\",\n\"anklet\",\n\"anklong\",\n\"ankus\",\n\"ankusha\",\n\"anlace\",\n\"anlaut\",\n\"ann\",\n\"anna\",\n\"annal\",\n\"annale\",\n\"annals\",\n\"annat\",\n\"annates\",\n\"annatto\",\n\"anneal\",\n\"annelid\",\n\"annet\",\n\"annex\",\n\"annexa\",\n\"annexal\",\n\"annexer\",\n\"annite\",\n\"annona\",\n\"annoy\",\n\"annoyer\",\n\"annual\",\n\"annuary\",\n\"annuent\",\n\"annuity\",\n\"annul\",\n\"annular\",\n\"annulet\",\n\"annulus\",\n\"anoa\",\n\"anodal\",\n\"anode\",\n\"anodic\",\n\"anodize\",\n\"anodos\",\n\"anodyne\",\n\"anoesia\",\n\"anoesis\",\n\"anoetic\",\n\"anoil\",\n\"anoine\",\n\"anoint\",\n\"anole\",\n\"anoli\",\n\"anolian\",\n\"anolyte\",\n\"anomaly\",\n\"anomite\",\n\"anomy\",\n\"anon\",\n\"anonang\",\n\"anonol\",\n\"anonym\",\n\"anonyma\",\n\"anopia\",\n\"anopsia\",\n\"anorak\",\n\"anorexy\",\n\"anormal\",\n\"anorth\",\n\"anosmia\",\n\"anosmic\",\n\"another\",\n\"anotia\",\n\"anotta\",\n\"anotto\",\n\"anotus\",\n\"anounou\",\n\"anoxia\",\n\"anoxic\",\n\"ansa\",\n\"ansar\",\n\"ansate\",\n\"ansu\",\n\"answer\",\n\"ant\",\n\"anta\",\n\"antacid\",\n\"antal\",\n\"antapex\",\n\"antdom\",\n\"ante\",\n\"anteact\",\n\"anteal\",\n\"antefix\",\n\"antenna\",\n\"antes\",\n\"antewar\",\n\"anthela\",\n\"anthem\",\n\"anthema\",\n\"anthemy\",\n\"anther\",\n\"anthill\",\n\"anthine\",\n\"anthoid\",\n\"anthood\",\n\"anthrax\",\n\"anthrol\",\n\"anthryl\",\n\"anti\",\n\"antiae\",\n\"antiar\",\n\"antic\",\n\"antical\",\n\"anticly\",\n\"anticor\",\n\"anticum\",\n\"antifat\",\n\"antigen\",\n\"antigod\",\n\"antihum\",\n\"antiqua\",\n\"antique\",\n\"antired\",\n\"antirun\",\n\"antisun\",\n\"antitax\",\n\"antiwar\",\n\"antiwit\",\n\"antler\",\n\"antlia\",\n\"antling\",\n\"antoeci\",\n\"antonym\",\n\"antra\",\n\"antral\",\n\"antre\",\n\"antrin\",\n\"antrum\",\n\"antship\",\n\"antu\",\n\"antwise\",\n\"anubing\",\n\"anuloma\",\n\"anuran\",\n\"anuria\",\n\"anuric\",\n\"anurous\",\n\"anury\",\n\"anus\",\n\"anusim\",\n\"anvil\",\n\"anxiety\",\n\"anxious\",\n\"any\",\n\"anybody\",\n\"anyhow\",\n\"anyone\",\n\"anyway\",\n\"anyways\",\n\"anywhen\",\n\"anywhy\",\n\"anywise\",\n\"aogiri\",\n\"aonach\",\n\"aorist\",\n\"aorta\",\n\"aortal\",\n\"aortic\",\n\"aortism\",\n\"aosmic\",\n\"aoudad\",\n\"apa\",\n\"apace\",\n\"apache\",\n\"apadana\",\n\"apagoge\",\n\"apaid\",\n\"apalit\",\n\"apandry\",\n\"apar\",\n\"aparejo\",\n\"apart\",\n\"apasote\",\n\"apatan\",\n\"apathic\",\n\"apathy\",\n\"apatite\",\n\"ape\",\n\"apeak\",\n\"apedom\",\n\"apehood\",\n\"apeiron\",\n\"apelet\",\n\"apelike\",\n\"apeling\",\n\"apepsia\",\n\"apepsy\",\n\"apeptic\",\n\"aper\",\n\"aperch\",\n\"aperea\",\n\"apert\",\n\"apertly\",\n\"apery\",\n\"apetaly\",\n\"apex\",\n\"apexed\",\n\"aphagia\",\n\"aphakia\",\n\"aphakic\",\n\"aphasia\",\n\"aphasic\",\n\"aphemia\",\n\"aphemic\",\n\"aphesis\",\n\"apheta\",\n\"aphetic\",\n\"aphid\",\n\"aphides\",\n\"aphidid\",\n\"aphodal\",\n\"aphodus\",\n\"aphonia\",\n\"aphonic\",\n\"aphony\",\n\"aphoria\",\n\"aphotic\",\n\"aphrite\",\n\"aphtha\",\n\"aphthic\",\n\"aphylly\",\n\"aphyric\",\n\"apian\",\n\"apiary\",\n\"apiator\",\n\"apicad\",\n\"apical\",\n\"apices\",\n\"apicula\",\n\"apiece\",\n\"apieces\",\n\"apii\",\n\"apiin\",\n\"apilary\",\n\"apinch\",\n\"aping\",\n\"apinoid\",\n\"apio\",\n\"apioid\",\n\"apiole\",\n\"apiolin\",\n\"apionol\",\n\"apiose\",\n\"apish\",\n\"apishly\",\n\"apism\",\n\"apitong\",\n\"apitpat\",\n\"aplanat\",\n\"aplasia\",\n\"aplenty\",\n\"aplite\",\n\"aplitic\",\n\"aplomb\",\n\"aplome\",\n\"apnea\",\n\"apneal\",\n\"apneic\",\n\"apocarp\",\n\"apocha\",\n\"apocope\",\n\"apod\",\n\"apodal\",\n\"apodan\",\n\"apodema\",\n\"apodeme\",\n\"apodia\",\n\"apodous\",\n\"apogamy\",\n\"apogeal\",\n\"apogean\",\n\"apogee\",\n\"apogeic\",\n\"apogeny\",\n\"apohyal\",\n\"apoise\",\n\"apojove\",\n\"apokrea\",\n\"apolar\",\n\"apology\",\n\"aponia\",\n\"aponic\",\n\"apoop\",\n\"apoplex\",\n\"apopyle\",\n\"aporia\",\n\"aporose\",\n\"aport\",\n\"aposia\",\n\"aposoro\",\n\"apostil\",\n\"apostle\",\n\"apothem\",\n\"apotome\",\n\"apotype\",\n\"apout\",\n\"apozem\",\n\"apozema\",\n\"appall\",\n\"apparel\",\n\"appay\",\n\"appeal\",\n\"appear\",\n\"appease\",\n\"append\",\n\"appet\",\n\"appete\",\n\"applaud\",\n\"apple\",\n\"applied\",\n\"applier\",\n\"applot\",\n\"apply\",\n\"appoint\",\n\"apport\",\n\"appose\",\n\"apposer\",\n\"apprend\",\n\"apprise\",\n\"apprize\",\n\"approof\",\n\"approve\",\n\"appulse\",\n\"apraxia\",\n\"apraxic\",\n\"apricot\",\n\"apriori\",\n\"apron\",\n\"apropos\",\n\"apse\",\n\"apsidal\",\n\"apsides\",\n\"apsis\",\n\"apt\",\n\"apteral\",\n\"apteran\",\n\"aptly\",\n\"aptness\",\n\"aptote\",\n\"aptotic\",\n\"apulse\",\n\"apyonin\",\n\"apyrene\",\n\"apyrexy\",\n\"apyrous\",\n\"aqua\",\n\"aquabib\",\n\"aquage\",\n\"aquaria\",\n\"aquatic\",\n\"aquavit\",\n\"aqueous\",\n\"aquifer\",\n\"aquiver\",\n\"aquo\",\n\"aquose\",\n\"ar\",\n\"ara\",\n\"araba\",\n\"araban\",\n\"arabana\",\n\"arabin\",\n\"arabit\",\n\"arable\",\n\"araca\",\n\"aracari\",\n\"arachic\",\n\"arachin\",\n\"arad\",\n\"arado\",\n\"arain\",\n\"arake\",\n\"araliad\",\n\"aralie\",\n\"aralkyl\",\n\"aramina\",\n\"araneid\",\n\"aranein\",\n\"aranga\",\n\"arango\",\n\"arar\",\n\"arara\",\n\"ararao\",\n\"arariba\",\n\"araroba\",\n\"arati\",\n\"aration\",\n\"aratory\",\n\"arba\",\n\"arbacin\",\n\"arbalo\",\n\"arbiter\",\n\"arbor\",\n\"arboral\",\n\"arbored\",\n\"arboret\",\n\"arbute\",\n\"arbutin\",\n\"arbutus\",\n\"arc\",\n\"arca\",\n\"arcade\",\n\"arcana\",\n\"arcanal\",\n\"arcane\",\n\"arcanum\",\n\"arcate\",\n\"arch\",\n\"archae\",\n\"archaic\",\n\"arche\",\n\"archeal\",\n\"arched\",\n\"archer\",\n\"archery\",\n\"arches\",\n\"archeus\",\n\"archfoe\",\n\"archgod\",\n\"archil\",\n\"arching\",\n\"archive\",\n\"archly\",\n\"archon\",\n\"archont\",\n\"archsee\",\n\"archsin\",\n\"archspy\",\n\"archwag\",\n\"archway\",\n\"archy\",\n\"arcing\",\n\"arcked\",\n\"arcking\",\n\"arctian\",\n\"arctic\",\n\"arctiid\",\n\"arctoid\",\n\"arcual\",\n\"arcuale\",\n\"arcuate\",\n\"arcula\",\n\"ardeb\",\n\"ardella\",\n\"ardency\",\n\"ardent\",\n\"ardish\",\n\"ardoise\",\n\"ardor\",\n\"ardri\",\n\"ardu\",\n\"arduous\",\n\"are\",\n\"area\",\n\"areach\",\n\"aread\",\n\"areal\",\n\"arear\",\n\"areaway\",\n\"arecain\",\n\"ared\",\n\"areek\",\n\"areel\",\n\"arefact\",\n\"areito\",\n\"arena\",\n\"arenae\",\n\"arend\",\n\"areng\",\n\"arenoid\",\n\"arenose\",\n\"arent\",\n\"areola\",\n\"areolar\",\n\"areole\",\n\"areolet\",\n\"arete\",\n\"argal\",\n\"argala\",\n\"argali\",\n\"argans\",\n\"argasid\",\n\"argeers\",\n\"argel\",\n\"argenol\",\n\"argent\",\n\"arghan\",\n\"arghel\",\n\"arghool\",\n\"argil\",\n\"argo\",\n\"argol\",\n\"argolet\",\n\"argon\",\n\"argosy\",\n\"argot\",\n\"argotic\",\n\"argue\",\n\"arguer\",\n\"argufy\",\n\"argute\",\n\"argyria\",\n\"argyric\",\n\"arhar\",\n\"arhat\",\n\"aria\",\n\"aribine\",\n\"aricine\",\n\"arid\",\n\"aridge\",\n\"aridian\",\n\"aridity\",\n\"aridly\",\n\"ariel\",\n\"arienzo\",\n\"arietta\",\n\"aright\",\n\"arigue\",\n\"aril\",\n\"ariled\",\n\"arillus\",\n\"ariose\",\n\"arioso\",\n\"ariot\",\n\"aripple\",\n\"arisard\",\n\"arise\",\n\"arisen\",\n\"arist\",\n\"arista\",\n\"arite\",\n\"arjun\",\n\"ark\",\n\"arkite\",\n\"arkose\",\n\"arkosic\",\n\"arles\",\n\"arm\",\n\"armada\",\n\"armbone\",\n\"armed\",\n\"armer\",\n\"armet\",\n\"armful\",\n\"armhole\",\n\"armhoop\",\n\"armied\",\n\"armiger\",\n\"armil\",\n\"armilla\",\n\"arming\",\n\"armless\",\n\"armlet\",\n\"armload\",\n\"armoire\",\n\"armor\",\n\"armored\",\n\"armorer\",\n\"armory\",\n\"armpit\",\n\"armrack\",\n\"armrest\",\n\"arms\",\n\"armscye\",\n\"armure\",\n\"army\",\n\"arn\",\n\"arna\",\n\"arnee\",\n\"arni\",\n\"arnica\",\n\"arnotta\",\n\"arnotto\",\n\"arnut\",\n\"aroar\",\n\"aroast\",\n\"arock\",\n\"aroeira\",\n\"aroid\",\n\"aroint\",\n\"arolium\",\n\"arolla\",\n\"aroma\",\n\"aroon\",\n\"arose\",\n\"around\",\n\"arousal\",\n\"arouse\",\n\"arouser\",\n\"arow\",\n\"aroxyl\",\n\"arpen\",\n\"arpent\",\n\"arrack\",\n\"arrah\",\n\"arraign\",\n\"arrame\",\n\"arrange\",\n\"arrant\",\n\"arras\",\n\"arrased\",\n\"arratel\",\n\"arrau\",\n\"array\",\n\"arrayal\",\n\"arrayer\",\n\"arrear\",\n\"arrect\",\n\"arrent\",\n\"arrest\",\n\"arriage\",\n\"arriba\",\n\"arride\",\n\"arridge\",\n\"arrie\",\n\"arriere\",\n\"arrimby\",\n\"arris\",\n\"arrish\",\n\"arrival\",\n\"arrive\",\n\"arriver\",\n\"arroba\",\n\"arrope\",\n\"arrow\",\n\"arrowed\",\n\"arrowy\",\n\"arroyo\",\n\"arse\",\n\"arsenal\",\n\"arsenic\",\n\"arseno\",\n\"arsenyl\",\n\"arses\",\n\"arsheen\",\n\"arshin\",\n\"arshine\",\n\"arsine\",\n\"arsinic\",\n\"arsino\",\n\"arsis\",\n\"arsle\",\n\"arsoite\",\n\"arson\",\n\"arsonic\",\n\"arsono\",\n\"arsyl\",\n\"art\",\n\"artaba\",\n\"artabe\",\n\"artal\",\n\"artar\",\n\"artel\",\n\"arterin\",\n\"artery\",\n\"artful\",\n\"artha\",\n\"arthel\",\n\"arthral\",\n\"artiad\",\n\"article\",\n\"artisan\",\n\"artist\",\n\"artiste\",\n\"artless\",\n\"artlet\",\n\"artlike\",\n\"artware\",\n\"arty\",\n\"aru\",\n\"arui\",\n\"aruke\",\n\"arumin\",\n\"arupa\",\n\"arusa\",\n\"arusha\",\n\"arustle\",\n\"arval\",\n\"arvel\",\n\"arx\",\n\"ary\",\n\"aryl\",\n\"arylate\",\n\"arzan\",\n\"arzun\",\n\"as\",\n\"asaddle\",\n\"asak\",\n\"asale\",\n\"asana\",\n\"asaphia\",\n\"asaphid\",\n\"asaprol\",\n\"asarite\",\n\"asaron\",\n\"asarone\",\n\"asbest\",\n\"asbolin\",\n\"ascan\",\n\"ascare\",\n\"ascarid\",\n\"ascaron\",\n\"ascend\",\n\"ascent\",\n\"ascetic\",\n\"ascham\",\n\"asci\",\n\"ascian\",\n\"ascii\",\n\"ascites\",\n\"ascitic\",\n\"asclent\",\n\"ascoma\",\n\"ascon\",\n\"ascot\",\n\"ascribe\",\n\"ascript\",\n\"ascry\",\n\"ascula\",\n\"ascus\",\n\"asdic\",\n\"ase\",\n\"asearch\",\n\"aseethe\",\n\"aseity\",\n\"asem\",\n\"asemia\",\n\"asepsis\",\n\"aseptic\",\n\"aseptol\",\n\"asexual\",\n\"ash\",\n\"ashake\",\n\"ashame\",\n\"ashamed\",\n\"ashamnu\",\n\"ashcake\",\n\"ashen\",\n\"asherah\",\n\"ashery\",\n\"ashes\",\n\"ashet\",\n\"ashily\",\n\"ashine\",\n\"ashiver\",\n\"ashkoko\",\n\"ashlar\",\n\"ashless\",\n\"ashling\",\n\"ashman\",\n\"ashore\",\n\"ashpan\",\n\"ashpit\",\n\"ashraf\",\n\"ashrafi\",\n\"ashur\",\n\"ashweed\",\n\"ashwort\",\n\"ashy\",\n\"asialia\",\n\"aside\",\n\"asideu\",\n\"asiento\",\n\"asilid\",\n\"asimen\",\n\"asimmer\",\n\"asinego\",\n\"asinine\",\n\"asitia\",\n\"ask\",\n\"askable\",\n\"askance\",\n\"askant\",\n\"askar\",\n\"askari\",\n\"asker\",\n\"askew\",\n\"askip\",\n\"asklent\",\n\"askos\",\n\"aslant\",\n\"aslaver\",\n\"asleep\",\n\"aslop\",\n\"aslope\",\n\"asmack\",\n\"asmalte\",\n\"asmear\",\n\"asmile\",\n\"asmoke\",\n\"asnort\",\n\"asoak\",\n\"asocial\",\n\"asok\",\n\"asoka\",\n\"asonant\",\n\"asonia\",\n\"asop\",\n\"asor\",\n\"asouth\",\n\"asp\",\n\"aspace\",\n\"aspect\",\n\"aspen\",\n\"asper\",\n\"asperge\",\n\"asperse\",\n\"asphalt\",\n\"asphyxy\",\n\"aspic\",\n\"aspire\",\n\"aspirer\",\n\"aspirin\",\n\"aspish\",\n\"asport\",\n\"aspout\",\n\"asprawl\",\n\"aspread\",\n\"aspring\",\n\"asprout\",\n\"asquare\",\n\"asquat\",\n\"asqueal\",\n\"asquint\",\n\"asquirm\",\n\"ass\",\n\"assacu\",\n\"assagai\",\n\"assai\",\n\"assail\",\n\"assapan\",\n\"assart\",\n\"assary\",\n\"assate\",\n\"assault\",\n\"assaut\",\n\"assay\",\n\"assayer\",\n\"assbaa\",\n\"asse\",\n\"assegai\",\n\"asself\",\n\"assent\",\n\"assert\",\n\"assess\",\n\"asset\",\n\"assets\",\n\"assever\",\n\"asshead\",\n\"assi\",\n\"assify\",\n\"assign\",\n\"assilag\",\n\"assis\",\n\"assise\",\n\"assish\",\n\"assist\",\n\"assize\",\n\"assizer\",\n\"assizes\",\n\"asslike\",\n\"assman\",\n\"assoil\",\n\"assort\",\n\"assuade\",\n\"assuage\",\n\"assume\",\n\"assumed\",\n\"assumer\",\n\"assure\",\n\"assured\",\n\"assurer\",\n\"assurge\",\n\"ast\",\n\"asta\",\n\"astalk\",\n\"astare\",\n\"astart\",\n\"astasia\",\n\"astatic\",\n\"astay\",\n\"asteam\",\n\"asteep\",\n\"asteer\",\n\"asteism\",\n\"astelic\",\n\"astely\",\n\"aster\",\n\"asteria\",\n\"asterin\",\n\"astern\",\n\"astheny\",\n\"asthma\",\n\"asthore\",\n\"astilbe\",\n\"astint\",\n\"astir\",\n\"astite\",\n\"astomia\",\n\"astony\",\n\"astoop\",\n\"astor\",\n\"astound\",\n\"astrain\",\n\"astral\",\n\"astrand\",\n\"astray\",\n\"astream\",\n\"astrer\",\n\"astrict\",\n\"astride\",\n\"astrier\",\n\"astrild\",\n\"astroid\",\n\"astrut\",\n\"astute\",\n\"astylar\",\n\"asudden\",\n\"asunder\",\n\"aswail\",\n\"aswarm\",\n\"asway\",\n\"asweat\",\n\"aswell\",\n\"aswim\",\n\"aswing\",\n\"aswirl\",\n\"aswoon\",\n\"asyla\",\n\"asylum\",\n\"at\",\n\"atabal\",\n\"atabeg\",\n\"atabek\",\n\"atactic\",\n\"atafter\",\n\"ataman\",\n\"atangle\",\n\"atap\",\n\"ataraxy\",\n\"ataunt\",\n\"atavi\",\n\"atavic\",\n\"atavism\",\n\"atavist\",\n\"atavus\",\n\"ataxia\",\n\"ataxic\",\n\"ataxite\",\n\"ataxy\",\n\"atazir\",\n\"atbash\",\n\"ate\",\n\"atebrin\",\n\"atechny\",\n\"ateeter\",\n\"atef\",\n\"atelets\",\n\"atelier\",\n\"atelo\",\n\"ates\",\n\"ateuchi\",\n\"athanor\",\n\"athar\",\n\"atheism\",\n\"atheist\",\n\"atheize\",\n\"athelia\",\n\"athenee\",\n\"athenor\",\n\"atheous\",\n\"athing\",\n\"athirst\",\n\"athlete\",\n\"athodyd\",\n\"athort\",\n\"athrill\",\n\"athrive\",\n\"athrob\",\n\"athrong\",\n\"athwart\",\n\"athymia\",\n\"athymic\",\n\"athymy\",\n\"athyria\",\n\"athyrid\",\n\"atilt\",\n\"atimon\",\n\"atinga\",\n\"atingle\",\n\"atinkle\",\n\"atip\",\n\"atis\",\n\"atlas\",\n\"atlatl\",\n\"atle\",\n\"atlee\",\n\"atloid\",\n\"atma\",\n\"atman\",\n\"atmid\",\n\"atmo\",\n\"atmos\",\n\"atocha\",\n\"atocia\",\n\"atokal\",\n\"atoke\",\n\"atokous\",\n\"atoll\",\n\"atom\",\n\"atomerg\",\n\"atomic\",\n\"atomics\",\n\"atomism\",\n\"atomist\",\n\"atomity\",\n\"atomize\",\n\"atomy\",\n\"atonal\",\n\"atone\",\n\"atoner\",\n\"atonia\",\n\"atonic\",\n\"atony\",\n\"atop\",\n\"atophan\",\n\"atopic\",\n\"atopite\",\n\"atopy\",\n\"atour\",\n\"atoxic\",\n\"atoxyl\",\n\"atrail\",\n\"atrepsy\",\n\"atresia\",\n\"atresic\",\n\"atresy\",\n\"atretic\",\n\"atria\",\n\"atrial\",\n\"atrip\",\n\"atrium\",\n\"atrocha\",\n\"atropal\",\n\"atrophy\",\n\"atropia\",\n\"atropic\",\n\"atrous\",\n\"atry\",\n\"atta\",\n\"attacco\",\n\"attach\",\n\"attache\",\n\"attack\",\n\"attacus\",\n\"attagen\",\n\"attain\",\n\"attaint\",\n\"attaleh\",\n\"attar\",\n\"attask\",\n\"attempt\",\n\"attend\",\n\"attent\",\n\"atter\",\n\"attern\",\n\"attery\",\n\"attest\",\n\"attic\",\n\"attid\",\n\"attinge\",\n\"attire\",\n\"attired\",\n\"attirer\",\n\"attorn\",\n\"attract\",\n\"attrap\",\n\"attrist\",\n\"attrite\",\n\"attune\",\n\"atule\",\n\"atumble\",\n\"atune\",\n\"atwain\",\n\"atweel\",\n\"atween\",\n\"atwin\",\n\"atwirl\",\n\"atwist\",\n\"atwitch\",\n\"atwixt\",\n\"atwo\",\n\"atypic\",\n\"atypy\",\n\"auantic\",\n\"aube\",\n\"aubrite\",\n\"auburn\",\n\"auca\",\n\"auchlet\",\n\"auction\",\n\"aucuba\",\n\"audible\",\n\"audibly\",\n\"audient\",\n\"audile\",\n\"audio\",\n\"audion\",\n\"audit\",\n\"auditor\",\n\"auge\",\n\"augen\",\n\"augend\",\n\"auger\",\n\"augerer\",\n\"augh\",\n\"aught\",\n\"augite\",\n\"augitic\",\n\"augment\",\n\"augur\",\n\"augural\",\n\"augury\",\n\"august\",\n\"auh\",\n\"auhuhu\",\n\"auk\",\n\"auklet\",\n\"aula\",\n\"aulae\",\n\"auld\",\n\"auletai\",\n\"aulete\",\n\"auletes\",\n\"auletic\",\n\"aulic\",\n\"auloi\",\n\"aulos\",\n\"aulu\",\n\"aum\",\n\"aumaga\",\n\"aumail\",\n\"aumbry\",\n\"aumery\",\n\"aumil\",\n\"aumous\",\n\"aumrie\",\n\"auncel\",\n\"aune\",\n\"aunt\",\n\"auntie\",\n\"auntish\",\n\"auntly\",\n\"aupaka\",\n\"aura\",\n\"aurae\",\n\"aural\",\n\"aurally\",\n\"aurar\",\n\"aurate\",\n\"aurated\",\n\"aureate\",\n\"aureity\",\n\"aurelia\",\n\"aureola\",\n\"aureole\",\n\"aureous\",\n\"auresca\",\n\"aureus\",\n\"auric\",\n\"auricle\",\n\"auride\",\n\"aurific\",\n\"aurify\",\n\"aurigal\",\n\"aurin\",\n\"aurir\",\n\"aurist\",\n\"aurite\",\n\"aurochs\",\n\"auronal\",\n\"aurora\",\n\"aurorae\",\n\"auroral\",\n\"aurore\",\n\"aurous\",\n\"aurum\",\n\"aurure\",\n\"auryl\",\n\"auscult\",\n\"auslaut\",\n\"auspex\",\n\"auspice\",\n\"auspicy\",\n\"austere\",\n\"austral\",\n\"ausu\",\n\"ausubo\",\n\"autarch\",\n\"autarky\",\n\"aute\",\n\"autecy\",\n\"autem\",\n\"author\",\n\"autism\",\n\"autist\",\n\"auto\",\n\"autobus\",\n\"autocab\",\n\"autocar\",\n\"autoecy\",\n\"autoist\",\n\"automa\",\n\"automat\",\n\"autonym\",\n\"autopsy\",\n\"autumn\",\n\"auxesis\",\n\"auxetic\",\n\"auxin\",\n\"auxinic\",\n\"auxotox\",\n\"ava\",\n\"avadana\",\n\"avahi\",\n\"avail\",\n\"aval\",\n\"avalent\",\n\"avania\",\n\"avarice\",\n\"avast\",\n\"avaunt\",\n\"ave\",\n\"avellan\",\n\"aveloz\",\n\"avenage\",\n\"avener\",\n\"avenge\",\n\"avenger\",\n\"avenin\",\n\"avenous\",\n\"avens\",\n\"avenue\",\n\"aver\",\n\"avera\",\n\"average\",\n\"averah\",\n\"averil\",\n\"averin\",\n\"averral\",\n\"averse\",\n\"avert\",\n\"averted\",\n\"averter\",\n\"avian\",\n\"aviary\",\n\"aviate\",\n\"aviatic\",\n\"aviator\",\n\"avichi\",\n\"avicide\",\n\"avick\",\n\"avid\",\n\"avidity\",\n\"avidly\",\n\"avidous\",\n\"avidya\",\n\"avigate\",\n\"avijja\",\n\"avine\",\n\"aviso\",\n\"avital\",\n\"avitic\",\n\"avives\",\n\"avo\",\n\"avocado\",\n\"avocate\",\n\"avocet\",\n\"avodire\",\n\"avoid\",\n\"avoider\",\n\"avolate\",\n\"avouch\",\n\"avow\",\n\"avowal\",\n\"avowant\",\n\"avowed\",\n\"avower\",\n\"avowry\",\n\"avoyer\",\n\"avulse\",\n\"aw\",\n\"awa\",\n\"awabi\",\n\"awaft\",\n\"awag\",\n\"await\",\n\"awaiter\",\n\"awake\",\n\"awaken\",\n\"awald\",\n\"awalim\",\n\"awalt\",\n\"awane\",\n\"awapuhi\",\n\"award\",\n\"awarder\",\n\"aware\",\n\"awash\",\n\"awaste\",\n\"awat\",\n\"awatch\",\n\"awater\",\n\"awave\",\n\"away\",\n\"awber\",\n\"awd\",\n\"awe\",\n\"aweary\",\n\"aweband\",\n\"awee\",\n\"aweek\",\n\"aweel\",\n\"aweigh\",\n\"awesome\",\n\"awest\",\n\"aweto\",\n\"awfu\",\n\"awful\",\n\"awfully\",\n\"awheel\",\n\"awheft\",\n\"awhet\",\n\"awhile\",\n\"awhir\",\n\"awhirl\",\n\"awide\",\n\"awiggle\",\n\"awin\",\n\"awing\",\n\"awink\",\n\"awiwi\",\n\"awkward\",\n\"awl\",\n\"awless\",\n\"awlwort\",\n\"awmous\",\n\"awn\",\n\"awned\",\n\"awner\",\n\"awning\",\n\"awnless\",\n\"awnlike\",\n\"awny\",\n\"awoke\",\n\"awork\",\n\"awreck\",\n\"awrist\",\n\"awrong\",\n\"awry\",\n\"ax\",\n\"axal\",\n\"axe\",\n\"axed\",\n\"axenic\",\n\"axes\",\n\"axfetch\",\n\"axhead\",\n\"axial\",\n\"axially\",\n\"axiate\",\n\"axiform\",\n\"axil\",\n\"axile\",\n\"axilla\",\n\"axillae\",\n\"axillar\",\n\"axine\",\n\"axinite\",\n\"axiom\",\n\"axion\",\n\"axis\",\n\"axised\",\n\"axite\",\n\"axle\",\n\"axled\",\n\"axmaker\",\n\"axman\",\n\"axogamy\",\n\"axoid\",\n\"axolotl\",\n\"axon\",\n\"axonal\",\n\"axonost\",\n\"axseed\",\n\"axstone\",\n\"axtree\",\n\"axunge\",\n\"axweed\",\n\"axwise\",\n\"axwort\",\n\"ay\",\n\"ayah\",\n\"aye\",\n\"ayelp\",\n\"ayin\",\n\"ayless\",\n\"aylet\",\n\"ayllu\",\n\"ayond\",\n\"ayont\",\n\"ayous\",\n\"ayu\",\n\"azafrin\",\n\"azalea\",\n\"azarole\",\n\"azelaic\",\n\"azelate\",\n\"azide\",\n\"azilut\",\n\"azimene\",\n\"azimide\",\n\"azimine\",\n\"azimino\",\n\"azimuth\",\n\"azine\",\n\"aziola\",\n\"azo\",\n\"azoch\",\n\"azofier\",\n\"azofy\",\n\"azoic\",\n\"azole\",\n\"azon\",\n\"azonal\",\n\"azonic\",\n\"azonium\",\n\"azophen\",\n\"azorite\",\n\"azotate\",\n\"azote\",\n\"azoted\",\n\"azoth\",\n\"azotic\",\n\"azotine\",\n\"azotite\",\n\"azotize\",\n\"azotous\",\n\"azox\",\n\"azoxime\",\n\"azoxine\",\n\"azoxy\",\n\"azteca\",\n\"azulene\",\n\"azulite\",\n\"azulmic\",\n\"azumbre\",\n\"azure\",\n\"azurean\",\n\"azured\",\n\"azurine\",\n\"azurite\",\n\"azurous\",\n\"azury\",\n\"azygos\",\n\"azygous\",\n\"azyme\",\n\"azymite\",\n\"azymous\",\n\"b\",\n\"ba\",\n\"baa\",\n\"baal\",\n\"baar\",\n\"baba\",\n\"babai\",\n\"babasco\",\n\"babassu\",\n\"babbitt\",\n\"babble\",\n\"babbler\",\n\"babbly\",\n\"babby\",\n\"babe\",\n\"babelet\",\n\"babery\",\n\"babiche\",\n\"babied\",\n\"babish\",\n\"bablah\",\n\"babloh\",\n\"baboen\",\n\"baboo\",\n\"baboon\",\n\"baboot\",\n\"babroot\",\n\"babu\",\n\"babudom\",\n\"babuina\",\n\"babuism\",\n\"babul\",\n\"baby\",\n\"babydom\",\n\"babyish\",\n\"babyism\",\n\"bac\",\n\"bacaba\",\n\"bacach\",\n\"bacalao\",\n\"bacao\",\n\"bacca\",\n\"baccae\",\n\"baccara\",\n\"baccate\",\n\"bacchar\",\n\"bacchic\",\n\"bacchii\",\n\"bach\",\n\"bache\",\n\"bachel\",\n\"bacilli\",\n\"back\",\n\"backage\",\n\"backcap\",\n\"backed\",\n\"backen\",\n\"backer\",\n\"backet\",\n\"backie\",\n\"backing\",\n\"backjaw\",\n\"backlet\",\n\"backlog\",\n\"backrun\",\n\"backsaw\",\n\"backset\",\n\"backup\",\n\"backway\",\n\"baclin\",\n\"bacon\",\n\"baconer\",\n\"bacony\",\n\"bacula\",\n\"bacule\",\n\"baculi\",\n\"baculum\",\n\"baculus\",\n\"bacury\",\n\"bad\",\n\"badan\",\n\"baddish\",\n\"baddock\",\n\"bade\",\n\"badge\",\n\"badger\",\n\"badiaga\",\n\"badian\",\n\"badious\",\n\"badland\",\n\"badly\",\n\"badness\",\n\"bae\",\n\"baetuli\",\n\"baetyl\",\n\"bafaro\",\n\"baff\",\n\"baffeta\",\n\"baffle\",\n\"baffler\",\n\"baffy\",\n\"baft\",\n\"bafta\",\n\"bag\",\n\"baga\",\n\"bagani\",\n\"bagasse\",\n\"bagel\",\n\"bagful\",\n\"baggage\",\n\"baggala\",\n\"bagged\",\n\"bagger\",\n\"baggie\",\n\"baggily\",\n\"bagging\",\n\"baggit\",\n\"baggy\",\n\"baglike\",\n\"bagman\",\n\"bagnio\",\n\"bagnut\",\n\"bago\",\n\"bagonet\",\n\"bagpipe\",\n\"bagre\",\n\"bagreef\",\n\"bagroom\",\n\"bagwig\",\n\"bagworm\",\n\"bagwyn\",\n\"bah\",\n\"bahan\",\n\"bahar\",\n\"bahay\",\n\"bahera\",\n\"bahisti\",\n\"bahnung\",\n\"baho\",\n\"bahoe\",\n\"bahoo\",\n\"baht\",\n\"bahur\",\n\"bahut\",\n\"baignet\",\n\"baikie\",\n\"bail\",\n\"bailage\",\n\"bailee\",\n\"bailer\",\n\"bailey\",\n\"bailie\",\n\"bailiff\",\n\"bailor\",\n\"bain\",\n\"bainie\",\n\"baioc\",\n\"baiocco\",\n\"bairagi\",\n\"bairn\",\n\"bairnie\",\n\"bairnly\",\n\"baister\",\n\"bait\",\n\"baiter\",\n\"baith\",\n\"baittle\",\n\"baize\",\n\"bajada\",\n\"bajan\",\n\"bajra\",\n\"bajree\",\n\"bajri\",\n\"bajury\",\n\"baka\",\n\"bakal\",\n\"bake\",\n\"baked\",\n\"baken\",\n\"bakepan\",\n\"baker\",\n\"bakerly\",\n\"bakery\",\n\"bakie\",\n\"baking\",\n\"bakli\",\n\"baktun\",\n\"baku\",\n\"bakula\",\n\"bal\",\n\"balafo\",\n\"balagan\",\n\"balai\",\n\"balance\",\n\"balanic\",\n\"balanid\",\n\"balao\",\n\"balas\",\n\"balata\",\n\"balboa\",\n\"balcony\",\n\"bald\",\n\"balden\",\n\"balder\",\n\"baldish\",\n\"baldly\",\n\"baldrib\",\n\"baldric\",\n\"baldy\",\n\"bale\",\n\"baleen\",\n\"baleful\",\n\"balei\",\n\"baleise\",\n\"baler\",\n\"balete\",\n\"bali\",\n\"baline\",\n\"balita\",\n\"balk\",\n\"balker\",\n\"balky\",\n\"ball\",\n\"ballad\",\n\"ballade\",\n\"ballam\",\n\"ballan\",\n\"ballant\",\n\"ballast\",\n\"ballata\",\n\"ballate\",\n\"balldom\",\n\"balled\",\n\"baller\",\n\"ballet\",\n\"balli\",\n\"ballist\",\n\"ballium\",\n\"balloon\",\n\"ballot\",\n\"ballow\",\n\"ballup\",\n\"bally\",\n\"balm\",\n\"balmily\",\n\"balmony\",\n\"balmy\",\n\"balneal\",\n\"balonea\",\n\"baloney\",\n\"baloo\",\n\"balow\",\n\"balsa\",\n\"balsam\",\n\"balsamo\",\n\"balsamy\",\n\"baltei\",\n\"balter\",\n\"balteus\",\n\"balu\",\n\"balut\",\n\"balza\",\n\"bam\",\n\"bamban\",\n\"bambini\",\n\"bambino\",\n\"bamboo\",\n\"bamoth\",\n\"ban\",\n\"banaba\",\n\"banago\",\n\"banak\",\n\"banal\",\n\"banally\",\n\"banana\",\n\"banat\",\n\"banc\",\n\"banca\",\n\"bancal\",\n\"banchi\",\n\"banco\",\n\"bancus\",\n\"band\",\n\"banda\",\n\"bandage\",\n\"bandaka\",\n\"bandala\",\n\"bandar\",\n\"bandbox\",\n\"bande\",\n\"bandeau\",\n\"banded\",\n\"bander\",\n\"bandhu\",\n\"bandi\",\n\"bandie\",\n\"banding\",\n\"bandit\",\n\"bandle\",\n\"bandlet\",\n\"bandman\",\n\"bando\",\n\"bandog\",\n\"bandore\",\n\"bandrol\",\n\"bandy\",\n\"bane\",\n\"baneful\",\n\"bang\",\n\"banga\",\n\"bange\",\n\"banger\",\n\"banghy\",\n\"banging\",\n\"bangkok\",\n\"bangle\",\n\"bangled\",\n\"bani\",\n\"banian\",\n\"banig\",\n\"banilad\",\n\"banish\",\n\"baniwa\",\n\"baniya\",\n\"banjo\",\n\"banjore\",\n\"banjuke\",\n\"bank\",\n\"banked\",\n\"banker\",\n\"bankera\",\n\"banket\",\n\"banking\",\n\"bankman\",\n\"banky\",\n\"banner\",\n\"bannet\",\n\"banning\",\n\"bannock\",\n\"banns\",\n\"bannut\",\n\"banquet\",\n\"banshee\",\n\"bant\",\n\"bantam\",\n\"bantay\",\n\"banteng\",\n\"banter\",\n\"bantery\",\n\"banty\",\n\"banuyo\",\n\"banya\",\n\"banyan\",\n\"banzai\",\n\"baobab\",\n\"bap\",\n\"baptism\",\n\"baptize\",\n\"bar\",\n\"bara\",\n\"barad\",\n\"barauna\",\n\"barb\",\n\"barbal\",\n\"barbary\",\n\"barbas\",\n\"barbate\",\n\"barbe\",\n\"barbed\",\n\"barbel\",\n\"barber\",\n\"barbet\",\n\"barbion\",\n\"barblet\",\n\"barbone\",\n\"barbudo\",\n\"barbule\",\n\"bard\",\n\"bardane\",\n\"bardash\",\n\"bardel\",\n\"bardess\",\n\"bardic\",\n\"bardie\",\n\"bardily\",\n\"barding\",\n\"bardish\",\n\"bardism\",\n\"bardlet\",\n\"bardo\",\n\"bardy\",\n\"bare\",\n\"bareca\",\n\"barefit\",\n\"barely\",\n\"barer\",\n\"baresma\",\n\"baretta\",\n\"barff\",\n\"barfish\",\n\"barfly\",\n\"barful\",\n\"bargain\",\n\"barge\",\n\"bargee\",\n\"bargeer\",\n\"barger\",\n\"bargh\",\n\"bargham\",\n\"bari\",\n\"baria\",\n\"baric\",\n\"barid\",\n\"barie\",\n\"barile\",\n\"barilla\",\n\"baring\",\n\"baris\",\n\"barish\",\n\"barit\",\n\"barite\",\n\"barium\",\n\"bark\",\n\"barken\",\n\"barker\",\n\"barkery\",\n\"barkey\",\n\"barkhan\",\n\"barking\",\n\"barkle\",\n\"barky\",\n\"barless\",\n\"barley\",\n\"barling\",\n\"barlock\",\n\"barlow\",\n\"barm\",\n\"barmaid\",\n\"barman\",\n\"barmkin\",\n\"barmote\",\n\"barmy\",\n\"barn\",\n\"barnard\",\n\"barney\",\n\"barnful\",\n\"barnman\",\n\"barny\",\n\"baroi\",\n\"barolo\",\n\"baron\",\n\"baronet\",\n\"barong\",\n\"baronry\",\n\"barony\",\n\"baroque\",\n\"baroto\",\n\"barpost\",\n\"barra\",\n\"barrack\",\n\"barrad\",\n\"barrage\",\n\"barras\",\n\"barred\",\n\"barrel\",\n\"barren\",\n\"barrer\",\n\"barret\",\n\"barrico\",\n\"barrier\",\n\"barring\",\n\"barrio\",\n\"barroom\",\n\"barrow\",\n\"barruly\",\n\"barry\",\n\"barse\",\n\"barsom\",\n\"barter\",\n\"barth\",\n\"barton\",\n\"baru\",\n\"baruria\",\n\"barvel\",\n\"barwal\",\n\"barway\",\n\"barways\",\n\"barwise\",\n\"barwood\",\n\"barye\",\n\"baryta\",\n\"barytes\",\n\"barytic\",\n\"baryton\",\n\"bas\",\n\"basal\",\n\"basale\",\n\"basalia\",\n\"basally\",\n\"basalt\",\n\"basaree\",\n\"bascule\",\n\"base\",\n\"based\",\n\"basely\",\n\"baseman\",\n\"basenji\",\n\"bases\",\n\"bash\",\n\"bashaw\",\n\"bashful\",\n\"bashlyk\",\n\"basial\",\n\"basiate\",\n\"basic\",\n\"basidia\",\n\"basify\",\n\"basil\",\n\"basilar\",\n\"basilic\",\n\"basin\",\n\"basined\",\n\"basinet\",\n\"basion\",\n\"basis\",\n\"bask\",\n\"basker\",\n\"basket\",\n\"basoid\",\n\"bason\",\n\"basos\",\n\"basote\",\n\"basque\",\n\"basqued\",\n\"bass\",\n\"bassan\",\n\"bassara\",\n\"basset\",\n\"bassie\",\n\"bassine\",\n\"bassist\",\n\"basso\",\n\"bassoon\",\n\"bassus\",\n\"bast\",\n\"basta\",\n\"bastard\",\n\"baste\",\n\"basten\",\n\"baster\",\n\"bastide\",\n\"basting\",\n\"bastion\",\n\"bastite\",\n\"basto\",\n\"baston\",\n\"bat\",\n\"bataan\",\n\"batad\",\n\"batakan\",\n\"batara\",\n\"batata\",\n\"batch\",\n\"batcher\",\n\"bate\",\n\"batea\",\n\"bateau\",\n\"bateaux\",\n\"bated\",\n\"batel\",\n\"bateman\",\n\"bater\",\n\"batfish\",\n\"batfowl\",\n\"bath\",\n\"bathe\",\n\"bather\",\n\"bathic\",\n\"bathing\",\n\"bathman\",\n\"bathmic\",\n\"bathos\",\n\"bathtub\",\n\"bathyal\",\n\"batik\",\n\"batiker\",\n\"bating\",\n\"batino\",\n\"batiste\",\n\"batlan\",\n\"batlike\",\n\"batling\",\n\"batlon\",\n\"batman\",\n\"batoid\",\n\"baton\",\n\"batonne\",\n\"bats\",\n\"batsman\",\n\"batster\",\n\"batt\",\n\"batta\",\n\"battel\",\n\"batten\",\n\"batter\",\n\"battery\",\n\"battik\",\n\"batting\",\n\"battish\",\n\"battle\",\n\"battled\",\n\"battler\",\n\"battue\",\n\"batty\",\n\"batule\",\n\"batwing\",\n\"batz\",\n\"batzen\",\n\"bauble\",\n\"bauch\",\n\"bauchle\",\n\"bauckie\",\n\"baud\",\n\"baul\",\n\"bauleah\",\n\"baun\",\n\"bauno\",\n\"bauson\",\n\"bausond\",\n\"bauta\",\n\"bauxite\",\n\"bavaroy\",\n\"bavary\",\n\"bavian\",\n\"baviere\",\n\"bavin\",\n\"bavoso\",\n\"baw\",\n\"bawbee\",\n\"bawcock\",\n\"bawd\",\n\"bawdily\",\n\"bawdry\",\n\"bawl\",\n\"bawler\",\n\"bawley\",\n\"bawn\",\n\"bawtie\",\n\"baxter\",\n\"baxtone\",\n\"bay\",\n\"baya\",\n\"bayal\",\n\"bayamo\",\n\"bayard\",\n\"baybolt\",\n\"baybush\",\n\"baycuru\",\n\"bayed\",\n\"bayeta\",\n\"baygall\",\n\"bayhead\",\n\"bayish\",\n\"baylet\",\n\"baylike\",\n\"bayman\",\n\"bayness\",\n\"bayok\",\n\"bayonet\",\n\"bayou\",\n\"baywood\",\n\"bazaar\",\n\"baze\",\n\"bazoo\",\n\"bazooka\",\n\"bazzite\",\n\"bdellid\",\n\"be\",\n\"beach\",\n\"beached\",\n\"beachy\",\n\"beacon\",\n\"bead\",\n\"beaded\",\n\"beader\",\n\"beadily\",\n\"beading\",\n\"beadle\",\n\"beadlet\",\n\"beadman\",\n\"beadrow\",\n\"beady\",\n\"beagle\",\n\"beak\",\n\"beaked\",\n\"beaker\",\n\"beakful\",\n\"beaky\",\n\"beal\",\n\"beala\",\n\"bealing\",\n\"beam\",\n\"beamage\",\n\"beamed\",\n\"beamer\",\n\"beamful\",\n\"beamily\",\n\"beaming\",\n\"beamish\",\n\"beamlet\",\n\"beamman\",\n\"beamy\",\n\"bean\",\n\"beanbag\",\n\"beancod\",\n\"beanery\",\n\"beanie\",\n\"beano\",\n\"beant\",\n\"beany\",\n\"bear\",\n\"beard\",\n\"bearded\",\n\"bearder\",\n\"beardie\",\n\"beardom\",\n\"beardy\",\n\"bearer\",\n\"bearess\",\n\"bearing\",\n\"bearish\",\n\"bearlet\",\n\"bearm\",\n\"beast\",\n\"beastie\",\n\"beastly\",\n\"beat\",\n\"beata\",\n\"beatae\",\n\"beatee\",\n\"beaten\",\n\"beater\",\n\"beath\",\n\"beatify\",\n\"beating\",\n\"beatus\",\n\"beau\",\n\"beaufin\",\n\"beauish\",\n\"beauism\",\n\"beauti\",\n\"beauty\",\n\"beaux\",\n\"beaver\",\n\"beavery\",\n\"beback\",\n\"bebait\",\n\"bebang\",\n\"bebar\",\n\"bebaron\",\n\"bebaste\",\n\"bebat\",\n\"bebathe\",\n\"bebay\",\n\"bebeast\",\n\"bebed\",\n\"bebeeru\",\n\"bebilya\",\n\"bebite\",\n\"beblain\",\n\"beblear\",\n\"bebled\",\n\"bebless\",\n\"beblood\",\n\"bebloom\",\n\"bebog\",\n\"bebop\",\n\"beboss\",\n\"bebotch\",\n\"bebrave\",\n\"bebrine\",\n\"bebrush\",\n\"bebump\",\n\"bebusy\",\n\"becall\",\n\"becalm\",\n\"becap\",\n\"becard\",\n\"becarve\",\n\"becater\",\n\"because\",\n\"becense\",\n\"bechalk\",\n\"becharm\",\n\"bechase\",\n\"becheck\",\n\"becher\",\n\"bechern\",\n\"bechirp\",\n\"becivet\",\n\"beck\",\n\"becker\",\n\"becket\",\n\"beckon\",\n\"beclad\",\n\"beclang\",\n\"beclart\",\n\"beclasp\",\n\"beclaw\",\n\"becloak\",\n\"beclog\",\n\"becloud\",\n\"beclout\",\n\"beclown\",\n\"becolme\",\n\"becolor\",\n\"become\",\n\"becomes\",\n\"becomma\",\n\"becoom\",\n\"becost\",\n\"becovet\",\n\"becram\",\n\"becramp\",\n\"becrawl\",\n\"becreep\",\n\"becrime\",\n\"becroak\",\n\"becross\",\n\"becrowd\",\n\"becrown\",\n\"becrush\",\n\"becrust\",\n\"becry\",\n\"becuiba\",\n\"becuna\",\n\"becurl\",\n\"becurry\",\n\"becurse\",\n\"becut\",\n\"bed\",\n\"bedad\",\n\"bedamn\",\n\"bedamp\",\n\"bedare\",\n\"bedark\",\n\"bedash\",\n\"bedaub\",\n\"bedawn\",\n\"beday\",\n\"bedaze\",\n\"bedbug\",\n\"bedcap\",\n\"bedcase\",\n\"bedcord\",\n\"bedded\",\n\"bedder\",\n\"bedding\",\n\"bedead\",\n\"bedeaf\",\n\"bedebt\",\n\"bedeck\",\n\"bedel\",\n\"beden\",\n\"bedene\",\n\"bedevil\",\n\"bedew\",\n\"bedewer\",\n\"bedfast\",\n\"bedfoot\",\n\"bedgery\",\n\"bedgoer\",\n\"bedgown\",\n\"bedight\",\n\"bedikah\",\n\"bedim\",\n\"bedin\",\n\"bedip\",\n\"bedirt\",\n\"bedirty\",\n\"bedizen\",\n\"bedkey\",\n\"bedlam\",\n\"bedlar\",\n\"bedless\",\n\"bedlids\",\n\"bedman\",\n\"bedmate\",\n\"bedog\",\n\"bedolt\",\n\"bedot\",\n\"bedote\",\n\"bedouse\",\n\"bedown\",\n\"bedoyo\",\n\"bedpan\",\n\"bedpost\",\n\"bedrail\",\n\"bedral\",\n\"bedrape\",\n\"bedress\",\n\"bedrid\",\n\"bedrift\",\n\"bedrip\",\n\"bedrock\",\n\"bedroll\",\n\"bedroom\",\n\"bedrop\",\n\"bedrown\",\n\"bedrug\",\n\"bedsick\",\n\"bedside\",\n\"bedsite\",\n\"bedsock\",\n\"bedsore\",\n\"bedtick\",\n\"bedtime\",\n\"bedub\",\n\"beduck\",\n\"beduke\",\n\"bedull\",\n\"bedumb\",\n\"bedunce\",\n\"bedunch\",\n\"bedung\",\n\"bedur\",\n\"bedusk\",\n\"bedust\",\n\"bedwarf\",\n\"bedway\",\n\"bedways\",\n\"bedwell\",\n\"bedye\",\n\"bee\",\n\"beearn\",\n\"beech\",\n\"beechen\",\n\"beechy\",\n\"beedged\",\n\"beedom\",\n\"beef\",\n\"beefer\",\n\"beefily\",\n\"beefin\",\n\"beefish\",\n\"beefy\",\n\"beehead\",\n\"beeherd\",\n\"beehive\",\n\"beeish\",\n\"beek\",\n\"beekite\",\n\"beelbow\",\n\"beelike\",\n\"beeline\",\n\"beelol\",\n\"beeman\",\n\"been\",\n\"beennut\",\n\"beer\",\n\"beerage\",\n\"beerily\",\n\"beerish\",\n\"beery\",\n\"bees\",\n\"beest\",\n\"beeswax\",\n\"beet\",\n\"beeth\",\n\"beetle\",\n\"beetled\",\n\"beetler\",\n\"beety\",\n\"beeve\",\n\"beevish\",\n\"beeware\",\n\"beeway\",\n\"beeweed\",\n\"beewise\",\n\"beewort\",\n\"befall\",\n\"befame\",\n\"befan\",\n\"befancy\",\n\"befavor\",\n\"befilch\",\n\"befile\",\n\"befilth\",\n\"befire\",\n\"befist\",\n\"befit\",\n\"beflag\",\n\"beflap\",\n\"beflea\",\n\"befleck\",\n\"beflour\",\n\"beflout\",\n\"beflum\",\n\"befoam\",\n\"befog\",\n\"befool\",\n\"befop\",\n\"before\",\n\"befoul\",\n\"befret\",\n\"befrill\",\n\"befriz\",\n\"befume\",\n\"beg\",\n\"begad\",\n\"begall\",\n\"begani\",\n\"begar\",\n\"begari\",\n\"begash\",\n\"begat\",\n\"begaud\",\n\"begaudy\",\n\"begay\",\n\"begaze\",\n\"begeck\",\n\"begem\",\n\"beget\",\n\"beggar\",\n\"beggary\",\n\"begging\",\n\"begift\",\n\"begild\",\n\"begin\",\n\"begird\",\n\"beglad\",\n\"beglare\",\n\"beglic\",\n\"beglide\",\n\"begloom\",\n\"begloze\",\n\"begluc\",\n\"beglue\",\n\"begnaw\",\n\"bego\",\n\"begob\",\n\"begobs\",\n\"begohm\",\n\"begone\",\n\"begonia\",\n\"begorra\",\n\"begorry\",\n\"begoud\",\n\"begowk\",\n\"begrace\",\n\"begrain\",\n\"begrave\",\n\"begray\",\n\"begreen\",\n\"begrett\",\n\"begrim\",\n\"begrime\",\n\"begroan\",\n\"begrown\",\n\"beguard\",\n\"beguess\",\n\"beguile\",\n\"beguine\",\n\"begulf\",\n\"begum\",\n\"begun\",\n\"begunk\",\n\"begut\",\n\"behale\",\n\"behalf\",\n\"behap\",\n\"behave\",\n\"behead\",\n\"behear\",\n\"behears\",\n\"behedge\",\n\"beheld\",\n\"behelp\",\n\"behen\",\n\"behenic\",\n\"behest\",\n\"behind\",\n\"behint\",\n\"behn\",\n\"behold\",\n\"behoney\",\n\"behoof\",\n\"behoot\",\n\"behoove\",\n\"behorn\",\n\"behowl\",\n\"behung\",\n\"behymn\",\n\"beice\",\n\"beige\",\n\"being\",\n\"beinked\",\n\"beira\",\n\"beisa\",\n\"bejade\",\n\"bejan\",\n\"bejant\",\n\"bejazz\",\n\"bejel\",\n\"bejewel\",\n\"bejig\",\n\"bekah\",\n\"bekick\",\n\"beking\",\n\"bekiss\",\n\"bekko\",\n\"beknave\",\n\"beknit\",\n\"beknow\",\n\"beknown\",\n\"bel\",\n\"bela\",\n\"belabor\",\n\"belaced\",\n\"beladle\",\n\"belady\",\n\"belage\",\n\"belah\",\n\"belam\",\n\"belanda\",\n\"belar\",\n\"belard\",\n\"belash\",\n\"belate\",\n\"belated\",\n\"belaud\",\n\"belay\",\n\"belayer\",\n\"belch\",\n\"belcher\",\n\"beld\",\n\"beldam\",\n\"beleaf\",\n\"beleap\",\n\"beleave\",\n\"belee\",\n\"belfry\",\n\"belga\",\n\"belibel\",\n\"belick\",\n\"belie\",\n\"belief\",\n\"belier\",\n\"believe\",\n\"belight\",\n\"beliked\",\n\"belion\",\n\"belite\",\n\"belive\",\n\"bell\",\n\"bellboy\",\n\"belle\",\n\"belled\",\n\"bellhop\",\n\"bellied\",\n\"belling\",\n\"bellite\",\n\"bellman\",\n\"bellote\",\n\"bellow\",\n\"bellows\",\n\"belly\",\n\"bellyer\",\n\"beloam\",\n\"beloid\",\n\"belong\",\n\"belonid\",\n\"belord\",\n\"belout\",\n\"belove\",\n\"beloved\",\n\"below\",\n\"belsire\",\n\"belt\",\n\"belted\",\n\"belter\",\n\"beltie\",\n\"beltine\",\n\"belting\",\n\"beltman\",\n\"belton\",\n\"beluga\",\n\"belute\",\n\"belve\",\n\"bely\",\n\"belying\",\n\"bema\",\n\"bemad\",\n\"bemadam\",\n\"bemail\",\n\"bemaim\",\n\"beman\",\n\"bemar\",\n\"bemask\",\n\"bemat\",\n\"bemata\",\n\"bemaul\",\n\"bemazed\",\n\"bemeal\",\n\"bemean\",\n\"bemercy\",\n\"bemire\",\n\"bemist\",\n\"bemix\",\n\"bemoan\",\n\"bemoat\",\n\"bemock\",\n\"bemoil\",\n\"bemole\",\n\"bemolt\",\n\"bemoon\",\n\"bemotto\",\n\"bemoult\",\n\"bemouth\",\n\"bemuck\",\n\"bemud\",\n\"bemuddy\",\n\"bemuse\",\n\"bemused\",\n\"bemusk\",\n\"ben\",\n\"bena\",\n\"benab\",\n\"bename\",\n\"benami\",\n\"benasty\",\n\"benben\",\n\"bench\",\n\"bencher\",\n\"benchy\",\n\"bencite\",\n\"bend\",\n\"benda\",\n\"bended\",\n\"bender\",\n\"bending\",\n\"bendlet\",\n\"bendy\",\n\"bene\",\n\"beneath\",\n\"benefic\",\n\"benefit\",\n\"benempt\",\n\"benet\",\n\"beng\",\n\"beni\",\n\"benight\",\n\"benign\",\n\"benison\",\n\"benj\",\n\"benjy\",\n\"benmost\",\n\"benn\",\n\"benne\",\n\"bennel\",\n\"bennet\",\n\"benny\",\n\"beno\",\n\"benorth\",\n\"benote\",\n\"bensel\",\n\"bensh\",\n\"benshea\",\n\"benshee\",\n\"benshi\",\n\"bent\",\n\"bentang\",\n\"benthal\",\n\"benthic\",\n\"benthon\",\n\"benthos\",\n\"benting\",\n\"benty\",\n\"benumb\",\n\"benward\",\n\"benweed\",\n\"benzal\",\n\"benzein\",\n\"benzene\",\n\"benzil\",\n\"benzine\",\n\"benzo\",\n\"benzoic\",\n\"benzoid\",\n\"benzoin\",\n\"benzol\",\n\"benzole\",\n\"benzoxy\",\n\"benzoyl\",\n\"benzyl\",\n\"beode\",\n\"bepaid\",\n\"bepale\",\n\"bepaper\",\n\"beparch\",\n\"beparse\",\n\"bepart\",\n\"bepaste\",\n\"bepat\",\n\"bepaw\",\n\"bepearl\",\n\"bepelt\",\n\"bepen\",\n\"bepewed\",\n\"bepiece\",\n\"bepile\",\n\"bepill\",\n\"bepinch\",\n\"bepity\",\n\"beprank\",\n\"bepray\",\n\"bepress\",\n\"bepride\",\n\"beprose\",\n\"bepuff\",\n\"bepun\",\n\"bequalm\",\n\"bequest\",\n\"bequote\",\n\"ber\",\n\"berain\",\n\"berakah\",\n\"berake\",\n\"berapt\",\n\"berat\",\n\"berate\",\n\"beray\",\n\"bere\",\n\"bereave\",\n\"bereft\",\n\"berend\",\n\"beret\",\n\"berg\",\n\"berger\",\n\"berglet\",\n\"bergut\",\n\"bergy\",\n\"bergylt\",\n\"berhyme\",\n\"beride\",\n\"berinse\",\n\"berith\",\n\"berley\",\n\"berlin\",\n\"berline\",\n\"berm\",\n\"berne\",\n\"berobed\",\n\"beroll\",\n\"beround\",\n\"berret\",\n\"berri\",\n\"berried\",\n\"berrier\",\n\"berry\",\n\"berseem\",\n\"berserk\",\n\"berth\",\n\"berthed\",\n\"berther\",\n\"bertram\",\n\"bertrum\",\n\"berust\",\n\"bervie\",\n\"berycid\",\n\"beryl\",\n\"bes\",\n\"besa\",\n\"besagne\",\n\"besaiel\",\n\"besaint\",\n\"besan\",\n\"besauce\",\n\"bescab\",\n\"bescarf\",\n\"bescent\",\n\"bescorn\",\n\"bescour\",\n\"bescurf\",\n\"beseam\",\n\"besee\",\n\"beseech\",\n\"beseem\",\n\"beseen\",\n\"beset\",\n\"beshade\",\n\"beshag\",\n\"beshake\",\n\"beshame\",\n\"beshear\",\n\"beshell\",\n\"beshine\",\n\"beshlik\",\n\"beshod\",\n\"beshout\",\n\"beshow\",\n\"beshrew\",\n\"beside\",\n\"besides\",\n\"besiege\",\n\"besigh\",\n\"besin\",\n\"besing\",\n\"besiren\",\n\"besit\",\n\"beslab\",\n\"beslap\",\n\"beslash\",\n\"beslave\",\n\"beslime\",\n\"beslow\",\n\"beslur\",\n\"besmear\",\n\"besmell\",\n\"besmile\",\n\"besmoke\",\n\"besmut\",\n\"besnare\",\n\"besneer\",\n\"besnow\",\n\"besnuff\",\n\"besogne\",\n\"besoil\",\n\"besom\",\n\"besomer\",\n\"besoot\",\n\"besot\",\n\"besoul\",\n\"besour\",\n\"bespate\",\n\"bespawl\",\n\"bespeak\",\n\"besped\",\n\"bespeed\",\n\"bespell\",\n\"bespend\",\n\"bespete\",\n\"bespew\",\n\"bespice\",\n\"bespill\",\n\"bespin\",\n\"bespit\",\n\"besplit\",\n\"bespoke\",\n\"bespot\",\n\"bespout\",\n\"bespray\",\n\"bespy\",\n\"besquib\",\n\"besra\",\n\"best\",\n\"bestab\",\n\"bestain\",\n\"bestamp\",\n\"bestar\",\n\"bestare\",\n\"bestay\",\n\"bestead\",\n\"besteer\",\n\"bester\",\n\"bestial\",\n\"bestick\",\n\"bestill\",\n\"bestink\",\n\"bestir\",\n\"bestock\",\n\"bestore\",\n\"bestorm\",\n\"bestove\",\n\"bestow\",\n\"bestraw\",\n\"bestrew\",\n\"bestuck\",\n\"bestud\",\n\"besugar\",\n\"besuit\",\n\"besully\",\n\"beswarm\",\n\"beswim\",\n\"bet\",\n\"beta\",\n\"betag\",\n\"betail\",\n\"betaine\",\n\"betalk\",\n\"betask\",\n\"betaxed\",\n\"betear\",\n\"beteela\",\n\"beteem\",\n\"betel\",\n\"beth\",\n\"bethel\",\n\"bethink\",\n\"bethumb\",\n\"bethump\",\n\"betide\",\n\"betimes\",\n\"betinge\",\n\"betire\",\n\"betis\",\n\"betitle\",\n\"betoil\",\n\"betoken\",\n\"betone\",\n\"betony\",\n\"betoss\",\n\"betowel\",\n\"betrace\",\n\"betrail\",\n\"betrap\",\n\"betray\",\n\"betread\",\n\"betrend\",\n\"betrim\",\n\"betroth\",\n\"betrunk\",\n\"betso\",\n\"betted\",\n\"better\",\n\"betters\",\n\"betting\",\n\"bettong\",\n\"bettor\",\n\"betty\",\n\"betulin\",\n\"betutor\",\n\"between\",\n\"betwine\",\n\"betwit\",\n\"betwixt\",\n\"beveil\",\n\"bevel\",\n\"beveled\",\n\"beveler\",\n\"bevenom\",\n\"bever\",\n\"beverse\",\n\"beveto\",\n\"bevined\",\n\"bevomit\",\n\"bevue\",\n\"bevy\",\n\"bewail\",\n\"bewall\",\n\"beware\",\n\"bewash\",\n\"bewaste\",\n\"bewater\",\n\"beweary\",\n\"beweep\",\n\"bewept\",\n\"bewest\",\n\"bewet\",\n\"bewhig\",\n\"bewhite\",\n\"bewidow\",\n\"bewig\",\n\"bewired\",\n\"bewitch\",\n\"bewith\",\n\"bework\",\n\"beworm\",\n\"beworn\",\n\"beworry\",\n\"bewrap\",\n\"bewray\",\n\"bewreck\",\n\"bewrite\",\n\"bey\",\n\"beydom\",\n\"beylic\",\n\"beyond\",\n\"beyship\",\n\"bezant\",\n\"bezanty\",\n\"bezel\",\n\"bezetta\",\n\"bezique\",\n\"bezoar\",\n\"bezzi\",\n\"bezzle\",\n\"bezzo\",\n\"bhabar\",\n\"bhakta\",\n\"bhakti\",\n\"bhalu\",\n\"bhandar\",\n\"bhang\",\n\"bhangi\",\n\"bhara\",\n\"bharal\",\n\"bhat\",\n\"bhava\",\n\"bheesty\",\n\"bhikku\",\n\"bhikshu\",\n\"bhoosa\",\n\"bhoy\",\n\"bhungi\",\n\"bhut\",\n\"biabo\",\n\"biacid\",\n\"biacuru\",\n\"bialate\",\n\"biallyl\",\n\"bianco\",\n\"biarchy\",\n\"bias\",\n\"biaxal\",\n\"biaxial\",\n\"bib\",\n\"bibasic\",\n\"bibb\",\n\"bibber\",\n\"bibble\",\n\"bibbler\",\n\"bibbons\",\n\"bibcock\",\n\"bibi\",\n\"bibiri\",\n\"bibless\",\n\"biblus\",\n\"bice\",\n\"biceps\",\n\"bicetyl\",\n\"bichir\",\n\"bichord\",\n\"bichy\",\n\"bick\",\n\"bicker\",\n\"bickern\",\n\"bicolor\",\n\"bicone\",\n\"biconic\",\n\"bicorn\",\n\"bicorne\",\n\"bicron\",\n\"bicycle\",\n\"bicyclo\",\n\"bid\",\n\"bidar\",\n\"bidarka\",\n\"bidcock\",\n\"bidder\",\n\"bidding\",\n\"biddy\",\n\"bide\",\n\"bident\",\n\"bider\",\n\"bidet\",\n\"biding\",\n\"bidri\",\n\"biduous\",\n\"bield\",\n\"bieldy\",\n\"bien\",\n\"bienly\",\n\"biennia\",\n\"bier\",\n\"bietle\",\n\"bifara\",\n\"bifer\",\n\"biff\",\n\"biffin\",\n\"bifid\",\n\"bifidly\",\n\"bifilar\",\n\"biflex\",\n\"bifocal\",\n\"bifoil\",\n\"bifold\",\n\"bifolia\",\n\"biform\",\n\"bifront\",\n\"big\",\n\"biga\",\n\"bigamic\",\n\"bigamy\",\n\"bigener\",\n\"bigeye\",\n\"bigg\",\n\"biggah\",\n\"biggen\",\n\"bigger\",\n\"biggest\",\n\"biggin\",\n\"biggish\",\n\"bigha\",\n\"bighead\",\n\"bighorn\",\n\"bight\",\n\"biglot\",\n\"bigness\",\n\"bignou\",\n\"bigot\",\n\"bigoted\",\n\"bigotry\",\n\"bigotty\",\n\"bigroot\",\n\"bigwig\",\n\"bija\",\n\"bijasal\",\n\"bijou\",\n\"bijoux\",\n\"bike\",\n\"bikh\",\n\"bikini\",\n\"bilabe\",\n\"bilalo\",\n\"bilbie\",\n\"bilbo\",\n\"bilby\",\n\"bilch\",\n\"bilcock\",\n\"bildar\",\n\"bilders\",\n\"bile\",\n\"bilge\",\n\"bilgy\",\n\"biliary\",\n\"biliate\",\n\"bilic\",\n\"bilify\",\n\"bilimbi\",\n\"bilio\",\n\"bilious\",\n\"bilith\",\n\"bilk\",\n\"bilker\",\n\"bill\",\n\"billa\",\n\"billbug\",\n\"billed\",\n\"biller\",\n\"billet\",\n\"billety\",\n\"billian\",\n\"billing\",\n\"billion\",\n\"billman\",\n\"billon\",\n\"billot\",\n\"billow\",\n\"billowy\",\n\"billy\",\n\"billyer\",\n\"bilo\",\n\"bilobe\",\n\"bilobed\",\n\"bilsh\",\n\"bilsted\",\n\"biltong\",\n\"bimalar\",\n\"bimanal\",\n\"bimane\",\n\"bimasty\",\n\"bimbil\",\n\"bimeby\",\n\"bimodal\",\n\"bin\",\n\"binal\",\n\"binary\",\n\"binate\",\n\"bind\",\n\"binder\",\n\"bindery\",\n\"binding\",\n\"bindle\",\n\"bindlet\",\n\"bindweb\",\n\"bine\",\n\"bing\",\n\"binge\",\n\"bingey\",\n\"binghi\",\n\"bingle\",\n\"bingo\",\n\"bingy\",\n\"binh\",\n\"bink\",\n\"binman\",\n\"binna\",\n\"binning\",\n\"binnite\",\n\"bino\",\n\"binocle\",\n\"binodal\",\n\"binode\",\n\"binotic\",\n\"binous\",\n\"bint\",\n\"binukau\",\n\"biod\",\n\"biodyne\",\n\"biogen\",\n\"biogeny\",\n\"bioherm\",\n\"biolith\",\n\"biology\",\n\"biome\",\n\"bion\",\n\"bionomy\",\n\"biopsic\",\n\"biopsy\",\n\"bioral\",\n\"biorgan\",\n\"bios\",\n\"biose\",\n\"biosis\",\n\"biota\",\n\"biotaxy\",\n\"biotic\",\n\"biotics\",\n\"biotin\",\n\"biotite\",\n\"biotome\",\n\"biotomy\",\n\"biotope\",\n\"biotype\",\n\"bioxide\",\n\"bipack\",\n\"biparty\",\n\"biped\",\n\"bipedal\",\n\"biphase\",\n\"biplane\",\n\"bipod\",\n\"bipolar\",\n\"biprism\",\n\"biprong\",\n\"birch\",\n\"birchen\",\n\"bird\",\n\"birddom\",\n\"birdeen\",\n\"birder\",\n\"birdie\",\n\"birding\",\n\"birdlet\",\n\"birdman\",\n\"birdy\",\n\"bireme\",\n\"biretta\",\n\"biri\",\n\"biriba\",\n\"birk\",\n\"birken\",\n\"birkie\",\n\"birl\",\n\"birle\",\n\"birler\",\n\"birlie\",\n\"birlinn\",\n\"birma\",\n\"birn\",\n\"birny\",\n\"birr\",\n\"birse\",\n\"birsle\",\n\"birsy\",\n\"birth\",\n\"birthy\",\n\"bis\",\n\"bisabol\",\n\"bisalt\",\n\"biscuit\",\n\"bisect\",\n\"bisexed\",\n\"bisext\",\n\"bishop\",\n\"bismar\",\n\"bismite\",\n\"bismuth\",\n\"bisnaga\",\n\"bison\",\n\"bispore\",\n\"bisque\",\n\"bissext\",\n\"bisson\",\n\"bistate\",\n\"bister\",\n\"bisti\",\n\"bistort\",\n\"bistro\",\n\"bit\",\n\"bitable\",\n\"bitch\",\n\"bite\",\n\"biter\",\n\"biti\",\n\"biting\",\n\"bitless\",\n\"bito\",\n\"bitolyl\",\n\"bitt\",\n\"bitted\",\n\"bitten\",\n\"bitter\",\n\"bittern\",\n\"bitters\",\n\"bittie\",\n\"bittock\",\n\"bitty\",\n\"bitume\",\n\"bitumed\",\n\"bitumen\",\n\"bitwise\",\n\"bityite\",\n\"bitypic\",\n\"biune\",\n\"biunial\",\n\"biunity\",\n\"biurate\",\n\"biurea\",\n\"biuret\",\n\"bivalve\",\n\"bivinyl\",\n\"bivious\",\n\"bivocal\",\n\"bivouac\",\n\"biwa\",\n\"bixin\",\n\"biz\",\n\"bizarre\",\n\"bizet\",\n\"bizonal\",\n\"bizone\",\n\"bizz\",\n\"blab\",\n\"blabber\",\n\"black\",\n\"blacken\",\n\"blacker\",\n\"blackey\",\n\"blackie\",\n\"blackit\",\n\"blackly\",\n\"blacky\",\n\"blad\",\n\"bladder\",\n\"blade\",\n\"bladed\",\n\"blader\",\n\"blading\",\n\"bladish\",\n\"blady\",\n\"blae\",\n\"blaff\",\n\"blaflum\",\n\"blah\",\n\"blain\",\n\"blair\",\n\"blake\",\n\"blame\",\n\"blamed\",\n\"blamer\",\n\"blaming\",\n\"blan\",\n\"blanc\",\n\"blanca\",\n\"blanch\",\n\"blanco\",\n\"bland\",\n\"blanda\",\n\"blandly\",\n\"blank\",\n\"blanked\",\n\"blanket\",\n\"blankly\",\n\"blanky\",\n\"blanque\",\n\"blare\",\n\"blarney\",\n\"blarnid\",\n\"blarny\",\n\"blart\",\n\"blas\",\n\"blase\",\n\"blash\",\n\"blashy\",\n\"blast\",\n\"blasted\",\n\"blaster\",\n\"blastid\",\n\"blastie\",\n\"blasty\",\n\"blat\",\n\"blatant\",\n\"blate\",\n\"blately\",\n\"blather\",\n\"blatta\",\n\"blatter\",\n\"blatti\",\n\"blattid\",\n\"blaubok\",\n\"blaver\",\n\"blaw\",\n\"blawort\",\n\"blay\",\n\"blaze\",\n\"blazer\",\n\"blazing\",\n\"blazon\",\n\"blazy\",\n\"bleach\",\n\"bleak\",\n\"bleakly\",\n\"bleaky\",\n\"blear\",\n\"bleared\",\n\"bleary\",\n\"bleat\",\n\"bleater\",\n\"bleaty\",\n\"bleb\",\n\"blebby\",\n\"bleck\",\n\"blee\",\n\"bleed\",\n\"bleeder\",\n\"bleery\",\n\"bleeze\",\n\"bleezy\",\n\"blellum\",\n\"blemish\",\n\"blench\",\n\"blend\",\n\"blende\",\n\"blended\",\n\"blender\",\n\"blendor\",\n\"blenny\",\n\"blent\",\n\"bleo\",\n\"blesbok\",\n\"bless\",\n\"blessed\",\n\"blesser\",\n\"blest\",\n\"blet\",\n\"blewits\",\n\"blibe\",\n\"blick\",\n\"blickey\",\n\"blight\",\n\"blighty\",\n\"blimp\",\n\"blimy\",\n\"blind\",\n\"blinded\",\n\"blinder\",\n\"blindly\",\n\"blink\",\n\"blinked\",\n\"blinker\",\n\"blinks\",\n\"blinky\",\n\"blinter\",\n\"blintze\",\n\"blip\",\n\"bliss\",\n\"blissom\",\n\"blister\",\n\"blite\",\n\"blithe\",\n\"blithen\",\n\"blither\",\n\"blitter\",\n\"blitz\",\n\"blizz\",\n\"blo\",\n\"bloat\",\n\"bloated\",\n\"bloater\",\n\"blob\",\n\"blobbed\",\n\"blobber\",\n\"blobby\",\n\"bloc\",\n\"block\",\n\"blocked\",\n\"blocker\",\n\"blocky\",\n\"blodite\",\n\"bloke\",\n\"blolly\",\n\"blonde\",\n\"blood\",\n\"blooded\",\n\"bloody\",\n\"blooey\",\n\"bloom\",\n\"bloomer\",\n\"bloomy\",\n\"bloop\",\n\"blooper\",\n\"blore\",\n\"blosmy\",\n\"blossom\",\n\"blot\",\n\"blotch\",\n\"blotchy\",\n\"blotter\",\n\"blotto\",\n\"blotty\",\n\"blouse\",\n\"bloused\",\n\"blout\",\n\"blow\",\n\"blowen\",\n\"blower\",\n\"blowfly\",\n\"blowgun\",\n\"blowing\",\n\"blown\",\n\"blowoff\",\n\"blowout\",\n\"blowth\",\n\"blowup\",\n\"blowy\",\n\"blowze\",\n\"blowzed\",\n\"blowzy\",\n\"blub\",\n\"blubber\",\n\"blucher\",\n\"blue\",\n\"bluecap\",\n\"bluecup\",\n\"blueing\",\n\"blueleg\",\n\"bluely\",\n\"bluer\",\n\"blues\",\n\"bluet\",\n\"bluetop\",\n\"bluey\",\n\"bluff\",\n\"bluffer\",\n\"bluffly\",\n\"bluffy\",\n\"bluggy\",\n\"bluing\",\n\"bluish\",\n\"bluism\",\n\"blunder\",\n\"blunge\",\n\"blunger\",\n\"blunk\",\n\"blunker\",\n\"blunks\",\n\"blunnen\",\n\"blunt\",\n\"blunter\",\n\"bluntie\",\n\"bluntly\",\n\"blup\",\n\"blur\",\n\"blurb\",\n\"blurred\",\n\"blurrer\",\n\"blurry\",\n\"blurt\",\n\"blush\",\n\"blusher\",\n\"blushy\",\n\"bluster\",\n\"blype\",\n\"bo\",\n\"boa\",\n\"boagane\",\n\"boar\",\n\"board\",\n\"boarder\",\n\"boardly\",\n\"boardy\",\n\"boarish\",\n\"boast\",\n\"boaster\",\n\"boat\",\n\"boatage\",\n\"boater\",\n\"boatful\",\n\"boatie\",\n\"boating\",\n\"boatlip\",\n\"boatly\",\n\"boatman\",\n\"bob\",\n\"boba\",\n\"bobac\",\n\"bobbed\",\n\"bobber\",\n\"bobbery\",\n\"bobbin\",\n\"bobbing\",\n\"bobbish\",\n\"bobble\",\n\"bobby\",\n\"bobcat\",\n\"bobcoat\",\n\"bobeche\",\n\"bobfly\",\n\"bobo\",\n\"bobotie\",\n\"bobsled\",\n\"bobstay\",\n\"bobtail\",\n\"bobwood\",\n\"bocal\",\n\"bocardo\",\n\"bocca\",\n\"boccale\",\n\"boccaro\",\n\"bocce\",\n\"boce\",\n\"bocher\",\n\"bock\",\n\"bocking\",\n\"bocoy\",\n\"bod\",\n\"bodach\",\n\"bode\",\n\"bodeful\",\n\"bodega\",\n\"boden\",\n\"boder\",\n\"bodge\",\n\"bodger\",\n\"bodgery\",\n\"bodhi\",\n\"bodice\",\n\"bodiced\",\n\"bodied\",\n\"bodier\",\n\"bodikin\",\n\"bodily\",\n\"boding\",\n\"bodkin\",\n\"bodle\",\n\"bodock\",\n\"body\",\n\"bog\",\n\"boga\",\n\"bogan\",\n\"bogard\",\n\"bogart\",\n\"bogey\",\n\"boggart\",\n\"boggin\",\n\"boggish\",\n\"boggle\",\n\"boggler\",\n\"boggy\",\n\"boghole\",\n\"bogie\",\n\"bogier\",\n\"bogland\",\n\"bogle\",\n\"boglet\",\n\"bogman\",\n\"bogmire\",\n\"bogo\",\n\"bogong\",\n\"bogtrot\",\n\"bogue\",\n\"bogum\",\n\"bogus\",\n\"bogway\",\n\"bogwood\",\n\"bogwort\",\n\"bogy\",\n\"bogydom\",\n\"bogyism\",\n\"bohawn\",\n\"bohea\",\n\"boho\",\n\"bohor\",\n\"bohunk\",\n\"boid\",\n\"boil\",\n\"boiled\",\n\"boiler\",\n\"boilery\",\n\"boiling\",\n\"boily\",\n\"boist\",\n\"bojite\",\n\"bojo\",\n\"bokadam\",\n\"bokard\",\n\"bokark\",\n\"boke\",\n\"bokom\",\n\"bola\",\n\"bolar\",\n\"bold\",\n\"bolden\",\n\"boldine\",\n\"boldly\",\n\"boldo\",\n\"bole\",\n\"boled\",\n\"boleite\",\n\"bolero\",\n\"bolete\",\n\"bolide\",\n\"bolimba\",\n\"bolis\",\n\"bolivar\",\n\"bolivia\",\n\"bolk\",\n\"boll\",\n\"bollard\",\n\"bolled\",\n\"boller\",\n\"bolling\",\n\"bollock\",\n\"bolly\",\n\"bolo\",\n\"boloman\",\n\"boloney\",\n\"bolson\",\n\"bolster\",\n\"bolt\",\n\"boltage\",\n\"boltant\",\n\"boltel\",\n\"bolter\",\n\"bolti\",\n\"bolting\",\n\"bolus\",\n\"bom\",\n\"boma\",\n\"bomb\",\n\"bombard\",\n\"bombast\",\n\"bombed\",\n\"bomber\",\n\"bombo\",\n\"bombola\",\n\"bombous\",\n\"bon\",\n\"bonaci\",\n\"bonagh\",\n\"bonaght\",\n\"bonair\",\n\"bonally\",\n\"bonang\",\n\"bonanza\",\n\"bonasus\",\n\"bonbon\",\n\"bonce\",\n\"bond\",\n\"bondage\",\n\"bondar\",\n\"bonded\",\n\"bonder\",\n\"bonding\",\n\"bondman\",\n\"bonduc\",\n\"bone\",\n\"boned\",\n\"bonedog\",\n\"bonelet\",\n\"boner\",\n\"boneset\",\n\"bonfire\",\n\"bong\",\n\"bongo\",\n\"boniata\",\n\"bonify\",\n\"bonito\",\n\"bonk\",\n\"bonnaz\",\n\"bonnet\",\n\"bonnily\",\n\"bonny\",\n\"bonsai\",\n\"bonus\",\n\"bonxie\",\n\"bony\",\n\"bonze\",\n\"bonzer\",\n\"bonzery\",\n\"bonzian\",\n\"boo\",\n\"boob\",\n\"boobery\",\n\"boobily\",\n\"boobook\",\n\"booby\",\n\"bood\",\n\"boodie\",\n\"boodle\",\n\"boodler\",\n\"boody\",\n\"boof\",\n\"booger\",\n\"boohoo\",\n\"boojum\",\n\"book\",\n\"bookdom\",\n\"booked\",\n\"booker\",\n\"bookery\",\n\"bookful\",\n\"bookie\",\n\"booking\",\n\"bookish\",\n\"bookism\",\n\"booklet\",\n\"bookman\",\n\"booky\",\n\"bool\",\n\"booly\",\n\"boolya\",\n\"boom\",\n\"boomage\",\n\"boomah\",\n\"boomdas\",\n\"boomer\",\n\"booming\",\n\"boomlet\",\n\"boomy\",\n\"boon\",\n\"boonk\",\n\"boopis\",\n\"boor\",\n\"boorish\",\n\"boort\",\n\"boose\",\n\"boost\",\n\"booster\",\n\"boosy\",\n\"boot\",\n\"bootboy\",\n\"booted\",\n\"bootee\",\n\"booter\",\n\"bootery\",\n\"bootful\",\n\"booth\",\n\"boother\",\n\"bootied\",\n\"booting\",\n\"bootleg\",\n\"boots\",\n\"booty\",\n\"booze\",\n\"boozed\",\n\"boozer\",\n\"boozily\",\n\"boozy\",\n\"bop\",\n\"bopeep\",\n\"boppist\",\n\"bopyrid\",\n\"bor\",\n\"bora\",\n\"borable\",\n\"boracic\",\n\"borage\",\n\"borak\",\n\"boral\",\n\"borasca\",\n\"borate\",\n\"borax\",\n\"bord\",\n\"bordage\",\n\"bordar\",\n\"bordel\",\n\"border\",\n\"bordure\",\n\"bore\",\n\"boread\",\n\"boreal\",\n\"borean\",\n\"boredom\",\n\"boree\",\n\"boreen\",\n\"boregat\",\n\"boreism\",\n\"borele\",\n\"borer\",\n\"borg\",\n\"borgh\",\n\"borh\",\n\"boric\",\n\"boride\",\n\"borine\",\n\"boring\",\n\"borish\",\n\"borism\",\n\"bority\",\n\"borize\",\n\"borlase\",\n\"born\",\n\"borne\",\n\"borneol\",\n\"borning\",\n\"bornite\",\n\"bornyl\",\n\"boro\",\n\"boron\",\n\"boronic\",\n\"borough\",\n\"borrel\",\n\"borrow\",\n\"borsch\",\n\"borscht\",\n\"borsht\",\n\"bort\",\n\"bortsch\",\n\"borty\",\n\"bortz\",\n\"borwort\",\n\"boryl\",\n\"borzoi\",\n\"boscage\",\n\"bosch\",\n\"bose\",\n\"boser\",\n\"bosh\",\n\"bosher\",\n\"bosk\",\n\"bosker\",\n\"bosket\",\n\"bosky\",\n\"bosn\",\n\"bosom\",\n\"bosomed\",\n\"bosomer\",\n\"bosomy\",\n\"boss\",\n\"bossage\",\n\"bossdom\",\n\"bossed\",\n\"bosser\",\n\"bosset\",\n\"bossing\",\n\"bossism\",\n\"bosslet\",\n\"bossy\",\n\"boston\",\n\"bostryx\",\n\"bosun\",\n\"bot\",\n\"bota\",\n\"botanic\",\n\"botany\",\n\"botargo\",\n\"botch\",\n\"botched\",\n\"botcher\",\n\"botchka\",\n\"botchy\",\n\"bote\",\n\"botella\",\n\"boterol\",\n\"botfly\",\n\"both\",\n\"bother\",\n\"bothros\",\n\"bothway\",\n\"bothy\",\n\"botonee\",\n\"botong\",\n\"bott\",\n\"bottine\",\n\"bottle\",\n\"bottled\",\n\"bottler\",\n\"bottom\",\n\"botulin\",\n\"bouchal\",\n\"bouche\",\n\"boucher\",\n\"boud\",\n\"boudoir\",\n\"bougar\",\n\"bouge\",\n\"bouget\",\n\"bough\",\n\"boughed\",\n\"bought\",\n\"boughy\",\n\"bougie\",\n\"bouk\",\n\"boukit\",\n\"boulder\",\n\"boule\",\n\"boultel\",\n\"boulter\",\n\"boun\",\n\"bounce\",\n\"bouncer\",\n\"bound\",\n\"bounded\",\n\"bounden\",\n\"bounder\",\n\"boundly\",\n\"bounty\",\n\"bouquet\",\n\"bourbon\",\n\"bourd\",\n\"bourder\",\n\"bourdon\",\n\"bourg\",\n\"bourn\",\n\"bourock\",\n\"bourse\",\n\"bouse\",\n\"bouser\",\n\"bousy\",\n\"bout\",\n\"boutade\",\n\"bouto\",\n\"bouw\",\n\"bovate\",\n\"bovid\",\n\"bovine\",\n\"bovoid\",\n\"bow\",\n\"bowable\",\n\"bowback\",\n\"bowbent\",\n\"bowboy\",\n\"bowed\",\n\"bowel\",\n\"boweled\",\n\"bowels\",\n\"bower\",\n\"bowery\",\n\"bowet\",\n\"bowfin\",\n\"bowhead\",\n\"bowie\",\n\"bowing\",\n\"bowk\",\n\"bowkail\",\n\"bowker\",\n\"bowknot\",\n\"bowl\",\n\"bowla\",\n\"bowleg\",\n\"bowler\",\n\"bowless\",\n\"bowlful\",\n\"bowlike\",\n\"bowline\",\n\"bowling\",\n\"bowls\",\n\"bowly\",\n\"bowman\",\n\"bowpin\",\n\"bowshot\",\n\"bowwood\",\n\"bowwort\",\n\"bowwow\",\n\"bowyer\",\n\"boxbush\",\n\"boxcar\",\n\"boxen\",\n\"boxer\",\n\"boxfish\",\n\"boxful\",\n\"boxhaul\",\n\"boxhead\",\n\"boxing\",\n\"boxlike\",\n\"boxman\",\n\"boxty\",\n\"boxwood\",\n\"boxwork\",\n\"boxy\",\n\"boy\",\n\"boyang\",\n\"boyar\",\n\"boyard\",\n\"boycott\",\n\"boydom\",\n\"boyer\",\n\"boyhood\",\n\"boyish\",\n\"boyism\",\n\"boyla\",\n\"boylike\",\n\"boyship\",\n\"boza\",\n\"bozal\",\n\"bozo\",\n\"bozze\",\n\"bra\",\n\"brab\",\n\"brabant\",\n\"brabble\",\n\"braca\",\n\"braccia\",\n\"braccio\",\n\"brace\",\n\"braced\",\n\"bracer\",\n\"bracero\",\n\"braces\",\n\"brach\",\n\"brachet\",\n\"bracing\",\n\"brack\",\n\"bracken\",\n\"bracker\",\n\"bracket\",\n\"bracky\",\n\"bract\",\n\"bractea\",\n\"bracted\",\n\"brad\",\n\"bradawl\",\n\"bradsot\",\n\"brae\",\n\"braeman\",\n\"brag\",\n\"braggat\",\n\"bragger\",\n\"bragget\",\n\"bragite\",\n\"braid\",\n\"braided\",\n\"braider\",\n\"brail\",\n\"brain\",\n\"brainer\",\n\"brainge\",\n\"brains\",\n\"brainy\",\n\"braird\",\n\"brairo\",\n\"braise\",\n\"brake\",\n\"braker\",\n\"brakie\",\n\"braky\",\n\"bramble\",\n\"brambly\",\n\"bran\",\n\"branch\",\n\"branchi\",\n\"branchy\",\n\"brand\",\n\"branded\",\n\"brander\",\n\"brandy\",\n\"brangle\",\n\"branial\",\n\"brank\",\n\"brankie\",\n\"branle\",\n\"branner\",\n\"branny\",\n\"bransle\",\n\"brant\",\n\"brash\",\n\"brashy\",\n\"brasque\",\n\"brass\",\n\"brasse\",\n\"brasser\",\n\"brasset\",\n\"brassic\",\n\"brassie\",\n\"brassy\",\n\"brat\",\n\"brattie\",\n\"brattle\",\n\"brauna\",\n\"bravade\",\n\"bravado\",\n\"brave\",\n\"bravely\",\n\"braver\",\n\"bravery\",\n\"braving\",\n\"bravish\",\n\"bravo\",\n\"bravura\",\n\"braw\",\n\"brawl\",\n\"brawler\",\n\"brawly\",\n\"brawlys\",\n\"brawn\",\n\"brawned\",\n\"brawner\",\n\"brawny\",\n\"braws\",\n\"braxy\",\n\"bray\",\n\"brayer\",\n\"brayera\",\n\"braza\",\n\"braze\",\n\"brazen\",\n\"brazer\",\n\"brazera\",\n\"brazier\",\n\"brazil\",\n\"breach\",\n\"breachy\",\n\"bread\",\n\"breaden\",\n\"breadth\",\n\"breaghe\",\n\"break\",\n\"breakax\",\n\"breaker\",\n\"breakup\",\n\"bream\",\n\"breards\",\n\"breast\",\n\"breath\",\n\"breathe\",\n\"breathy\",\n\"breba\",\n\"breccia\",\n\"brecham\",\n\"breck\",\n\"brecken\",\n\"bred\",\n\"brede\",\n\"bredi\",\n\"bree\",\n\"breech\",\n\"breed\",\n\"breeder\",\n\"breedy\",\n\"breek\",\n\"breeze\",\n\"breezy\",\n\"bregma\",\n\"brehon\",\n\"brei\",\n\"brekkle\",\n\"brelaw\",\n\"breme\",\n\"bremely\",\n\"brent\",\n\"brephic\",\n\"bret\",\n\"breth\",\n\"brett\",\n\"breva\",\n\"breve\",\n\"brevet\",\n\"brevier\",\n\"brevit\",\n\"brevity\",\n\"brew\",\n\"brewage\",\n\"brewer\",\n\"brewery\",\n\"brewing\",\n\"brewis\",\n\"brewst\",\n\"brey\",\n\"briar\",\n\"bribe\",\n\"bribee\",\n\"briber\",\n\"bribery\",\n\"brichen\",\n\"brick\",\n\"brickel\",\n\"bricken\",\n\"brickle\",\n\"brickly\",\n\"bricky\",\n\"bricole\",\n\"bridal\",\n\"bridale\",\n\"bride\",\n\"bridely\",\n\"bridge\",\n\"bridged\",\n\"bridger\",\n\"bridle\",\n\"bridled\",\n\"bridler\",\n\"bridoon\",\n\"brief\",\n\"briefly\",\n\"briefs\",\n\"brier\",\n\"briered\",\n\"briery\",\n\"brieve\",\n\"brig\",\n\"brigade\",\n\"brigand\",\n\"bright\",\n\"brill\",\n\"brills\",\n\"brim\",\n\"brimful\",\n\"briming\",\n\"brimmed\",\n\"brimmer\",\n\"brin\",\n\"brine\",\n\"briner\",\n\"bring\",\n\"bringal\",\n\"bringer\",\n\"brinish\",\n\"brinjal\",\n\"brink\",\n\"briny\",\n\"brioche\",\n\"brique\",\n\"brisk\",\n\"brisken\",\n\"brisket\",\n\"briskly\",\n\"brisque\",\n\"briss\",\n\"bristle\",\n\"bristly\",\n\"brisure\",\n\"brit\",\n\"brith\",\n\"brither\",\n\"britska\",\n\"britten\",\n\"brittle\",\n\"brizz\",\n\"broach\",\n\"broad\",\n\"broadax\",\n\"broaden\",\n\"broadly\",\n\"brob\",\n\"brocade\",\n\"brocard\",\n\"broch\",\n\"brochan\",\n\"broche\",\n\"brocho\",\n\"brock\",\n\"brocked\",\n\"brocket\",\n\"brockle\",\n\"brod\",\n\"brodder\",\n\"brog\",\n\"brogan\",\n\"brogger\",\n\"broggle\",\n\"brogue\",\n\"broguer\",\n\"broider\",\n\"broigne\",\n\"broil\",\n\"broiler\",\n\"brokage\",\n\"broke\",\n\"broken\",\n\"broker\",\n\"broking\",\n\"brolga\",\n\"broll\",\n\"brolly\",\n\"broma\",\n\"bromal\",\n\"bromate\",\n\"brome\",\n\"bromic\",\n\"bromide\",\n\"bromine\",\n\"bromism\",\n\"bromite\",\n\"bromize\",\n\"bromoil\",\n\"bromol\",\n\"bromous\",\n\"bronc\",\n\"bronchi\",\n\"bronco\",\n\"bronk\",\n\"bronze\",\n\"bronzed\",\n\"bronzen\",\n\"bronzer\",\n\"bronzy\",\n\"broo\",\n\"brooch\",\n\"brood\",\n\"brooder\",\n\"broody\",\n\"brook\",\n\"brooked\",\n\"brookie\",\n\"brooky\",\n\"brool\",\n\"broom\",\n\"broomer\",\n\"broomy\",\n\"broon\",\n\"broose\",\n\"brose\",\n\"brosot\",\n\"brosy\",\n\"brot\",\n\"brotan\",\n\"brotany\",\n\"broth\",\n\"brothel\",\n\"brother\",\n\"brothy\",\n\"brough\",\n\"brought\",\n\"brow\",\n\"browden\",\n\"browed\",\n\"browis\",\n\"browman\",\n\"brown\",\n\"browner\",\n\"brownie\",\n\"brownly\",\n\"browny\",\n\"browse\",\n\"browser\",\n\"browst\",\n\"bruang\",\n\"brucia\",\n\"brucina\",\n\"brucine\",\n\"brucite\",\n\"bruckle\",\n\"brugh\",\n\"bruin\",\n\"bruise\",\n\"bruiser\",\n\"bruit\",\n\"bruiter\",\n\"bruke\",\n\"brulee\",\n\"brulyie\",\n\"brumal\",\n\"brumby\",\n\"brume\",\n\"brumous\",\n\"brunch\",\n\"brunet\",\n\"brunt\",\n\"bruscus\",\n\"brush\",\n\"brushed\",\n\"brusher\",\n\"brushes\",\n\"brushet\",\n\"brushy\",\n\"brusque\",\n\"brustle\",\n\"brut\",\n\"brutage\",\n\"brutal\",\n\"brute\",\n\"brutely\",\n\"brutify\",\n\"bruting\",\n\"brutish\",\n\"brutism\",\n\"brutter\",\n\"bruzz\",\n\"bryonin\",\n\"bryony\",\n\"bu\",\n\"bual\",\n\"buaze\",\n\"bub\",\n\"buba\",\n\"bubal\",\n\"bubalis\",\n\"bubble\",\n\"bubbler\",\n\"bubbly\",\n\"bubby\",\n\"bubinga\",\n\"bubo\",\n\"buboed\",\n\"bubonic\",\n\"bubukle\",\n\"bucare\",\n\"bucca\",\n\"buccal\",\n\"buccan\",\n\"buccate\",\n\"buccina\",\n\"buccula\",\n\"buchite\",\n\"buchu\",\n\"buck\",\n\"bucked\",\n\"buckeen\",\n\"bucker\",\n\"bucket\",\n\"buckety\",\n\"buckeye\",\n\"buckie\",\n\"bucking\",\n\"buckish\",\n\"buckle\",\n\"buckled\",\n\"buckler\",\n\"bucklum\",\n\"bucko\",\n\"buckpot\",\n\"buckra\",\n\"buckram\",\n\"bucksaw\",\n\"bucky\",\n\"bucolic\",\n\"bucrane\",\n\"bud\",\n\"buda\",\n\"buddage\",\n\"budder\",\n\"buddhi\",\n\"budding\",\n\"buddle\",\n\"buddler\",\n\"buddy\",\n\"budge\",\n\"budger\",\n\"budget\",\n\"budless\",\n\"budlet\",\n\"budlike\",\n\"budmash\",\n\"budtime\",\n\"budwood\",\n\"budworm\",\n\"budzat\",\n\"bufagin\",\n\"buff\",\n\"buffalo\",\n\"buffed\",\n\"buffer\",\n\"buffet\",\n\"buffing\",\n\"buffle\",\n\"buffont\",\n\"buffoon\",\n\"buffy\",\n\"bufidin\",\n\"bufo\",\n\"bug\",\n\"bugaboo\",\n\"bugan\",\n\"bugbane\",\n\"bugbear\",\n\"bugbite\",\n\"bugdom\",\n\"bugfish\",\n\"bugger\",\n\"buggery\",\n\"buggy\",\n\"bughead\",\n\"bugle\",\n\"bugled\",\n\"bugler\",\n\"buglet\",\n\"bugloss\",\n\"bugre\",\n\"bugseed\",\n\"bugweed\",\n\"bugwort\",\n\"buhl\",\n\"buhr\",\n\"build\",\n\"builder\",\n\"buildup\",\n\"built\",\n\"buirdly\",\n\"buisson\",\n\"buist\",\n\"bukh\",\n\"bukshi\",\n\"bulak\",\n\"bulb\",\n\"bulbar\",\n\"bulbed\",\n\"bulbil\",\n\"bulblet\",\n\"bulbose\",\n\"bulbous\",\n\"bulbul\",\n\"bulbule\",\n\"bulby\",\n\"bulchin\",\n\"bulge\",\n\"bulger\",\n\"bulgy\",\n\"bulimia\",\n\"bulimic\",\n\"bulimy\",\n\"bulk\",\n\"bulked\",\n\"bulker\",\n\"bulkily\",\n\"bulkish\",\n\"bulky\",\n\"bull\",\n\"bulla\",\n\"bullace\",\n\"bullan\",\n\"bullary\",\n\"bullate\",\n\"bullbat\",\n\"bulldog\",\n\"buller\",\n\"bullet\",\n\"bullety\",\n\"bulling\",\n\"bullion\",\n\"bullish\",\n\"bullism\",\n\"bullit\",\n\"bullnut\",\n\"bullock\",\n\"bullous\",\n\"bullule\",\n\"bully\",\n\"bulrush\",\n\"bulse\",\n\"bult\",\n\"bulter\",\n\"bultey\",\n\"bultong\",\n\"bultow\",\n\"bulwand\",\n\"bulwark\",\n\"bum\",\n\"bumbaze\",\n\"bumbee\",\n\"bumble\",\n\"bumbler\",\n\"bumbo\",\n\"bumboat\",\n\"bumicky\",\n\"bummalo\",\n\"bummed\",\n\"bummer\",\n\"bummie\",\n\"bumming\",\n\"bummler\",\n\"bummock\",\n\"bump\",\n\"bumpee\",\n\"bumper\",\n\"bumpily\",\n\"bumping\",\n\"bumpkin\",\n\"bumpy\",\n\"bumtrap\",\n\"bumwood\",\n\"bun\",\n\"buna\",\n\"buncal\",\n\"bunce\",\n\"bunch\",\n\"buncher\",\n\"bunchy\",\n\"bund\",\n\"bunder\",\n\"bundle\",\n\"bundler\",\n\"bundlet\",\n\"bundook\",\n\"bundy\",\n\"bung\",\n\"bungee\",\n\"bungey\",\n\"bungfu\",\n\"bungle\",\n\"bungler\",\n\"bungo\",\n\"bungy\",\n\"bunion\",\n\"bunk\",\n\"bunker\",\n\"bunkery\",\n\"bunkie\",\n\"bunko\",\n\"bunkum\",\n\"bunnell\",\n\"bunny\",\n\"bunt\",\n\"buntal\",\n\"bunted\",\n\"bunter\",\n\"bunting\",\n\"bunton\",\n\"bunty\",\n\"bunya\",\n\"bunyah\",\n\"bunyip\",\n\"buoy\",\n\"buoyage\",\n\"buoyant\",\n\"bur\",\n\"buran\",\n\"burao\",\n\"burbank\",\n\"burbark\",\n\"burble\",\n\"burbler\",\n\"burbly\",\n\"burbot\",\n\"burbush\",\n\"burd\",\n\"burden\",\n\"burdie\",\n\"burdock\",\n\"burdon\",\n\"bure\",\n\"bureau\",\n\"bureaux\",\n\"burel\",\n\"burele\",\n\"buret\",\n\"burette\",\n\"burfish\",\n\"burg\",\n\"burgage\",\n\"burgall\",\n\"burgee\",\n\"burgeon\",\n\"burgess\",\n\"burgh\",\n\"burghal\",\n\"burgher\",\n\"burglar\",\n\"burgle\",\n\"burgoo\",\n\"burgul\",\n\"burgus\",\n\"burhead\",\n\"buri\",\n\"burial\",\n\"burian\",\n\"buried\",\n\"burier\",\n\"burin\",\n\"burion\",\n\"buriti\",\n\"burka\",\n\"burke\",\n\"burker\",\n\"burl\",\n\"burlap\",\n\"burled\",\n\"burler\",\n\"burlet\",\n\"burlily\",\n\"burly\",\n\"burmite\",\n\"burn\",\n\"burned\",\n\"burner\",\n\"burnet\",\n\"burnie\",\n\"burning\",\n\"burnish\",\n\"burnous\",\n\"burnout\",\n\"burnt\",\n\"burnut\",\n\"burny\",\n\"buro\",\n\"burp\",\n\"burr\",\n\"burrah\",\n\"burred\",\n\"burrel\",\n\"burrer\",\n\"burring\",\n\"burrish\",\n\"burrito\",\n\"burro\",\n\"burrow\",\n\"burry\",\n\"bursa\",\n\"bursal\",\n\"bursar\",\n\"bursary\",\n\"bursate\",\n\"burse\",\n\"burseed\",\n\"burst\",\n\"burster\",\n\"burt\",\n\"burton\",\n\"burucha\",\n\"burweed\",\n\"bury\",\n\"burying\",\n\"bus\",\n\"busby\",\n\"buscarl\",\n\"bush\",\n\"bushed\",\n\"bushel\",\n\"busher\",\n\"bushful\",\n\"bushi\",\n\"bushily\",\n\"bushing\",\n\"bushlet\",\n\"bushwa\",\n\"bushy\",\n\"busied\",\n\"busily\",\n\"busine\",\n\"busk\",\n\"busked\",\n\"busker\",\n\"busket\",\n\"buskin\",\n\"buskle\",\n\"busky\",\n\"busman\",\n\"buss\",\n\"busser\",\n\"bussock\",\n\"bussu\",\n\"bust\",\n\"bustard\",\n\"busted\",\n\"bustee\",\n\"buster\",\n\"bustic\",\n\"bustle\",\n\"bustled\",\n\"bustler\",\n\"busy\",\n\"busying\",\n\"busyish\",\n\"but\",\n\"butanal\",\n\"butane\",\n\"butanol\",\n\"butch\",\n\"butcher\",\n\"butein\",\n\"butene\",\n\"butenyl\",\n\"butic\",\n\"butine\",\n\"butler\",\n\"butlery\",\n\"butment\",\n\"butoxy\",\n\"butoxyl\",\n\"butt\",\n\"butte\",\n\"butter\",\n\"buttery\",\n\"butting\",\n\"buttle\",\n\"buttock\",\n\"button\",\n\"buttons\",\n\"buttony\",\n\"butty\",\n\"butyl\",\n\"butylic\",\n\"butyne\",\n\"butyr\",\n\"butyral\",\n\"butyric\",\n\"butyrin\",\n\"butyryl\",\n\"buxerry\",\n\"buxom\",\n\"buxomly\",\n\"buy\",\n\"buyable\",\n\"buyer\",\n\"buzane\",\n\"buzz\",\n\"buzzard\",\n\"buzzer\",\n\"buzzies\",\n\"buzzing\",\n\"buzzle\",\n\"buzzwig\",\n\"buzzy\",\n\"by\",\n\"bycoket\",\n\"bye\",\n\"byee\",\n\"byeman\",\n\"byepath\",\n\"byerite\",\n\"bygane\",\n\"bygo\",\n\"bygoing\",\n\"bygone\",\n\"byhand\",\n\"bylaw\",\n\"byname\",\n\"byon\",\n\"byous\",\n\"byously\",\n\"bypass\",\n\"bypast\",\n\"bypath\",\n\"byplay\",\n\"byre\",\n\"byreman\",\n\"byrlaw\",\n\"byrnie\",\n\"byroad\",\n\"byrrus\",\n\"bysen\",\n\"byspell\",\n\"byssal\",\n\"byssin\",\n\"byssine\",\n\"byssoid\",\n\"byssus\",\n\"byth\",\n\"bytime\",\n\"bywalk\",\n\"byway\",\n\"bywoner\",\n\"byword\",\n\"bywork\",\n\"c\",\n\"ca\",\n\"caam\",\n\"caama\",\n\"caaming\",\n\"caapeba\",\n\"cab\",\n\"caba\",\n\"cabaan\",\n\"caback\",\n\"cabaho\",\n\"cabal\",\n\"cabala\",\n\"cabalic\",\n\"caban\",\n\"cabana\",\n\"cabaret\",\n\"cabas\",\n\"cabbage\",\n\"cabbagy\",\n\"cabber\",\n\"cabble\",\n\"cabbler\",\n\"cabby\",\n\"cabda\",\n\"caber\",\n\"cabezon\",\n\"cabin\",\n\"cabinet\",\n\"cabio\",\n\"cable\",\n\"cabled\",\n\"cabler\",\n\"cablet\",\n\"cabling\",\n\"cabman\",\n\"cabob\",\n\"cabocle\",\n\"cabook\",\n\"caboose\",\n\"cabot\",\n\"cabree\",\n\"cabrit\",\n\"cabuya\",\n\"cacam\",\n\"cacao\",\n\"cachaza\",\n\"cache\",\n\"cachet\",\n\"cachexy\",\n\"cachou\",\n\"cachrys\",\n\"cacique\",\n\"cack\",\n\"cackle\",\n\"cackler\",\n\"cacodyl\",\n\"cacoepy\",\n\"caconym\",\n\"cacoon\",\n\"cacti\",\n\"cactoid\",\n\"cacur\",\n\"cad\",\n\"cadamba\",\n\"cadaver\",\n\"cadbait\",\n\"cadbit\",\n\"cadbote\",\n\"caddice\",\n\"caddie\",\n\"caddis\",\n\"caddish\",\n\"caddle\",\n\"caddow\",\n\"caddy\",\n\"cade\",\n\"cadelle\",\n\"cadence\",\n\"cadency\",\n\"cadent\",\n\"cadenza\",\n\"cader\",\n\"caderas\",\n\"cadet\",\n\"cadetcy\",\n\"cadette\",\n\"cadew\",\n\"cadge\",\n\"cadger\",\n\"cadgily\",\n\"cadgy\",\n\"cadi\",\n\"cadism\",\n\"cadjan\",\n\"cadlock\",\n\"cadmia\",\n\"cadmic\",\n\"cadmide\",\n\"cadmium\",\n\"cados\",\n\"cadrans\",\n\"cadre\",\n\"cadua\",\n\"caduac\",\n\"caduca\",\n\"cadus\",\n\"cadweed\",\n\"caeca\",\n\"caecal\",\n\"caecum\",\n\"caeoma\",\n\"caesura\",\n\"cafeneh\",\n\"cafenet\",\n\"caffa\",\n\"caffeic\",\n\"caffeol\",\n\"caffiso\",\n\"caffle\",\n\"caffoy\",\n\"cafh\",\n\"cafiz\",\n\"caftan\",\n\"cag\",\n\"cage\",\n\"caged\",\n\"cageful\",\n\"cageman\",\n\"cager\",\n\"cagey\",\n\"caggy\",\n\"cagily\",\n\"cagit\",\n\"cagmag\",\n\"cahiz\",\n\"cahoot\",\n\"cahot\",\n\"cahow\",\n\"caickle\",\n\"caid\",\n\"caiman\",\n\"caimito\",\n\"cain\",\n\"caique\",\n\"caird\",\n\"cairn\",\n\"cairned\",\n\"cairny\",\n\"caisson\",\n\"caitiff\",\n\"cajeput\",\n\"cajole\",\n\"cajoler\",\n\"cajuela\",\n\"cajun\",\n\"cajuput\",\n\"cake\",\n\"cakebox\",\n\"caker\",\n\"cakette\",\n\"cakey\",\n\"caky\",\n\"cal\",\n\"calaba\",\n\"calaber\",\n\"calade\",\n\"calais\",\n\"calalu\",\n\"calamus\",\n\"calash\",\n\"calcar\",\n\"calced\",\n\"calcic\",\n\"calcify\",\n\"calcine\",\n\"calcite\",\n\"calcium\",\n\"calculi\",\n\"calden\",\n\"caldron\",\n\"calean\",\n\"calends\",\n\"calepin\",\n\"calf\",\n\"calfish\",\n\"caliber\",\n\"calibre\",\n\"calices\",\n\"calicle\",\n\"calico\",\n\"calid\",\n\"caliga\",\n\"caligo\",\n\"calinda\",\n\"calinut\",\n\"calipee\",\n\"caliper\",\n\"caliph\",\n\"caliver\",\n\"calix\",\n\"calk\",\n\"calkage\",\n\"calker\",\n\"calkin\",\n\"calking\",\n\"call\",\n\"callant\",\n\"callboy\",\n\"caller\",\n\"callet\",\n\"calli\",\n\"callid\",\n\"calling\",\n\"callo\",\n\"callose\",\n\"callous\",\n\"callow\",\n\"callus\",\n\"calm\",\n\"calmant\",\n\"calmer\",\n\"calmly\",\n\"calmy\",\n\"calomba\",\n\"calomel\",\n\"calool\",\n\"calor\",\n\"caloric\",\n\"calorie\",\n\"caloris\",\n\"calotte\",\n\"caloyer\",\n\"calp\",\n\"calpac\",\n\"calpack\",\n\"caltrap\",\n\"caltrop\",\n\"calumba\",\n\"calumet\",\n\"calumny\",\n\"calve\",\n\"calved\",\n\"calver\",\n\"calves\",\n\"calvish\",\n\"calvity\",\n\"calvous\",\n\"calx\",\n\"calyces\",\n\"calycle\",\n\"calymma\",\n\"calypso\",\n\"calyx\",\n\"cam\",\n\"camaca\",\n\"camagon\",\n\"camail\",\n\"caman\",\n\"camansi\",\n\"camara\",\n\"camass\",\n\"camata\",\n\"camb\",\n\"cambaye\",\n\"camber\",\n\"cambial\",\n\"cambism\",\n\"cambist\",\n\"cambium\",\n\"cambrel\",\n\"cambuca\",\n\"came\",\n\"cameist\",\n\"camel\",\n\"camelry\",\n\"cameo\",\n\"camera\",\n\"cameral\",\n\"camilla\",\n\"camion\",\n\"camise\",\n\"camisia\",\n\"camlet\",\n\"cammed\",\n\"cammock\",\n\"camoodi\",\n\"camp\",\n\"campana\",\n\"campane\",\n\"camper\",\n\"campho\",\n\"camphol\",\n\"camphor\",\n\"campion\",\n\"cample\",\n\"campo\",\n\"campody\",\n\"campoo\",\n\"campus\",\n\"camus\",\n\"camused\",\n\"camwood\",\n\"can\",\n\"canaba\",\n\"canada\",\n\"canadol\",\n\"canal\",\n\"canamo\",\n\"canape\",\n\"canard\",\n\"canari\",\n\"canarin\",\n\"canary\",\n\"canasta\",\n\"canaut\",\n\"cancan\",\n\"cancel\",\n\"cancer\",\n\"canch\",\n\"cancrum\",\n\"cand\",\n\"candela\",\n\"candent\",\n\"candid\",\n\"candied\",\n\"candier\",\n\"candify\",\n\"candiru\",\n\"candle\",\n\"candler\",\n\"candock\",\n\"candor\",\n\"candroy\",\n\"candy\",\n\"candys\",\n\"cane\",\n\"canel\",\n\"canella\",\n\"canelo\",\n\"caner\",\n\"canette\",\n\"canful\",\n\"cangan\",\n\"cangia\",\n\"cangle\",\n\"cangler\",\n\"cangue\",\n\"canhoop\",\n\"canid\",\n\"canille\",\n\"caninal\",\n\"canine\",\n\"caninus\",\n\"canions\",\n\"canjac\",\n\"cank\",\n\"canker\",\n\"cankery\",\n\"canman\",\n\"canna\",\n\"cannach\",\n\"canned\",\n\"cannel\",\n\"canner\",\n\"cannery\",\n\"cannet\",\n\"cannily\",\n\"canning\",\n\"cannon\",\n\"cannot\",\n\"cannula\",\n\"canny\",\n\"canoe\",\n\"canon\",\n\"canonic\",\n\"canonry\",\n\"canopic\",\n\"canopy\",\n\"canroy\",\n\"canso\",\n\"cant\",\n\"cantala\",\n\"cantar\",\n\"cantara\",\n\"cantaro\",\n\"cantata\",\n\"canted\",\n\"canteen\",\n\"canter\",\n\"canthal\",\n\"canthus\",\n\"cantic\",\n\"cantico\",\n\"cantily\",\n\"cantina\",\n\"canting\",\n\"cantion\",\n\"cantish\",\n\"cantle\",\n\"cantlet\",\n\"canto\",\n\"canton\",\n\"cantoon\",\n\"cantor\",\n\"cantred\",\n\"cantref\",\n\"cantrip\",\n\"cantus\",\n\"canty\",\n\"canun\",\n\"canvas\",\n\"canvass\",\n\"cany\",\n\"canyon\",\n\"canzon\",\n\"caoba\",\n\"cap\",\n\"capable\",\n\"capably\",\n\"capanna\",\n\"capanne\",\n\"capax\",\n\"capcase\",\n\"cape\",\n\"caped\",\n\"capel\",\n\"capelet\",\n\"capelin\",\n\"caper\",\n\"caperer\",\n\"capes\",\n\"capful\",\n\"caph\",\n\"caphar\",\n\"caphite\",\n\"capias\",\n\"capicha\",\n\"capital\",\n\"capitan\",\n\"capivi\",\n\"capkin\",\n\"capless\",\n\"caplin\",\n\"capman\",\n\"capmint\",\n\"capomo\",\n\"capon\",\n\"caporal\",\n\"capot\",\n\"capote\",\n\"capped\",\n\"capper\",\n\"cappie\",\n\"capping\",\n\"capple\",\n\"cappy\",\n\"caprate\",\n\"capreol\",\n\"capric\",\n\"caprice\",\n\"caprid\",\n\"caprin\",\n\"caprine\",\n\"caproic\",\n\"caproin\",\n\"caprone\",\n\"caproyl\",\n\"capryl\",\n\"capsa\",\n\"capsid\",\n\"capsize\",\n\"capstan\",\n\"capsula\",\n\"capsule\",\n\"captain\",\n\"caption\",\n\"captive\",\n\"captor\",\n\"capture\",\n\"capuche\",\n\"capulet\",\n\"capulin\",\n\"car\",\n\"carabao\",\n\"carabid\",\n\"carabin\",\n\"carabus\",\n\"caracal\",\n\"caracol\",\n\"caract\",\n\"carafe\",\n\"caraibe\",\n\"caraipi\",\n\"caramba\",\n\"caramel\",\n\"caranda\",\n\"carane\",\n\"caranna\",\n\"carapax\",\n\"carapo\",\n\"carat\",\n\"caratch\",\n\"caravan\",\n\"caravel\",\n\"caraway\",\n\"carbarn\",\n\"carbeen\",\n\"carbene\",\n\"carbide\",\n\"carbine\",\n\"carbo\",\n\"carbon\",\n\"carbona\",\n\"carbora\",\n\"carboxy\",\n\"carboy\",\n\"carbro\",\n\"carbure\",\n\"carbyl\",\n\"carcake\",\n\"carcass\",\n\"carceag\",\n\"carcel\",\n\"carcoon\",\n\"card\",\n\"cardecu\",\n\"carded\",\n\"cardel\",\n\"carder\",\n\"cardia\",\n\"cardiac\",\n\"cardial\",\n\"cardin\",\n\"carding\",\n\"cardo\",\n\"cardol\",\n\"cardon\",\n\"cardona\",\n\"cardoon\",\n\"care\",\n\"careen\",\n\"career\",\n\"careful\",\n\"carene\",\n\"carer\",\n\"caress\",\n\"carest\",\n\"caret\",\n\"carfare\",\n\"carfax\",\n\"carful\",\n\"carga\",\n\"cargo\",\n\"carhop\",\n\"cariama\",\n\"caribou\",\n\"carid\",\n\"caries\",\n\"carina\",\n\"carinal\",\n\"cariole\",\n\"carious\",\n\"cark\",\n\"carking\",\n\"carkled\",\n\"carl\",\n\"carless\",\n\"carlet\",\n\"carlie\",\n\"carlin\",\n\"carline\",\n\"carling\",\n\"carlish\",\n\"carload\",\n\"carlot\",\n\"carls\",\n\"carman\",\n\"carmele\",\n\"carmine\",\n\"carmot\",\n\"carnage\",\n\"carnal\",\n\"carnate\",\n\"carneol\",\n\"carney\",\n\"carnic\",\n\"carnify\",\n\"carnose\",\n\"carnous\",\n\"caroa\",\n\"carob\",\n\"caroba\",\n\"caroche\",\n\"carol\",\n\"caroler\",\n\"caroli\",\n\"carolin\",\n\"carolus\",\n\"carom\",\n\"carone\",\n\"caronic\",\n\"caroome\",\n\"caroon\",\n\"carotic\",\n\"carotid\",\n\"carotin\",\n\"carouse\",\n\"carp\",\n\"carpal\",\n\"carpale\",\n\"carpel\",\n\"carpent\",\n\"carper\",\n\"carpet\",\n\"carpid\",\n\"carping\",\n\"carpium\",\n\"carport\",\n\"carpos\",\n\"carpus\",\n\"carr\",\n\"carrack\",\n\"carrel\",\n\"carrick\",\n\"carried\",\n\"carrier\",\n\"carrion\",\n\"carrizo\",\n\"carroch\",\n\"carrot\",\n\"carroty\",\n\"carrow\",\n\"carry\",\n\"carse\",\n\"carshop\",\n\"carsick\",\n\"cart\",\n\"cartage\",\n\"carte\",\n\"cartel\",\n\"carter\",\n\"cartful\",\n\"cartman\",\n\"carton\",\n\"cartoon\",\n\"cartway\",\n\"carty\",\n\"carua\",\n\"carucal\",\n\"carval\",\n\"carve\",\n\"carvel\",\n\"carven\",\n\"carvene\",\n\"carver\",\n\"carving\",\n\"carvol\",\n\"carvone\",\n\"carvyl\",\n\"caryl\",\n\"casaba\",\n\"casabe\",\n\"casal\",\n\"casalty\",\n\"casate\",\n\"casaun\",\n\"casava\",\n\"casave\",\n\"casavi\",\n\"casbah\",\n\"cascade\",\n\"cascado\",\n\"cascara\",\n\"casco\",\n\"cascol\",\n\"case\",\n\"casease\",\n\"caseate\",\n\"casebox\",\n\"cased\",\n\"caseful\",\n\"casefy\",\n\"caseic\",\n\"casein\",\n\"caseose\",\n\"caseous\",\n\"caser\",\n\"casern\",\n\"caseum\",\n\"cash\",\n\"casha\",\n\"cashaw\",\n\"cashbox\",\n\"cashboy\",\n\"cashel\",\n\"cashew\",\n\"cashier\",\n\"casing\",\n\"casino\",\n\"casiri\",\n\"cask\",\n\"casket\",\n\"casking\",\n\"casque\",\n\"casqued\",\n\"casquet\",\n\"cass\",\n\"cassady\",\n\"casse\",\n\"cassena\",\n\"cassia\",\n\"cassie\",\n\"cassina\",\n\"cassine\",\n\"cassino\",\n\"cassis\",\n\"cassock\",\n\"casson\",\n\"cassoon\",\n\"cast\",\n\"caste\",\n\"caster\",\n\"castice\",\n\"casting\",\n\"castle\",\n\"castled\",\n\"castlet\",\n\"castock\",\n\"castoff\",\n\"castor\",\n\"castory\",\n\"castra\",\n\"castral\",\n\"castrum\",\n\"castuli\",\n\"casual\",\n\"casuary\",\n\"casuist\",\n\"casula\",\n\"cat\",\n\"catalpa\",\n\"catan\",\n\"catapan\",\n\"cataria\",\n\"catarrh\",\n\"catasta\",\n\"catbird\",\n\"catboat\",\n\"catcall\",\n\"catch\",\n\"catcher\",\n\"catchup\",\n\"catchy\",\n\"catclaw\",\n\"catdom\",\n\"cate\",\n\"catechu\",\n\"catella\",\n\"catena\",\n\"catenae\",\n\"cater\",\n\"cateran\",\n\"caterer\",\n\"caterva\",\n\"cateye\",\n\"catface\",\n\"catfall\",\n\"catfish\",\n\"catfoot\",\n\"catgut\",\n\"cathead\",\n\"cathect\",\n\"catheti\",\n\"cathin\",\n\"cathine\",\n\"cathion\",\n\"cathode\",\n\"cathole\",\n\"cathood\",\n\"cathop\",\n\"cathro\",\n\"cation\",\n\"cativo\",\n\"catjang\",\n\"catkin\",\n\"catlap\",\n\"catlike\",\n\"catlin\",\n\"catling\",\n\"catmint\",\n\"catnip\",\n\"catpipe\",\n\"catskin\",\n\"catstep\",\n\"catsup\",\n\"cattabu\",\n\"cattail\",\n\"cattalo\",\n\"cattery\",\n\"cattily\",\n\"catting\",\n\"cattish\",\n\"cattle\",\n\"catty\",\n\"catvine\",\n\"catwalk\",\n\"catwise\",\n\"catwood\",\n\"catwort\",\n\"caubeen\",\n\"cauboge\",\n\"cauch\",\n\"caucho\",\n\"caucus\",\n\"cauda\",\n\"caudad\",\n\"caudae\",\n\"caudal\",\n\"caudata\",\n\"caudate\",\n\"caudex\",\n\"caudle\",\n\"caught\",\n\"cauk\",\n\"caul\",\n\"cauld\",\n\"caules\",\n\"cauline\",\n\"caulis\",\n\"caulome\",\n\"caulote\",\n\"caum\",\n\"cauma\",\n\"caunch\",\n\"caup\",\n\"caupo\",\n\"caurale\",\n\"causal\",\n\"causate\",\n\"cause\",\n\"causer\",\n\"causey\",\n\"causing\",\n\"causse\",\n\"causson\",\n\"caustic\",\n\"cautel\",\n\"cauter\",\n\"cautery\",\n\"caution\",\n\"cautivo\",\n\"cava\",\n\"cavae\",\n\"caval\",\n\"cavalla\",\n\"cavalry\",\n\"cavate\",\n\"cave\",\n\"caveat\",\n\"cavel\",\n\"cavelet\",\n\"cavern\",\n\"cavetto\",\n\"caviar\",\n\"cavie\",\n\"cavil\",\n\"caviler\",\n\"caving\",\n\"cavings\",\n\"cavish\",\n\"cavity\",\n\"caviya\",\n\"cavort\",\n\"cavus\",\n\"cavy\",\n\"caw\",\n\"cawk\",\n\"cawky\",\n\"cawney\",\n\"cawquaw\",\n\"caxiri\",\n\"caxon\",\n\"cay\",\n\"cayenne\",\n\"cayman\",\n\"caza\",\n\"cazimi\",\n\"ce\",\n\"cearin\",\n\"cease\",\n\"ceasmic\",\n\"cebell\",\n\"cebian\",\n\"cebid\",\n\"cebil\",\n\"cebine\",\n\"ceboid\",\n\"cebur\",\n\"cecils\",\n\"cecity\",\n\"cedar\",\n\"cedared\",\n\"cedarn\",\n\"cedary\",\n\"cede\",\n\"cedent\",\n\"ceder\",\n\"cedilla\",\n\"cedrat\",\n\"cedrate\",\n\"cedre\",\n\"cedrene\",\n\"cedrin\",\n\"cedrine\",\n\"cedrium\",\n\"cedrol\",\n\"cedron\",\n\"cedry\",\n\"cedula\",\n\"cee\",\n\"ceibo\",\n\"ceil\",\n\"ceile\",\n\"ceiler\",\n\"ceilidh\",\n\"ceiling\",\n\"celadon\",\n\"celemin\",\n\"celery\",\n\"celesta\",\n\"celeste\",\n\"celiac\",\n\"celite\",\n\"cell\",\n\"cella\",\n\"cellae\",\n\"cellar\",\n\"celled\",\n\"cellist\",\n\"cello\",\n\"celloid\",\n\"cellose\",\n\"cellule\",\n\"celsian\",\n\"celt\",\n\"celtium\",\n\"celtuce\",\n\"cembalo\",\n\"cement\",\n\"cenacle\",\n\"cendre\",\n\"cenoby\",\n\"cense\",\n\"censer\",\n\"censive\",\n\"censor\",\n\"censual\",\n\"censure\",\n\"census\",\n\"cent\",\n\"centage\",\n\"cental\",\n\"centare\",\n\"centaur\",\n\"centavo\",\n\"centena\",\n\"center\",\n\"centiar\",\n\"centile\",\n\"centime\",\n\"centimo\",\n\"centner\",\n\"cento\",\n\"centrad\",\n\"central\",\n\"centric\",\n\"centrum\",\n\"centry\",\n\"centum\",\n\"century\",\n\"ceorl\",\n\"cep\",\n\"cepa\",\n\"cepe\",\n\"cephid\",\n\"ceps\",\n\"ceptor\",\n\"cequi\",\n\"cerago\",\n\"ceral\",\n\"ceramal\",\n\"ceramic\",\n\"ceras\",\n\"cerasin\",\n\"cerata\",\n\"cerate\",\n\"cerated\",\n\"cercal\",\n\"cerci\",\n\"cercus\",\n\"cere\",\n\"cereal\",\n\"cerebra\",\n\"cered\",\n\"cereous\",\n\"cerer\",\n\"ceresin\",\n\"cerevis\",\n\"ceria\",\n\"ceric\",\n\"ceride\",\n\"cerillo\",\n\"ceriman\",\n\"cerin\",\n\"cerine\",\n\"ceriops\",\n\"cerise\",\n\"cerite\",\n\"cerium\",\n\"cermet\",\n\"cern\",\n\"cero\",\n\"ceroma\",\n\"cerote\",\n\"cerotic\",\n\"cerotin\",\n\"cerous\",\n\"cerrero\",\n\"cerrial\",\n\"cerris\",\n\"certain\",\n\"certie\",\n\"certify\",\n\"certis\",\n\"certy\",\n\"cerule\",\n\"cerumen\",\n\"ceruse\",\n\"cervid\",\n\"cervine\",\n\"cervix\",\n\"cervoid\",\n\"ceryl\",\n\"cesious\",\n\"cesium\",\n\"cess\",\n\"cesser\",\n\"cession\",\n\"cessor\",\n\"cesspit\",\n\"cest\",\n\"cestode\",\n\"cestoid\",\n\"cestrum\",\n\"cestus\",\n\"cetane\",\n\"cetene\",\n\"ceti\",\n\"cetic\",\n\"cetin\",\n\"cetyl\",\n\"cetylic\",\n\"cevine\",\n\"cha\",\n\"chaa\",\n\"chab\",\n\"chabot\",\n\"chabouk\",\n\"chabuk\",\n\"chacate\",\n\"chack\",\n\"chacker\",\n\"chackle\",\n\"chacma\",\n\"chacona\",\n\"chacte\",\n\"chad\",\n\"chaeta\",\n\"chafe\",\n\"chafer\",\n\"chafery\",\n\"chaff\",\n\"chaffer\",\n\"chaffy\",\n\"chaft\",\n\"chafted\",\n\"chagan\",\n\"chagrin\",\n\"chaguar\",\n\"chagul\",\n\"chahar\",\n\"chai\",\n\"chain\",\n\"chained\",\n\"chainer\",\n\"chainon\",\n\"chair\",\n\"chairer\",\n\"chais\",\n\"chaise\",\n\"chaitya\",\n\"chaja\",\n\"chaka\",\n\"chakar\",\n\"chakari\",\n\"chakazi\",\n\"chakdar\",\n\"chakobu\",\n\"chakra\",\n\"chakram\",\n\"chaksi\",\n\"chal\",\n\"chalaco\",\n\"chalana\",\n\"chalaza\",\n\"chalaze\",\n\"chalcid\",\n\"chalcon\",\n\"chalcus\",\n\"chalder\",\n\"chalet\",\n\"chalice\",\n\"chalk\",\n\"chalker\",\n\"chalky\",\n\"challah\",\n\"challie\",\n\"challis\",\n\"chalmer\",\n\"chalon\",\n\"chalone\",\n\"chalque\",\n\"chalta\",\n\"chalutz\",\n\"cham\",\n\"chamal\",\n\"chamar\",\n\"chamber\",\n\"chambul\",\n\"chamfer\",\n\"chamiso\",\n\"chamite\",\n\"chamma\",\n\"chamois\",\n\"champ\",\n\"champac\",\n\"champer\",\n\"champy\",\n\"chance\",\n\"chancel\",\n\"chancer\",\n\"chanche\",\n\"chanco\",\n\"chancre\",\n\"chancy\",\n\"chandam\",\n\"chandi\",\n\"chandoo\",\n\"chandu\",\n\"chandul\",\n\"chang\",\n\"changa\",\n\"changar\",\n\"change\",\n\"changer\",\n\"chank\",\n\"channel\",\n\"channer\",\n\"chanson\",\n\"chanst\",\n\"chant\",\n\"chanter\",\n\"chantey\",\n\"chantry\",\n\"chao\",\n\"chaos\",\n\"chaotic\",\n\"chap\",\n\"chapah\",\n\"chape\",\n\"chapeau\",\n\"chaped\",\n\"chapel\",\n\"chapin\",\n\"chaplet\",\n\"chapman\",\n\"chapped\",\n\"chapper\",\n\"chappie\",\n\"chappin\",\n\"chappow\",\n\"chappy\",\n\"chaps\",\n\"chapt\",\n\"chapter\",\n\"char\",\n\"charac\",\n\"charade\",\n\"charas\",\n\"charbon\",\n\"chard\",\n\"chare\",\n\"charer\",\n\"charet\",\n\"charge\",\n\"chargee\",\n\"charger\",\n\"charier\",\n\"charily\",\n\"chariot\",\n\"charism\",\n\"charity\",\n\"chark\",\n\"charka\",\n\"charkha\",\n\"charm\",\n\"charmel\",\n\"charmer\",\n\"charnel\",\n\"charpit\",\n\"charpoy\",\n\"charqui\",\n\"charr\",\n\"charry\",\n\"chart\",\n\"charter\",\n\"charuk\",\n\"chary\",\n\"chase\",\n\"chaser\",\n\"chasing\",\n\"chasm\",\n\"chasma\",\n\"chasmal\",\n\"chasmed\",\n\"chasmic\",\n\"chasmy\",\n\"chasse\",\n\"chassis\",\n\"chaste\",\n\"chasten\",\n\"chat\",\n\"chataka\",\n\"chateau\",\n\"chati\",\n\"chatta\",\n\"chattel\",\n\"chatter\",\n\"chatty\",\n\"chauk\",\n\"chaus\",\n\"chaute\",\n\"chauth\",\n\"chavish\",\n\"chaw\",\n\"chawan\",\n\"chawer\",\n\"chawk\",\n\"chawl\",\n\"chay\",\n\"chaya\",\n\"chayote\",\n\"chazan\",\n\"che\",\n\"cheap\",\n\"cheapen\",\n\"cheaply\",\n\"cheat\",\n\"cheatee\",\n\"cheater\",\n\"chebec\",\n\"chebel\",\n\"chebog\",\n\"chebule\",\n\"check\",\n\"checked\",\n\"checker\",\n\"checkup\",\n\"checky\",\n\"cheder\",\n\"chee\",\n\"cheecha\",\n\"cheek\",\n\"cheeker\",\n\"cheeky\",\n\"cheep\",\n\"cheeper\",\n\"cheepy\",\n\"cheer\",\n\"cheered\",\n\"cheerer\",\n\"cheerio\",\n\"cheerly\",\n\"cheery\",\n\"cheese\",\n\"cheeser\",\n\"cheesy\",\n\"cheet\",\n\"cheetah\",\n\"cheeter\",\n\"cheetie\",\n\"chef\",\n\"chegoe\",\n\"chegre\",\n\"cheir\",\n\"chekan\",\n\"cheke\",\n\"cheki\",\n\"chekmak\",\n\"chela\",\n\"chelate\",\n\"chelem\",\n\"chelide\",\n\"chello\",\n\"chelone\",\n\"chelp\",\n\"chelys\",\n\"chemic\",\n\"chemis\",\n\"chemise\",\n\"chemism\",\n\"chemist\",\n\"chena\",\n\"chende\",\n\"cheng\",\n\"chenica\",\n\"cheque\",\n\"cherem\",\n\"cherish\",\n\"cheroot\",\n\"cherry\",\n\"chert\",\n\"cherte\",\n\"cherty\",\n\"cherub\",\n\"chervil\",\n\"cheson\",\n\"chess\",\n\"chessel\",\n\"chesser\",\n\"chest\",\n\"chester\",\n\"chesty\",\n\"cheth\",\n\"chettik\",\n\"chetty\",\n\"chevage\",\n\"cheval\",\n\"cheve\",\n\"cheven\",\n\"chevin\",\n\"chevise\",\n\"chevon\",\n\"chevron\",\n\"chevy\",\n\"chew\",\n\"chewer\",\n\"chewink\",\n\"chewy\",\n\"cheyney\",\n\"chhatri\",\n\"chi\",\n\"chia\",\n\"chiasm\",\n\"chiasma\",\n\"chiaus\",\n\"chibouk\",\n\"chibrit\",\n\"chic\",\n\"chicane\",\n\"chichi\",\n\"chick\",\n\"chicken\",\n\"chicker\",\n\"chicky\",\n\"chicle\",\n\"chico\",\n\"chicory\",\n\"chicot\",\n\"chicote\",\n\"chid\",\n\"chidden\",\n\"chide\",\n\"chider\",\n\"chiding\",\n\"chidra\",\n\"chief\",\n\"chiefly\",\n\"chield\",\n\"chien\",\n\"chiffer\",\n\"chiffon\",\n\"chiggak\",\n\"chigger\",\n\"chignon\",\n\"chigoe\",\n\"chih\",\n\"chihfu\",\n\"chikara\",\n\"chil\",\n\"child\",\n\"childe\",\n\"childed\",\n\"childly\",\n\"chile\",\n\"chili\",\n\"chiliad\",\n\"chill\",\n\"chilla\",\n\"chilled\",\n\"chiller\",\n\"chillo\",\n\"chillum\",\n\"chilly\",\n\"chiloma\",\n\"chilver\",\n\"chimble\",\n\"chime\",\n\"chimer\",\n\"chimera\",\n\"chimney\",\n\"chin\",\n\"china\",\n\"chinar\",\n\"chinch\",\n\"chincha\",\n\"chinche\",\n\"chine\",\n\"chined\",\n\"ching\",\n\"chingma\",\n\"chinik\",\n\"chinin\",\n\"chink\",\n\"chinker\",\n\"chinkle\",\n\"chinks\",\n\"chinky\",\n\"chinnam\",\n\"chinned\",\n\"chinny\",\n\"chino\",\n\"chinoa\",\n\"chinol\",\n\"chinse\",\n\"chint\",\n\"chintz\",\n\"chip\",\n\"chiplet\",\n\"chipped\",\n\"chipper\",\n\"chippy\",\n\"chips\",\n\"chiral\",\n\"chirata\",\n\"chiripa\",\n\"chirk\",\n\"chirm\",\n\"chiro\",\n\"chirp\",\n\"chirper\",\n\"chirpy\",\n\"chirr\",\n\"chirrup\",\n\"chisel\",\n\"chit\",\n\"chitak\",\n\"chital\",\n\"chitin\",\n\"chiton\",\n\"chitose\",\n\"chitra\",\n\"chitter\",\n\"chitty\",\n\"chive\",\n\"chivey\",\n\"chkalik\",\n\"chlamyd\",\n\"chlamys\",\n\"chlor\",\n\"chloral\",\n\"chlore\",\n\"chloric\",\n\"chloryl\",\n\"cho\",\n\"choana\",\n\"choate\",\n\"choaty\",\n\"chob\",\n\"choca\",\n\"chocard\",\n\"chocho\",\n\"chock\",\n\"chocker\",\n\"choel\",\n\"choenix\",\n\"choffer\",\n\"choga\",\n\"chogak\",\n\"chogset\",\n\"choice\",\n\"choicy\",\n\"choil\",\n\"choiler\",\n\"choir\",\n\"chokage\",\n\"choke\",\n\"choker\",\n\"choking\",\n\"chokra\",\n\"choky\",\n\"chol\",\n\"chola\",\n\"cholane\",\n\"cholate\",\n\"chold\",\n\"choleic\",\n\"choler\",\n\"cholera\",\n\"choli\",\n\"cholic\",\n\"choline\",\n\"cholla\",\n\"choller\",\n\"cholum\",\n\"chomp\",\n\"chondre\",\n\"chonta\",\n\"choop\",\n\"choose\",\n\"chooser\",\n\"choosy\",\n\"chop\",\n\"chopa\",\n\"chopin\",\n\"chopine\",\n\"chopped\",\n\"chopper\",\n\"choppy\",\n\"choragy\",\n\"choral\",\n\"chord\",\n\"chorda\",\n\"chordal\",\n\"chorded\",\n\"chore\",\n\"chorea\",\n\"choreal\",\n\"choree\",\n\"choregy\",\n\"choreic\",\n\"choreus\",\n\"chorial\",\n\"choric\",\n\"chorine\",\n\"chorion\",\n\"chorism\",\n\"chorist\",\n\"chorogi\",\n\"choroid\",\n\"chorook\",\n\"chort\",\n\"chorten\",\n\"chortle\",\n\"chorus\",\n\"choryos\",\n\"chose\",\n\"chosen\",\n\"chott\",\n\"chough\",\n\"chouka\",\n\"choup\",\n\"chous\",\n\"chouse\",\n\"chouser\",\n\"chow\",\n\"chowder\",\n\"chowk\",\n\"chowry\",\n\"choya\",\n\"chria\",\n\"chrism\",\n\"chrisma\",\n\"chrisom\",\n\"chroma\",\n\"chrome\",\n\"chromic\",\n\"chromid\",\n\"chromo\",\n\"chromy\",\n\"chromyl\",\n\"chronal\",\n\"chronic\",\n\"chrotta\",\n\"chrysal\",\n\"chrysid\",\n\"chrysin\",\n\"chub\",\n\"chubbed\",\n\"chubby\",\n\"chuck\",\n\"chucker\",\n\"chuckle\",\n\"chucky\",\n\"chuddar\",\n\"chufa\",\n\"chuff\",\n\"chuffy\",\n\"chug\",\n\"chugger\",\n\"chuhra\",\n\"chukar\",\n\"chukker\",\n\"chukor\",\n\"chulan\",\n\"chullpa\",\n\"chum\",\n\"chummer\",\n\"chummy\",\n\"chump\",\n\"chumpy\",\n\"chun\",\n\"chunari\",\n\"chunga\",\n\"chunk\",\n\"chunky\",\n\"chunner\",\n\"chunnia\",\n\"chunter\",\n\"chupak\",\n\"chupon\",\n\"church\",\n\"churchy\",\n\"churel\",\n\"churl\",\n\"churled\",\n\"churly\",\n\"churm\",\n\"churn\",\n\"churr\",\n\"churrus\",\n\"chut\",\n\"chute\",\n\"chuter\",\n\"chutney\",\n\"chyack\",\n\"chyak\",\n\"chyle\",\n\"chylify\",\n\"chyloid\",\n\"chylous\",\n\"chymase\",\n\"chyme\",\n\"chymia\",\n\"chymic\",\n\"chymify\",\n\"chymous\",\n\"chypre\",\n\"chytra\",\n\"chytrid\",\n\"cibol\",\n\"cibory\",\n\"ciboule\",\n\"cicad\",\n\"cicada\",\n\"cicadid\",\n\"cicala\",\n\"cicely\",\n\"cicer\",\n\"cichlid\",\n\"cidarid\",\n\"cidaris\",\n\"cider\",\n\"cig\",\n\"cigala\",\n\"cigar\",\n\"cigua\",\n\"cilia\",\n\"ciliary\",\n\"ciliate\",\n\"cilice\",\n\"cilium\",\n\"cimbia\",\n\"cimelia\",\n\"cimex\",\n\"cimicid\",\n\"cimline\",\n\"cinch\",\n\"cincher\",\n\"cinclis\",\n\"cinct\",\n\"cinder\",\n\"cindery\",\n\"cine\",\n\"cinel\",\n\"cinema\",\n\"cinene\",\n\"cineole\",\n\"cinerea\",\n\"cingle\",\n\"cinnyl\",\n\"cinque\",\n\"cinter\",\n\"cinuran\",\n\"cion\",\n\"cipher\",\n\"cipo\",\n\"cipolin\",\n\"cippus\",\n\"circa\",\n\"circle\",\n\"circled\",\n\"circler\",\n\"circlet\",\n\"circuit\",\n\"circus\",\n\"circusy\",\n\"cirque\",\n\"cirrate\",\n\"cirri\",\n\"cirrose\",\n\"cirrous\",\n\"cirrus\",\n\"cirsoid\",\n\"ciruela\",\n\"cisco\",\n\"cise\",\n\"cisele\",\n\"cissing\",\n\"cissoid\",\n\"cist\",\n\"cista\",\n\"cistae\",\n\"cisted\",\n\"cistern\",\n\"cistic\",\n\"cit\",\n\"citable\",\n\"citadel\",\n\"citator\",\n\"cite\",\n\"citee\",\n\"citer\",\n\"citess\",\n\"cithara\",\n\"cither\",\n\"citied\",\n\"citify\",\n\"citizen\",\n\"citole\",\n\"citral\",\n\"citrate\",\n\"citrean\",\n\"citrene\",\n\"citric\",\n\"citril\",\n\"citrin\",\n\"citrine\",\n\"citron\",\n\"citrous\",\n\"citrus\",\n\"cittern\",\n\"citua\",\n\"city\",\n\"citydom\",\n\"cityful\",\n\"cityish\",\n\"cive\",\n\"civet\",\n\"civic\",\n\"civics\",\n\"civil\",\n\"civilly\",\n\"civism\",\n\"civvy\",\n\"cixiid\",\n\"clabber\",\n\"clachan\",\n\"clack\",\n\"clacker\",\n\"clacket\",\n\"clad\",\n\"cladine\",\n\"cladode\",\n\"cladose\",\n\"cladus\",\n\"clag\",\n\"claggum\",\n\"claggy\",\n\"claim\",\n\"claimer\",\n\"clairce\",\n\"claith\",\n\"claiver\",\n\"clam\",\n\"clamant\",\n\"clamb\",\n\"clamber\",\n\"clame\",\n\"clamer\",\n\"clammed\",\n\"clammer\",\n\"clammy\",\n\"clamor\",\n\"clamp\",\n\"clamper\",\n\"clan\",\n\"clang\",\n\"clangor\",\n\"clank\",\n\"clanned\",\n\"clap\",\n\"clapnet\",\n\"clapped\",\n\"clapper\",\n\"clapt\",\n\"claque\",\n\"claquer\",\n\"clarain\",\n\"claret\",\n\"clarify\",\n\"clarin\",\n\"clarion\",\n\"clarity\",\n\"clark\",\n\"claro\",\n\"clart\",\n\"clarty\",\n\"clary\",\n\"clash\",\n\"clasher\",\n\"clashy\",\n\"clasp\",\n\"clasper\",\n\"claspt\",\n\"class\",\n\"classed\",\n\"classer\",\n\"classes\",\n\"classic\",\n\"classis\",\n\"classy\",\n\"clastic\",\n\"clat\",\n\"clatch\",\n\"clatter\",\n\"clatty\",\n\"claught\",\n\"clausal\",\n\"clause\",\n\"claut\",\n\"clava\",\n\"claval\",\n\"clavate\",\n\"clave\",\n\"clavel\",\n\"claver\",\n\"clavial\",\n\"clavier\",\n\"claviol\",\n\"clavis\",\n\"clavola\",\n\"clavus\",\n\"clavy\",\n\"claw\",\n\"clawed\",\n\"clawer\",\n\"clawk\",\n\"clawker\",\n\"clay\",\n\"clayen\",\n\"clayer\",\n\"clayey\",\n\"clayish\",\n\"clayman\",\n\"claypan\",\n\"cleach\",\n\"clead\",\n\"cleaded\",\n\"cleam\",\n\"cleamer\",\n\"clean\",\n\"cleaner\",\n\"cleanly\",\n\"cleanse\",\n\"cleanup\",\n\"clear\",\n\"clearer\",\n\"clearly\",\n\"cleat\",\n\"cleave\",\n\"cleaver\",\n\"cleche\",\n\"cleck\",\n\"cled\",\n\"cledge\",\n\"cledgy\",\n\"clee\",\n\"cleek\",\n\"cleeked\",\n\"cleeky\",\n\"clef\",\n\"cleft\",\n\"clefted\",\n\"cleg\",\n\"clem\",\n\"clement\",\n\"clench\",\n\"cleoid\",\n\"clep\",\n\"clergy\",\n\"cleric\",\n\"clerid\",\n\"clerisy\",\n\"clerk\",\n\"clerkly\",\n\"cleruch\",\n\"cletch\",\n\"cleuch\",\n\"cleve\",\n\"clever\",\n\"clevis\",\n\"clew\",\n\"cliack\",\n\"cliche\",\n\"click\",\n\"clicker\",\n\"clicket\",\n\"clicky\",\n\"cliency\",\n\"client\",\n\"cliff\",\n\"cliffed\",\n\"cliffy\",\n\"clift\",\n\"clifty\",\n\"clima\",\n\"climata\",\n\"climate\",\n\"climath\",\n\"climax\",\n\"climb\",\n\"climber\",\n\"clime\",\n\"clinal\",\n\"clinch\",\n\"cline\",\n\"cling\",\n\"clinger\",\n\"clingy\",\n\"clinia\",\n\"clinic\",\n\"clinium\",\n\"clink\",\n\"clinker\",\n\"clinkum\",\n\"clinoid\",\n\"clint\",\n\"clinty\",\n\"clip\",\n\"clipei\",\n\"clipeus\",\n\"clipped\",\n\"clipper\",\n\"clips\",\n\"clipse\",\n\"clipt\",\n\"clique\",\n\"cliquy\",\n\"clisere\",\n\"clit\",\n\"clitch\",\n\"clite\",\n\"clites\",\n\"clithe\",\n\"clitia\",\n\"clition\",\n\"clitter\",\n\"clival\",\n\"clive\",\n\"clivers\",\n\"clivis\",\n\"clivus\",\n\"cloaca\",\n\"cloacal\",\n\"cloak\",\n\"cloaked\",\n\"cloam\",\n\"cloamen\",\n\"cloamer\",\n\"clobber\",\n\"clochan\",\n\"cloche\",\n\"clocher\",\n\"clock\",\n\"clocked\",\n\"clocker\",\n\"clod\",\n\"clodder\",\n\"cloddy\",\n\"clodlet\",\n\"cloff\",\n\"clog\",\n\"clogger\",\n\"cloggy\",\n\"cloghad\",\n\"clogwyn\",\n\"cloit\",\n\"clomb\",\n\"clomben\",\n\"clonal\",\n\"clone\",\n\"clonic\",\n\"clonism\",\n\"clonus\",\n\"cloof\",\n\"cloop\",\n\"cloot\",\n\"clootie\",\n\"clop\",\n\"close\",\n\"closed\",\n\"closely\",\n\"closen\",\n\"closer\",\n\"closet\",\n\"closh\",\n\"closish\",\n\"closter\",\n\"closure\",\n\"clot\",\n\"clotbur\",\n\"clote\",\n\"cloth\",\n\"clothe\",\n\"clothes\",\n\"clothy\",\n\"clotter\",\n\"clotty\",\n\"cloture\",\n\"cloud\",\n\"clouded\",\n\"cloudy\",\n\"clough\",\n\"clour\",\n\"clout\",\n\"clouted\",\n\"clouter\",\n\"clouty\",\n\"clove\",\n\"cloven\",\n\"clovene\",\n\"clover\",\n\"clovery\",\n\"clow\",\n\"clown\",\n\"cloy\",\n\"cloyer\",\n\"cloying\",\n\"club\",\n\"clubbed\",\n\"clubber\",\n\"clubby\",\n\"clubdom\",\n\"clubman\",\n\"cluck\",\n\"clue\",\n\"cluff\",\n\"clump\",\n\"clumpy\",\n\"clumse\",\n\"clumsy\",\n\"clunch\",\n\"clung\",\n\"clunk\",\n\"clupeid\",\n\"cluster\",\n\"clutch\",\n\"cluther\",\n\"clutter\",\n\"cly\",\n\"clyer\",\n\"clype\",\n\"clypeal\",\n\"clypeus\",\n\"clysis\",\n\"clysma\",\n\"clysmic\",\n\"clyster\",\n\"cnemial\",\n\"cnemis\",\n\"cnicin\",\n\"cnida\",\n\"coabode\",\n\"coach\",\n\"coachee\",\n\"coacher\",\n\"coachy\",\n\"coact\",\n\"coactor\",\n\"coadapt\",\n\"coadmit\",\n\"coadore\",\n\"coaged\",\n\"coagent\",\n\"coagula\",\n\"coaid\",\n\"coaita\",\n\"coak\",\n\"coakum\",\n\"coal\",\n\"coalbag\",\n\"coalbin\",\n\"coalbox\",\n\"coaler\",\n\"coalify\",\n\"coalize\",\n\"coalpit\",\n\"coaly\",\n\"coaming\",\n\"coannex\",\n\"coapt\",\n\"coarb\",\n\"coarse\",\n\"coarsen\",\n\"coast\",\n\"coastal\",\n\"coaster\",\n\"coat\",\n\"coated\",\n\"coatee\",\n\"coater\",\n\"coati\",\n\"coatie\",\n\"coating\",\n\"coax\",\n\"coaxal\",\n\"coaxer\",\n\"coaxial\",\n\"coaxing\",\n\"coaxy\",\n\"cob\",\n\"cobaea\",\n\"cobalt\",\n\"cobang\",\n\"cobbed\",\n\"cobber\",\n\"cobbing\",\n\"cobble\",\n\"cobbler\",\n\"cobbly\",\n\"cobbra\",\n\"cobby\",\n\"cobcab\",\n\"cobego\",\n\"cobhead\",\n\"cobia\",\n\"cobiron\",\n\"coble\",\n\"cobless\",\n\"cobloaf\",\n\"cobnut\",\n\"cobola\",\n\"cobourg\",\n\"cobra\",\n\"coburg\",\n\"cobweb\",\n\"cobwork\",\n\"coca\",\n\"cocaine\",\n\"cocash\",\n\"cocause\",\n\"coccal\",\n\"cocci\",\n\"coccid\",\n\"cocco\",\n\"coccoid\",\n\"coccous\",\n\"coccule\",\n\"coccus\",\n\"coccyx\",\n\"cochal\",\n\"cochief\",\n\"cochlea\",\n\"cock\",\n\"cockade\",\n\"cockal\",\n\"cocked\",\n\"cocker\",\n\"cocket\",\n\"cockeye\",\n\"cockily\",\n\"cocking\",\n\"cockish\",\n\"cockle\",\n\"cockled\",\n\"cockler\",\n\"cocklet\",\n\"cockly\",\n\"cockney\",\n\"cockpit\",\n\"cockshy\",\n\"cockup\",\n\"cocky\",\n\"coco\",\n\"cocoa\",\n\"cocoach\",\n\"coconut\",\n\"cocoon\",\n\"cocotte\",\n\"coctile\",\n\"coction\",\n\"cocuisa\",\n\"cocullo\",\n\"cocuyo\",\n\"cod\",\n\"coda\",\n\"codbank\",\n\"codder\",\n\"codding\",\n\"coddle\",\n\"coddler\",\n\"code\",\n\"codeine\",\n\"coder\",\n\"codex\",\n\"codfish\",\n\"codger\",\n\"codhead\",\n\"codical\",\n\"codices\",\n\"codicil\",\n\"codify\",\n\"codilla\",\n\"codille\",\n\"codist\",\n\"codling\",\n\"codman\",\n\"codo\",\n\"codol\",\n\"codon\",\n\"codworm\",\n\"coe\",\n\"coecal\",\n\"coecum\",\n\"coed\",\n\"coelar\",\n\"coelder\",\n\"coelect\",\n\"coelho\",\n\"coelia\",\n\"coeliac\",\n\"coelian\",\n\"coelin\",\n\"coeline\",\n\"coelom\",\n\"coeloma\",\n\"coempt\",\n\"coenact\",\n\"coenjoy\",\n\"coenobe\",\n\"coequal\",\n\"coerce\",\n\"coercer\",\n\"coetus\",\n\"coeval\",\n\"coexert\",\n\"coexist\",\n\"coff\",\n\"coffee\",\n\"coffer\",\n\"coffin\",\n\"coffle\",\n\"coffret\",\n\"coft\",\n\"cog\",\n\"cogence\",\n\"cogency\",\n\"cogener\",\n\"cogent\",\n\"cogged\",\n\"cogger\",\n\"coggie\",\n\"cogging\",\n\"coggle\",\n\"coggly\",\n\"coghle\",\n\"cogman\",\n\"cognac\",\n\"cognate\",\n\"cognize\",\n\"cogon\",\n\"cogonal\",\n\"cograil\",\n\"cogroad\",\n\"cogue\",\n\"cogway\",\n\"cogwood\",\n\"cohabit\",\n\"coheir\",\n\"cohere\",\n\"coherer\",\n\"cohibit\",\n\"coho\",\n\"cohoba\",\n\"cohol\",\n\"cohort\",\n\"cohosh\",\n\"cohune\",\n\"coif\",\n\"coifed\",\n\"coign\",\n\"coigue\",\n\"coil\",\n\"coiled\",\n\"coiler\",\n\"coiling\",\n\"coin\",\n\"coinage\",\n\"coiner\",\n\"coinfer\",\n\"coining\",\n\"cointer\",\n\"coiny\",\n\"coir\",\n\"coital\",\n\"coition\",\n\"coiture\",\n\"coitus\",\n\"cojudge\",\n\"cojuror\",\n\"coke\",\n\"cokeman\",\n\"coker\",\n\"cokery\",\n\"coking\",\n\"coky\",\n\"col\",\n\"cola\",\n\"colane\",\n\"colarin\",\n\"colate\",\n\"colauxe\",\n\"colback\",\n\"cold\",\n\"colder\",\n\"coldish\",\n\"coldly\",\n\"cole\",\n\"coletit\",\n\"coleur\",\n\"coli\",\n\"colibri\",\n\"colic\",\n\"colical\",\n\"colicky\",\n\"colima\",\n\"colin\",\n\"coling\",\n\"colitic\",\n\"colitis\",\n\"colk\",\n\"coll\",\n\"collage\",\n\"collar\",\n\"collard\",\n\"collare\",\n\"collate\",\n\"collaud\",\n\"collect\",\n\"colleen\",\n\"college\",\n\"collery\",\n\"collet\",\n\"colley\",\n\"collide\",\n\"collie\",\n\"collied\",\n\"collier\",\n\"collin\",\n\"colline\",\n\"colling\",\n\"collins\",\n\"collock\",\n\"colloid\",\n\"collop\",\n\"collude\",\n\"collum\",\n\"colly\",\n\"collyba\",\n\"colmar\",\n\"colobin\",\n\"colon\",\n\"colonel\",\n\"colonic\",\n\"colony\",\n\"color\",\n\"colored\",\n\"colorer\",\n\"colorin\",\n\"colors\",\n\"colory\",\n\"coloss\",\n\"colossi\",\n\"colove\",\n\"colp\",\n\"colpeo\",\n\"colport\",\n\"colpus\",\n\"colt\",\n\"colter\",\n\"coltish\",\n\"colugo\",\n\"columbo\",\n\"column\",\n\"colunar\",\n\"colure\",\n\"coly\",\n\"colyone\",\n\"colytic\",\n\"colyum\",\n\"colza\",\n\"coma\",\n\"comaker\",\n\"comal\",\n\"comamie\",\n\"comanic\",\n\"comart\",\n\"comate\",\n\"comb\",\n\"combat\",\n\"combed\",\n\"comber\",\n\"combine\",\n\"combing\",\n\"comble\",\n\"comboy\",\n\"combure\",\n\"combust\",\n\"comby\",\n\"come\",\n\"comedic\",\n\"comedo\",\n\"comedy\",\n\"comely\",\n\"comenic\",\n\"comer\",\n\"comes\",\n\"comet\",\n\"cometic\",\n\"comfit\",\n\"comfort\",\n\"comfrey\",\n\"comfy\",\n\"comic\",\n\"comical\",\n\"comicry\",\n\"coming\",\n\"comino\",\n\"comism\",\n\"comital\",\n\"comitia\",\n\"comity\",\n\"comma\",\n\"command\",\n\"commend\",\n\"comment\",\n\"commie\",\n\"commit\",\n\"commix\",\n\"commixt\",\n\"commode\",\n\"common\",\n\"commons\",\n\"commot\",\n\"commove\",\n\"communa\",\n\"commune\",\n\"commute\",\n\"comoid\",\n\"comose\",\n\"comourn\",\n\"comous\",\n\"compact\",\n\"company\",\n\"compare\",\n\"compart\",\n\"compass\",\n\"compear\",\n\"compeer\",\n\"compel\",\n\"compend\",\n\"compete\",\n\"compile\",\n\"complex\",\n\"complin\",\n\"complot\",\n\"comply\",\n\"compo\",\n\"compoer\",\n\"compole\",\n\"compone\",\n\"compony\",\n\"comport\",\n\"compos\",\n\"compose\",\n\"compost\",\n\"compote\",\n\"compreg\",\n\"compter\",\n\"compute\",\n\"comrade\",\n\"con\",\n\"conacre\",\n\"conal\",\n\"conamed\",\n\"conatus\",\n\"concave\",\n\"conceal\",\n\"concede\",\n\"conceit\",\n\"concent\",\n\"concept\",\n\"concern\",\n\"concert\",\n\"conch\",\n\"concha\",\n\"conchal\",\n\"conche\",\n\"conched\",\n\"concher\",\n\"conchy\",\n\"concile\",\n\"concise\",\n\"concoct\",\n\"concord\",\n\"concupy\",\n\"concur\",\n\"concuss\",\n\"cond\",\n\"condemn\",\n\"condign\",\n\"condite\",\n\"condole\",\n\"condone\",\n\"condor\",\n\"conduce\",\n\"conduct\",\n\"conduit\",\n\"condyle\",\n\"cone\",\n\"coned\",\n\"coneen\",\n\"coneine\",\n\"conelet\",\n\"coner\",\n\"cones\",\n\"confab\",\n\"confact\",\n\"confect\",\n\"confess\",\n\"confide\",\n\"confine\",\n\"confirm\",\n\"confix\",\n\"conflow\",\n\"conflux\",\n\"conform\",\n\"confuse\",\n\"confute\",\n\"conga\",\n\"congeal\",\n\"congee\",\n\"conger\",\n\"congest\",\n\"congius\",\n\"congou\",\n\"conic\",\n\"conical\",\n\"conicle\",\n\"conics\",\n\"conidia\",\n\"conifer\",\n\"conima\",\n\"conin\",\n\"conine\",\n\"conject\",\n\"conjoin\",\n\"conjure\",\n\"conjury\",\n\"conk\",\n\"conker\",\n\"conkers\",\n\"conky\",\n\"conn\",\n\"connach\",\n\"connate\",\n\"connect\",\n\"conner\",\n\"connex\",\n\"conning\",\n\"connive\",\n\"connote\",\n\"conoid\",\n\"conopid\",\n\"conquer\",\n\"conred\",\n\"consent\",\n\"consign\",\n\"consist\",\n\"consol\",\n\"console\",\n\"consort\",\n\"conspue\",\n\"constat\",\n\"consul\",\n\"consult\",\n\"consume\",\n\"consute\",\n\"contact\",\n\"contain\",\n\"conte\",\n\"contect\",\n\"contemn\",\n\"content\",\n\"conter\",\n\"contest\",\n\"context\",\n\"contise\",\n\"conto\",\n\"contort\",\n\"contour\",\n\"contra\",\n\"control\",\n\"contund\",\n\"contuse\",\n\"conure\",\n\"conus\",\n\"conusee\",\n\"conusor\",\n\"conuzee\",\n\"conuzor\",\n\"convect\",\n\"convene\",\n\"convent\",\n\"convert\",\n\"conveth\",\n\"convex\",\n\"convey\",\n\"convict\",\n\"convive\",\n\"convoke\",\n\"convoy\",\n\"cony\",\n\"coo\",\n\"cooba\",\n\"coodle\",\n\"cooee\",\n\"cooer\",\n\"coof\",\n\"cooing\",\n\"cooja\",\n\"cook\",\n\"cookdom\",\n\"cookee\",\n\"cooker\",\n\"cookery\",\n\"cooking\",\n\"cookish\",\n\"cookout\",\n\"cooky\",\n\"cool\",\n\"coolant\",\n\"coolen\",\n\"cooler\",\n\"coolie\",\n\"cooling\",\n\"coolish\",\n\"coolly\",\n\"coolth\",\n\"coolung\",\n\"cooly\",\n\"coom\",\n\"coomb\",\n\"coomy\",\n\"coon\",\n\"cooncan\",\n\"coonily\",\n\"coontie\",\n\"coony\",\n\"coop\",\n\"cooper\",\n\"coopery\",\n\"cooree\",\n\"coorie\",\n\"cooser\",\n\"coost\",\n\"coot\",\n\"cooter\",\n\"coothay\",\n\"cootie\",\n\"cop\",\n\"copa\",\n\"copable\",\n\"copaene\",\n\"copaiba\",\n\"copaiye\",\n\"copal\",\n\"copalm\",\n\"copart\",\n\"coparty\",\n\"cope\",\n\"copei\",\n\"copeman\",\n\"copen\",\n\"copepod\",\n\"coper\",\n\"coperta\",\n\"copied\",\n\"copier\",\n\"copilot\",\n\"coping\",\n\"copious\",\n\"copis\",\n\"copist\",\n\"copita\",\n\"copolar\",\n\"copped\",\n\"copper\",\n\"coppery\",\n\"coppet\",\n\"coppice\",\n\"coppin\",\n\"copping\",\n\"copple\",\n\"coppled\",\n\"coppy\",\n\"copr\",\n\"copra\",\n\"coprose\",\n\"copse\",\n\"copsing\",\n\"copsy\",\n\"copter\",\n\"copula\",\n\"copular\",\n\"copus\",\n\"copy\",\n\"copycat\",\n\"copyism\",\n\"copyist\",\n\"copyman\",\n\"coque\",\n\"coquet\",\n\"coquina\",\n\"coquita\",\n\"coquito\",\n\"cor\",\n\"cora\",\n\"corach\",\n\"coracle\",\n\"corah\",\n\"coraise\",\n\"coral\",\n\"coraled\",\n\"coram\",\n\"coranto\",\n\"corban\",\n\"corbeau\",\n\"corbeil\",\n\"corbel\",\n\"corbie\",\n\"corbula\",\n\"corcass\",\n\"corcir\",\n\"cord\",\n\"cordage\",\n\"cordant\",\n\"cordate\",\n\"cordax\",\n\"corded\",\n\"cordel\",\n\"corder\",\n\"cordial\",\n\"cordies\",\n\"cording\",\n\"cordite\",\n\"cordoba\",\n\"cordon\",\n\"cordy\",\n\"cordyl\",\n\"core\",\n\"corebel\",\n\"cored\",\n\"coreid\",\n\"coreign\",\n\"corella\",\n\"corer\",\n\"corf\",\n\"corge\",\n\"corgi\",\n\"corial\",\n\"coriin\",\n\"coring\",\n\"corinne\",\n\"corium\",\n\"cork\",\n\"corkage\",\n\"corke\",\n\"corked\",\n\"corker\",\n\"corking\",\n\"corkish\",\n\"corkite\",\n\"corky\",\n\"corm\",\n\"cormel\",\n\"cormoid\",\n\"cormous\",\n\"cormus\",\n\"corn\",\n\"cornage\",\n\"cornbin\",\n\"corncob\",\n\"cornea\",\n\"corneal\",\n\"cornein\",\n\"cornel\",\n\"corner\",\n\"cornet\",\n\"corneum\",\n\"cornic\",\n\"cornice\",\n\"cornin\",\n\"corning\",\n\"cornu\",\n\"cornual\",\n\"cornule\",\n\"cornute\",\n\"cornuto\",\n\"corny\",\n\"coroa\",\n\"corody\",\n\"corol\",\n\"corolla\",\n\"corona\",\n\"coronad\",\n\"coronae\",\n\"coronal\",\n\"coroner\",\n\"coronet\",\n\"corozo\",\n\"corp\",\n\"corpora\",\n\"corps\",\n\"corpse\",\n\"corpus\",\n\"corrade\",\n\"corral\",\n\"correal\",\n\"correct\",\n\"corrie\",\n\"corrige\",\n\"corrode\",\n\"corrupt\",\n\"corsac\",\n\"corsage\",\n\"corsair\",\n\"corse\",\n\"corset\",\n\"corsie\",\n\"corsite\",\n\"corta\",\n\"cortege\",\n\"cortex\",\n\"cortez\",\n\"cortin\",\n\"cortina\",\n\"coruco\",\n\"coruler\",\n\"corupay\",\n\"corver\",\n\"corvina\",\n\"corvine\",\n\"corvoid\",\n\"coryl\",\n\"corylin\",\n\"corymb\",\n\"coryza\",\n\"cos\",\n\"cosaque\",\n\"coscet\",\n\"coseat\",\n\"cosec\",\n\"cosech\",\n\"coseism\",\n\"coset\",\n\"cosh\",\n\"cosher\",\n\"coshery\",\n\"cosily\",\n\"cosine\",\n\"cosmic\",\n\"cosmism\",\n\"cosmist\",\n\"cosmos\",\n\"coss\",\n\"cossas\",\n\"cosse\",\n\"cosset\",\n\"cossid\",\n\"cost\",\n\"costa\",\n\"costal\",\n\"costar\",\n\"costard\",\n\"costate\",\n\"costean\",\n\"coster\",\n\"costing\",\n\"costive\",\n\"costly\",\n\"costrel\",\n\"costula\",\n\"costume\",\n\"cosy\",\n\"cot\",\n\"cotch\",\n\"cote\",\n\"coteful\",\n\"coterie\",\n\"coth\",\n\"cothe\",\n\"cothish\",\n\"cothon\",\n\"cothurn\",\n\"cothy\",\n\"cotidal\",\n\"cotise\",\n\"cotland\",\n\"cotman\",\n\"coto\",\n\"cotoin\",\n\"cotoro\",\n\"cotrine\",\n\"cotset\",\n\"cotta\",\n\"cottage\",\n\"cotte\",\n\"cotted\",\n\"cotter\",\n\"cottid\",\n\"cottier\",\n\"cottoid\",\n\"cotton\",\n\"cottony\",\n\"cotty\",\n\"cotuit\",\n\"cotula\",\n\"cotutor\",\n\"cotwin\",\n\"cotwist\",\n\"cotyla\",\n\"cotylar\",\n\"cotype\",\n\"couac\",\n\"coucal\",\n\"couch\",\n\"couched\",\n\"couchee\",\n\"coucher\",\n\"couchy\",\n\"coude\",\n\"coudee\",\n\"coue\",\n\"cougar\",\n\"cough\",\n\"cougher\",\n\"cougnar\",\n\"coul\",\n\"could\",\n\"coulee\",\n\"coulomb\",\n\"coulure\",\n\"couma\",\n\"coumara\",\n\"council\",\n\"counite\",\n\"counsel\",\n\"count\",\n\"counter\",\n\"countor\",\n\"country\",\n\"county\",\n\"coup\",\n\"coupage\",\n\"coupe\",\n\"couped\",\n\"coupee\",\n\"couper\",\n\"couple\",\n\"coupled\",\n\"coupler\",\n\"couplet\",\n\"coupon\",\n\"coupure\",\n\"courage\",\n\"courant\",\n\"courap\",\n\"courb\",\n\"courge\",\n\"courida\",\n\"courier\",\n\"couril\",\n\"courlan\",\n\"course\",\n\"coursed\",\n\"courser\",\n\"court\",\n\"courter\",\n\"courtin\",\n\"courtly\",\n\"cousin\",\n\"cousiny\",\n\"coutel\",\n\"couter\",\n\"couth\",\n\"couthie\",\n\"coutil\",\n\"couvade\",\n\"couxia\",\n\"covado\",\n\"cove\",\n\"coved\",\n\"covent\",\n\"cover\",\n\"covered\",\n\"coverer\",\n\"covert\",\n\"covet\",\n\"coveter\",\n\"covey\",\n\"covid\",\n\"covin\",\n\"coving\",\n\"covisit\",\n\"covite\",\n\"cow\",\n\"cowal\",\n\"coward\",\n\"cowardy\",\n\"cowbane\",\n\"cowbell\",\n\"cowbind\",\n\"cowbird\",\n\"cowboy\",\n\"cowdie\",\n\"coween\",\n\"cower\",\n\"cowfish\",\n\"cowgate\",\n\"cowgram\",\n\"cowhage\",\n\"cowheel\",\n\"cowherb\",\n\"cowherd\",\n\"cowhide\",\n\"cowhorn\",\n\"cowish\",\n\"cowitch\",\n\"cowl\",\n\"cowle\",\n\"cowled\",\n\"cowlick\",\n\"cowlike\",\n\"cowling\",\n\"cowman\",\n\"cowpath\",\n\"cowpea\",\n\"cowpen\",\n\"cowpock\",\n\"cowpox\",\n\"cowrie\",\n\"cowroid\",\n\"cowshed\",\n\"cowskin\",\n\"cowslip\",\n\"cowtail\",\n\"cowweed\",\n\"cowy\",\n\"cowyard\",\n\"cox\",\n\"coxa\",\n\"coxal\",\n\"coxcomb\",\n\"coxite\",\n\"coxitis\",\n\"coxy\",\n\"coy\",\n\"coyan\",\n\"coydog\",\n\"coyish\",\n\"coyly\",\n\"coyness\",\n\"coynye\",\n\"coyo\",\n\"coyol\",\n\"coyote\",\n\"coypu\",\n\"coyure\",\n\"coz\",\n\"coze\",\n\"cozen\",\n\"cozener\",\n\"cozier\",\n\"cozily\",\n\"cozy\",\n\"crab\",\n\"crabbed\",\n\"crabber\",\n\"crabby\",\n\"craber\",\n\"crablet\",\n\"crabman\",\n\"crack\",\n\"cracked\",\n\"cracker\",\n\"crackle\",\n\"crackly\",\n\"cracky\",\n\"craddy\",\n\"cradge\",\n\"cradle\",\n\"cradler\",\n\"craft\",\n\"crafty\",\n\"crag\",\n\"craggan\",\n\"cragged\",\n\"craggy\",\n\"craichy\",\n\"crain\",\n\"craisey\",\n\"craizey\",\n\"crajuru\",\n\"crake\",\n\"crakow\",\n\"cram\",\n\"crambe\",\n\"crambid\",\n\"cramble\",\n\"crambly\",\n\"crambo\",\n\"crammer\",\n\"cramp\",\n\"cramped\",\n\"cramper\",\n\"crampet\",\n\"crampon\",\n\"crampy\",\n\"cran\",\n\"cranage\",\n\"crance\",\n\"crane\",\n\"craner\",\n\"craney\",\n\"crania\",\n\"craniad\",\n\"cranial\",\n\"cranian\",\n\"cranic\",\n\"cranium\",\n\"crank\",\n\"cranked\",\n\"cranker\",\n\"crankle\",\n\"crankly\",\n\"crankum\",\n\"cranky\",\n\"crannog\",\n\"cranny\",\n\"crants\",\n\"crap\",\n\"crapaud\",\n\"crape\",\n\"crappie\",\n\"crappin\",\n\"crapple\",\n\"crappo\",\n\"craps\",\n\"crapy\",\n\"crare\",\n\"crash\",\n\"crasher\",\n\"crasis\",\n\"crass\",\n\"crassly\",\n\"cratch\",\n\"crate\",\n\"crater\",\n\"craunch\",\n\"cravat\",\n\"crave\",\n\"craven\",\n\"craver\",\n\"craving\",\n\"cravo\",\n\"craw\",\n\"crawdad\",\n\"crawful\",\n\"crawl\",\n\"crawler\",\n\"crawley\",\n\"crawly\",\n\"crawm\",\n\"crawtae\",\n\"crayer\",\n\"crayon\",\n\"craze\",\n\"crazed\",\n\"crazily\",\n\"crazy\",\n\"crea\",\n\"creagh\",\n\"creaght\",\n\"creak\",\n\"creaker\",\n\"creaky\",\n\"cream\",\n\"creamer\",\n\"creamy\",\n\"creance\",\n\"creant\",\n\"crease\",\n\"creaser\",\n\"creasy\",\n\"creat\",\n\"create\",\n\"creatic\",\n\"creator\",\n\"creche\",\n\"credent\",\n\"credit\",\n\"cree\",\n\"creed\",\n\"creedal\",\n\"creeded\",\n\"creek\",\n\"creeker\",\n\"creeky\",\n\"creel\",\n\"creeler\",\n\"creem\",\n\"creen\",\n\"creep\",\n\"creeper\",\n\"creepie\",\n\"creepy\",\n\"creese\",\n\"creesh\",\n\"creeshy\",\n\"cremate\",\n\"cremone\",\n\"cremor\",\n\"cremule\",\n\"crena\",\n\"crenate\",\n\"crenel\",\n\"crenele\",\n\"crenic\",\n\"crenula\",\n\"creole\",\n\"creosol\",\n\"crepe\",\n\"crepine\",\n\"crepon\",\n\"crept\",\n\"crepy\",\n\"cresol\",\n\"cresoxy\",\n\"cress\",\n\"cressed\",\n\"cresset\",\n\"cresson\",\n\"cressy\",\n\"crest\",\n\"crested\",\n\"cresyl\",\n\"creta\",\n\"cretic\",\n\"cretify\",\n\"cretin\",\n\"cretion\",\n\"crevice\",\n\"crew\",\n\"crewel\",\n\"crewer\",\n\"crewman\",\n\"crib\",\n\"cribber\",\n\"cribble\",\n\"cribo\",\n\"cribral\",\n\"cric\",\n\"crick\",\n\"cricket\",\n\"crickey\",\n\"crickle\",\n\"cricoid\",\n\"cried\",\n\"crier\",\n\"criey\",\n\"crig\",\n\"crile\",\n\"crime\",\n\"crimine\",\n\"crimp\",\n\"crimper\",\n\"crimple\",\n\"crimpy\",\n\"crimson\",\n\"crin\",\n\"crinal\",\n\"crine\",\n\"crined\",\n\"crinet\",\n\"cringe\",\n\"cringer\",\n\"cringle\",\n\"crinite\",\n\"crink\",\n\"crinkle\",\n\"crinkly\",\n\"crinoid\",\n\"crinose\",\n\"crinula\",\n\"cripes\",\n\"cripple\",\n\"cripply\",\n\"crises\",\n\"crisic\",\n\"crisis\",\n\"crisp\",\n\"crisped\",\n\"crisper\",\n\"crisply\",\n\"crispy\",\n\"criss\",\n\"crissal\",\n\"crissum\",\n\"crista\",\n\"critch\",\n\"crith\",\n\"critic\",\n\"crizzle\",\n\"cro\",\n\"croak\",\n\"croaker\",\n\"croaky\",\n\"croc\",\n\"crocard\",\n\"croceic\",\n\"crocein\",\n\"croche\",\n\"crochet\",\n\"croci\",\n\"crocin\",\n\"crock\",\n\"crocker\",\n\"crocket\",\n\"crocky\",\n\"crocus\",\n\"croft\",\n\"crofter\",\n\"crome\",\n\"crone\",\n\"cronet\",\n\"cronish\",\n\"cronk\",\n\"crony\",\n\"crood\",\n\"croodle\",\n\"crook\",\n\"crooked\",\n\"crooken\",\n\"crookle\",\n\"crool\",\n\"croon\",\n\"crooner\",\n\"crop\",\n\"cropman\",\n\"croppa\",\n\"cropper\",\n\"croppie\",\n\"croppy\",\n\"croquet\",\n\"crore\",\n\"crosa\",\n\"crosier\",\n\"crosnes\",\n\"cross\",\n\"crosse\",\n\"crossed\",\n\"crosser\",\n\"crossly\",\n\"crotal\",\n\"crotalo\",\n\"crotch\",\n\"crotchy\",\n\"crotin\",\n\"crottle\",\n\"crotyl\",\n\"crouch\",\n\"croup\",\n\"croupal\",\n\"croupe\",\n\"croupy\",\n\"crouse\",\n\"crout\",\n\"croute\",\n\"crouton\",\n\"crow\",\n\"crowbar\",\n\"crowd\",\n\"crowded\",\n\"crowder\",\n\"crowdy\",\n\"crower\",\n\"crowhop\",\n\"crowing\",\n\"crowl\",\n\"crown\",\n\"crowned\",\n\"crowner\",\n\"crowtoe\",\n\"croy\",\n\"croyden\",\n\"croydon\",\n\"croze\",\n\"crozer\",\n\"crozzle\",\n\"crozzly\",\n\"crubeen\",\n\"cruce\",\n\"cruces\",\n\"cruche\",\n\"crucial\",\n\"crucian\",\n\"crucify\",\n\"crucily\",\n\"cruck\",\n\"crude\",\n\"crudely\",\n\"crudity\",\n\"cruel\",\n\"cruelly\",\n\"cruels\",\n\"cruelty\",\n\"cruent\",\n\"cruet\",\n\"cruety\",\n\"cruise\",\n\"cruiser\",\n\"cruive\",\n\"cruller\",\n\"crum\",\n\"crumb\",\n\"crumber\",\n\"crumble\",\n\"crumbly\",\n\"crumby\",\n\"crumen\",\n\"crumlet\",\n\"crummie\",\n\"crummy\",\n\"crump\",\n\"crumper\",\n\"crumpet\",\n\"crumple\",\n\"crumply\",\n\"crumpy\",\n\"crunch\",\n\"crunchy\",\n\"crunk\",\n\"crunkle\",\n\"crunode\",\n\"crunt\",\n\"cruor\",\n\"crupper\",\n\"crural\",\n\"crureus\",\n\"crus\",\n\"crusade\",\n\"crusado\",\n\"cruse\",\n\"crush\",\n\"crushed\",\n\"crusher\",\n\"crusie\",\n\"crusily\",\n\"crust\",\n\"crusta\",\n\"crustal\",\n\"crusted\",\n\"cruster\",\n\"crusty\",\n\"crutch\",\n\"cruth\",\n\"crutter\",\n\"crux\",\n\"cry\",\n\"cryable\",\n\"crybaby\",\n\"crying\",\n\"cryogen\",\n\"cryosel\",\n\"crypt\",\n\"crypta\",\n\"cryptal\",\n\"crypted\",\n\"cryptic\",\n\"crystal\",\n\"crystic\",\n\"csardas\",\n\"ctene\",\n\"ctenoid\",\n\"cuadra\",\n\"cuarta\",\n\"cub\",\n\"cubage\",\n\"cubbing\",\n\"cubbish\",\n\"cubby\",\n\"cubdom\",\n\"cube\",\n\"cubeb\",\n\"cubelet\",\n\"cuber\",\n\"cubhood\",\n\"cubi\",\n\"cubic\",\n\"cubica\",\n\"cubical\",\n\"cubicle\",\n\"cubicly\",\n\"cubism\",\n\"cubist\",\n\"cubit\",\n\"cubital\",\n\"cubited\",\n\"cubito\",\n\"cubitus\",\n\"cuboid\",\n\"cuck\",\n\"cuckold\",\n\"cuckoo\",\n\"cuculla\",\n\"cud\",\n\"cudava\",\n\"cudbear\",\n\"cudden\",\n\"cuddle\",\n\"cuddly\",\n\"cuddy\",\n\"cudgel\",\n\"cudweed\",\n\"cue\",\n\"cueball\",\n\"cueca\",\n\"cueist\",\n\"cueman\",\n\"cuerda\",\n\"cuesta\",\n\"cuff\",\n\"cuffer\",\n\"cuffin\",\n\"cuffy\",\n\"cuinage\",\n\"cuir\",\n\"cuirass\",\n\"cuisine\",\n\"cuisse\",\n\"cuissen\",\n\"cuisten\",\n\"cuke\",\n\"culbut\",\n\"culebra\",\n\"culet\",\n\"culeus\",\n\"culgee\",\n\"culicid\",\n\"cull\",\n\"culla\",\n\"cullage\",\n\"culler\",\n\"cullet\",\n\"culling\",\n\"cullion\",\n\"cullis\",\n\"cully\",\n\"culm\",\n\"culmen\",\n\"culmy\",\n\"culotte\",\n\"culpa\",\n\"culpose\",\n\"culprit\",\n\"cult\",\n\"cultch\",\n\"cultic\",\n\"cultish\",\n\"cultism\",\n\"cultist\",\n\"cultual\",\n\"culture\",\n\"cultus\",\n\"culver\",\n\"culvert\",\n\"cum\",\n\"cumal\",\n\"cumay\",\n\"cumbent\",\n\"cumber\",\n\"cumbha\",\n\"cumbly\",\n\"cumbre\",\n\"cumbu\",\n\"cumene\",\n\"cumenyl\",\n\"cumhal\",\n\"cumic\",\n\"cumidin\",\n\"cumin\",\n\"cuminal\",\n\"cuminic\",\n\"cuminol\",\n\"cuminyl\",\n\"cummer\",\n\"cummin\",\n\"cumol\",\n\"cump\",\n\"cumshaw\",\n\"cumular\",\n\"cumuli\",\n\"cumulus\",\n\"cumyl\",\n\"cuneal\",\n\"cuneate\",\n\"cunette\",\n\"cuneus\",\n\"cunila\",\n\"cunjah\",\n\"cunjer\",\n\"cunner\",\n\"cunning\",\n\"cunye\",\n\"cuorin\",\n\"cup\",\n\"cupay\",\n\"cupcake\",\n\"cupel\",\n\"cupeler\",\n\"cupful\",\n\"cuphead\",\n\"cupidon\",\n\"cupless\",\n\"cupman\",\n\"cupmate\",\n\"cupola\",\n\"cupolar\",\n\"cupped\",\n\"cupper\",\n\"cupping\",\n\"cuppy\",\n\"cuprene\",\n\"cupric\",\n\"cupride\",\n\"cuprite\",\n\"cuproid\",\n\"cuprose\",\n\"cuprous\",\n\"cuprum\",\n\"cupseed\",\n\"cupula\",\n\"cupule\",\n\"cur\",\n\"curable\",\n\"curably\",\n\"curacao\",\n\"curacy\",\n\"curare\",\n\"curate\",\n\"curatel\",\n\"curatic\",\n\"curator\",\n\"curb\",\n\"curber\",\n\"curbing\",\n\"curby\",\n\"curcas\",\n\"curch\",\n\"curd\",\n\"curdle\",\n\"curdler\",\n\"curdly\",\n\"curdy\",\n\"cure\",\n\"curer\",\n\"curette\",\n\"curfew\",\n\"curial\",\n\"curiate\",\n\"curie\",\n\"curin\",\n\"curine\",\n\"curing\",\n\"curio\",\n\"curiosa\",\n\"curioso\",\n\"curious\",\n\"curite\",\n\"curium\",\n\"curl\",\n\"curled\",\n\"curler\",\n\"curlew\",\n\"curlike\",\n\"curlily\",\n\"curling\",\n\"curly\",\n\"curn\",\n\"curney\",\n\"curnock\",\n\"curple\",\n\"curr\",\n\"currach\",\n\"currack\",\n\"curragh\",\n\"currant\",\n\"current\",\n\"curried\",\n\"currier\",\n\"currish\",\n\"curry\",\n\"cursal\",\n\"curse\",\n\"cursed\",\n\"curser\",\n\"curship\",\n\"cursive\",\n\"cursor\",\n\"cursory\",\n\"curst\",\n\"curstly\",\n\"cursus\",\n\"curt\",\n\"curtail\",\n\"curtain\",\n\"curtal\",\n\"curtate\",\n\"curtesy\",\n\"curtly\",\n\"curtsy\",\n\"curua\",\n\"curuba\",\n\"curule\",\n\"cururo\",\n\"curvant\",\n\"curvate\",\n\"curve\",\n\"curved\",\n\"curver\",\n\"curvet\",\n\"curvity\",\n\"curvous\",\n\"curvy\",\n\"cuscus\",\n\"cusec\",\n\"cush\",\n\"cushag\",\n\"cushat\",\n\"cushaw\",\n\"cushion\",\n\"cushy\",\n\"cusie\",\n\"cusk\",\n\"cusp\",\n\"cuspal\",\n\"cuspate\",\n\"cusped\",\n\"cuspid\",\n\"cuspule\",\n\"cuss\",\n\"cussed\",\n\"cusser\",\n\"cusso\",\n\"custard\",\n\"custody\",\n\"custom\",\n\"customs\",\n\"cut\",\n\"cutaway\",\n\"cutback\",\n\"cutch\",\n\"cutcher\",\n\"cute\",\n\"cutely\",\n\"cutheal\",\n\"cuticle\",\n\"cutie\",\n\"cutin\",\n\"cutis\",\n\"cutitis\",\n\"cutlass\",\n\"cutler\",\n\"cutlery\",\n\"cutlet\",\n\"cutling\",\n\"cutlips\",\n\"cutoff\",\n\"cutout\",\n\"cutover\",\n\"cuttage\",\n\"cuttail\",\n\"cutted\",\n\"cutter\",\n\"cutting\",\n\"cuttle\",\n\"cuttler\",\n\"cuttoo\",\n\"cutty\",\n\"cutup\",\n\"cutweed\",\n\"cutwork\",\n\"cutworm\",\n\"cuvette\",\n\"cuvy\",\n\"cuya\",\n\"cwierc\",\n\"cwm\",\n\"cyan\",\n\"cyanate\",\n\"cyanean\",\n\"cyanic\",\n\"cyanide\",\n\"cyanin\",\n\"cyanine\",\n\"cyanite\",\n\"cyanize\",\n\"cyanol\",\n\"cyanole\",\n\"cyanose\",\n\"cyanus\",\n\"cyath\",\n\"cyathos\",\n\"cyathus\",\n\"cycad\",\n\"cyclane\",\n\"cyclar\",\n\"cyclas\",\n\"cycle\",\n\"cyclene\",\n\"cycler\",\n\"cyclian\",\n\"cyclic\",\n\"cyclide\",\n\"cycling\",\n\"cyclism\",\n\"cyclist\",\n\"cyclize\",\n\"cycloid\",\n\"cyclone\",\n\"cyclope\",\n\"cyclopy\",\n\"cyclose\",\n\"cyclus\",\n\"cyesis\",\n\"cygnet\",\n\"cygnine\",\n\"cyke\",\n\"cylix\",\n\"cyma\",\n\"cymar\",\n\"cymba\",\n\"cymbal\",\n\"cymbalo\",\n\"cymbate\",\n\"cyme\",\n\"cymelet\",\n\"cymene\",\n\"cymling\",\n\"cymoid\",\n\"cymose\",\n\"cymous\",\n\"cymule\",\n\"cynebot\",\n\"cynic\",\n\"cynical\",\n\"cynipid\",\n\"cynism\",\n\"cynoid\",\n\"cyp\",\n\"cypre\",\n\"cypres\",\n\"cypress\",\n\"cyprine\",\n\"cypsela\",\n\"cyrus\",\n\"cyst\",\n\"cystal\",\n\"cysted\",\n\"cystic\",\n\"cystid\",\n\"cystine\",\n\"cystis\",\n\"cystoid\",\n\"cystoma\",\n\"cystose\",\n\"cystous\",\n\"cytase\",\n\"cytasic\",\n\"cytitis\",\n\"cytode\",\n\"cytoid\",\n\"cytoma\",\n\"cyton\",\n\"cytost\",\n\"cytula\",\n\"czar\",\n\"czardas\",\n\"czardom\",\n\"czarian\",\n\"czaric\",\n\"czarina\",\n\"czarish\",\n\"czarism\",\n\"czarist\",\n\"d\",\n\"da\",\n\"daalder\",\n\"dab\",\n\"dabb\",\n\"dabba\",\n\"dabber\",\n\"dabble\",\n\"dabbler\",\n\"dabby\",\n\"dablet\",\n\"daboia\",\n\"daboya\",\n\"dabster\",\n\"dace\",\n\"dacite\",\n\"dacitic\",\n\"dacker\",\n\"dacoit\",\n\"dacoity\",\n\"dacryon\",\n\"dactyl\",\n\"dad\",\n\"dada\",\n\"dadap\",\n\"dadder\",\n\"daddle\",\n\"daddock\",\n\"daddy\",\n\"dade\",\n\"dado\",\n\"dae\",\n\"daedal\",\n\"daemon\",\n\"daemony\",\n\"daer\",\n\"daff\",\n\"daffery\",\n\"daffing\",\n\"daffish\",\n\"daffle\",\n\"daffy\",\n\"daft\",\n\"daftly\",\n\"dag\",\n\"dagaba\",\n\"dagame\",\n\"dagassa\",\n\"dagesh\",\n\"dagga\",\n\"dagger\",\n\"daggers\",\n\"daggle\",\n\"daggly\",\n\"daggy\",\n\"daghesh\",\n\"daglock\",\n\"dagoba\",\n\"dags\",\n\"dah\",\n\"dahoon\",\n\"daidle\",\n\"daidly\",\n\"daiker\",\n\"daikon\",\n\"daily\",\n\"daimen\",\n\"daimio\",\n\"daimon\",\n\"dain\",\n\"daincha\",\n\"dainty\",\n\"daira\",\n\"dairi\",\n\"dairy\",\n\"dais\",\n\"daisied\",\n\"daisy\",\n\"daitya\",\n\"daiva\",\n\"dak\",\n\"daker\",\n\"dakir\",\n\"dal\",\n\"dalar\",\n\"dale\",\n\"daleman\",\n\"daler\",\n\"daleth\",\n\"dali\",\n\"dalk\",\n\"dallack\",\n\"dalle\",\n\"dalles\",\n\"dallier\",\n\"dally\",\n\"dalt\",\n\"dalteen\",\n\"dalton\",\n\"dam\",\n\"dama\",\n\"damage\",\n\"damager\",\n\"damages\",\n\"daman\",\n\"damask\",\n\"damasse\",\n\"dambose\",\n\"dambrod\",\n\"dame\",\n\"damiana\",\n\"damie\",\n\"damier\",\n\"damine\",\n\"damlike\",\n\"dammar\",\n\"damme\",\n\"dammer\",\n\"dammish\",\n\"damn\",\n\"damned\",\n\"damner\",\n\"damnify\",\n\"damning\",\n\"damnous\",\n\"damp\",\n\"dampang\",\n\"damped\",\n\"dampen\",\n\"damper\",\n\"damping\",\n\"dampish\",\n\"damply\",\n\"dampy\",\n\"damsel\",\n\"damson\",\n\"dan\",\n\"danaid\",\n\"danaide\",\n\"danaine\",\n\"danaite\",\n\"dance\",\n\"dancer\",\n\"dancery\",\n\"dancing\",\n\"dand\",\n\"danda\",\n\"dander\",\n\"dandify\",\n\"dandily\",\n\"dandle\",\n\"dandler\",\n\"dandy\",\n\"dang\",\n\"danger\",\n\"dangle\",\n\"dangler\",\n\"danglin\",\n\"danio\",\n\"dank\",\n\"dankish\",\n\"dankly\",\n\"danli\",\n\"danner\",\n\"dannock\",\n\"dansant\",\n\"danta\",\n\"danton\",\n\"dao\",\n\"daoine\",\n\"dap\",\n\"daphnin\",\n\"dapicho\",\n\"dapico\",\n\"dapifer\",\n\"dapper\",\n\"dapple\",\n\"dappled\",\n\"dar\",\n\"darac\",\n\"daraf\",\n\"darat\",\n\"darbha\",\n\"darby\",\n\"dardaol\",\n\"dare\",\n\"dareall\",\n\"dareful\",\n\"darer\",\n\"daresay\",\n\"darg\",\n\"dargah\",\n\"darger\",\n\"dargue\",\n\"dari\",\n\"daribah\",\n\"daric\",\n\"daring\",\n\"dariole\",\n\"dark\",\n\"darken\",\n\"darkful\",\n\"darkish\",\n\"darkle\",\n\"darkly\",\n\"darky\",\n\"darling\",\n\"darn\",\n\"darned\",\n\"darnel\",\n\"darner\",\n\"darnex\",\n\"darning\",\n\"daroga\",\n\"daroo\",\n\"darr\",\n\"darrein\",\n\"darst\",\n\"dart\",\n\"dartars\",\n\"darter\",\n\"darting\",\n\"dartle\",\n\"dartman\",\n\"dartoic\",\n\"dartoid\",\n\"dartos\",\n\"dartre\",\n\"darts\",\n\"darzee\",\n\"das\",\n\"dash\",\n\"dashed\",\n\"dashee\",\n\"dasheen\",\n\"dasher\",\n\"dashing\",\n\"dashpot\",\n\"dashy\",\n\"dasi\",\n\"dasnt\",\n\"dassie\",\n\"dassy\",\n\"dastard\",\n\"dastur\",\n\"dasturi\",\n\"dasyure\",\n\"data\",\n\"datable\",\n\"datably\",\n\"dataria\",\n\"datary\",\n\"datch\",\n\"datcha\",\n\"date\",\n\"dater\",\n\"datil\",\n\"dating\",\n\"dation\",\n\"datival\",\n\"dative\",\n\"dattock\",\n\"datum\",\n\"daturic\",\n\"daub\",\n\"daube\",\n\"dauber\",\n\"daubery\",\n\"daubing\",\n\"dauby\",\n\"daud\",\n\"daunch\",\n\"dauncy\",\n\"daunt\",\n\"daunter\",\n\"daunton\",\n\"dauphin\",\n\"daut\",\n\"dautie\",\n\"dauw\",\n\"davach\",\n\"daven\",\n\"daver\",\n\"daverdy\",\n\"davit\",\n\"davoch\",\n\"davy\",\n\"davyne\",\n\"daw\",\n\"dawdle\",\n\"dawdler\",\n\"dawdy\",\n\"dawish\",\n\"dawkin\",\n\"dawn\",\n\"dawning\",\n\"dawny\",\n\"dawtet\",\n\"dawtit\",\n\"dawut\",\n\"day\",\n\"dayal\",\n\"daybeam\",\n\"daybook\",\n\"daydawn\",\n\"dayfly\",\n\"dayless\",\n\"daylit\",\n\"daylong\",\n\"dayman\",\n\"daymare\",\n\"daymark\",\n\"dayroom\",\n\"days\",\n\"daysman\",\n\"daystar\",\n\"daytale\",\n\"daytide\",\n\"daytime\",\n\"dayward\",\n\"daywork\",\n\"daywrit\",\n\"daze\",\n\"dazed\",\n\"dazedly\",\n\"dazy\",\n\"dazzle\",\n\"dazzler\",\n\"de\",\n\"deacon\",\n\"dead\",\n\"deaden\",\n\"deader\",\n\"deadeye\",\n\"deading\",\n\"deadish\",\n\"deadly\",\n\"deadman\",\n\"deadpan\",\n\"deadpay\",\n\"deaf\",\n\"deafen\",\n\"deafish\",\n\"deafly\",\n\"deair\",\n\"deal\",\n\"dealate\",\n\"dealer\",\n\"dealing\",\n\"dealt\",\n\"dean\",\n\"deaner\",\n\"deanery\",\n\"deaness\",\n\"dear\",\n\"dearie\",\n\"dearly\",\n\"dearth\",\n\"deary\",\n\"deash\",\n\"deasil\",\n\"death\",\n\"deathin\",\n\"deathly\",\n\"deathy\",\n\"deave\",\n\"deavely\",\n\"deb\",\n\"debacle\",\n\"debadge\",\n\"debar\",\n\"debark\",\n\"debase\",\n\"debaser\",\n\"debate\",\n\"debater\",\n\"debauch\",\n\"debby\",\n\"debeige\",\n\"deben\",\n\"debile\",\n\"debind\",\n\"debit\",\n\"debord\",\n\"debosh\",\n\"debouch\",\n\"debride\",\n\"debrief\",\n\"debris\",\n\"debt\",\n\"debtee\",\n\"debtful\",\n\"debtor\",\n\"debunk\",\n\"debus\",\n\"debut\",\n\"decad\",\n\"decadal\",\n\"decade\",\n\"decadic\",\n\"decafid\",\n\"decagon\",\n\"decal\",\n\"decamp\",\n\"decan\",\n\"decanal\",\n\"decane\",\n\"decani\",\n\"decant\",\n\"decap\",\n\"decapod\",\n\"decarch\",\n\"decare\",\n\"decart\",\n\"decast\",\n\"decate\",\n\"decator\",\n\"decatyl\",\n\"decay\",\n\"decayed\",\n\"decayer\",\n\"decease\",\n\"deceit\",\n\"deceive\",\n\"decence\",\n\"decency\",\n\"decene\",\n\"decent\",\n\"decenyl\",\n\"decern\",\n\"decess\",\n\"deciare\",\n\"decibel\",\n\"decide\",\n\"decided\",\n\"decider\",\n\"decidua\",\n\"decil\",\n\"decile\",\n\"decima\",\n\"decimal\",\n\"deck\",\n\"decke\",\n\"decked\",\n\"deckel\",\n\"decker\",\n\"deckie\",\n\"decking\",\n\"deckle\",\n\"declaim\",\n\"declare\",\n\"declass\",\n\"decline\",\n\"declive\",\n\"decoat\",\n\"decoct\",\n\"decode\",\n\"decoic\",\n\"decoke\",\n\"decolor\",\n\"decorum\",\n\"decoy\",\n\"decoyer\",\n\"decream\",\n\"decree\",\n\"decreer\",\n\"decreet\",\n\"decrete\",\n\"decrew\",\n\"decrial\",\n\"decried\",\n\"decrier\",\n\"decrown\",\n\"decry\",\n\"decuman\",\n\"decuple\",\n\"decuria\",\n\"decurve\",\n\"decury\",\n\"decus\",\n\"decyl\",\n\"decylic\",\n\"decyne\",\n\"dedimus\",\n\"dedo\",\n\"deduce\",\n\"deduct\",\n\"dee\",\n\"deed\",\n\"deedbox\",\n\"deedeed\",\n\"deedful\",\n\"deedily\",\n\"deedy\",\n\"deem\",\n\"deemer\",\n\"deemie\",\n\"deep\",\n\"deepen\",\n\"deeping\",\n\"deepish\",\n\"deeply\",\n\"deer\",\n\"deerdog\",\n\"deerlet\",\n\"deevey\",\n\"deface\",\n\"defacer\",\n\"defalk\",\n\"defame\",\n\"defamed\",\n\"defamer\",\n\"defassa\",\n\"defat\",\n\"default\",\n\"defease\",\n\"defeat\",\n\"defect\",\n\"defence\",\n\"defend\",\n\"defense\",\n\"defer\",\n\"defial\",\n\"defiant\",\n\"defiber\",\n\"deficit\",\n\"defier\",\n\"defile\",\n\"defiled\",\n\"defiler\",\n\"define\",\n\"defined\",\n\"definer\",\n\"deflate\",\n\"deflect\",\n\"deflesh\",\n\"deflex\",\n\"defog\",\n\"deforce\",\n\"deform\",\n\"defoul\",\n\"defraud\",\n\"defray\",\n\"defrock\",\n\"defrost\",\n\"deft\",\n\"deftly\",\n\"defunct\",\n\"defuse\",\n\"defy\",\n\"deg\",\n\"degas\",\n\"degauss\",\n\"degerm\",\n\"degged\",\n\"degger\",\n\"deglaze\",\n\"degorge\",\n\"degrade\",\n\"degrain\",\n\"degree\",\n\"degu\",\n\"degum\",\n\"degust\",\n\"dehair\",\n\"dehisce\",\n\"dehorn\",\n\"dehors\",\n\"dehort\",\n\"dehull\",\n\"dehusk\",\n\"deice\",\n\"deicer\",\n\"deicide\",\n\"deictic\",\n\"deific\",\n\"deifier\",\n\"deiform\",\n\"deify\",\n\"deign\",\n\"deink\",\n\"deinos\",\n\"deiseal\",\n\"deism\",\n\"deist\",\n\"deistic\",\n\"deity\",\n\"deject\",\n\"dejecta\",\n\"dejeune\",\n\"dekko\",\n\"dekle\",\n\"delaine\",\n\"delapse\",\n\"delate\",\n\"delater\",\n\"delator\",\n\"delawn\",\n\"delay\",\n\"delayer\",\n\"dele\",\n\"delead\",\n\"delenda\",\n\"delete\",\n\"delf\",\n\"delft\",\n\"delible\",\n\"delict\",\n\"delight\",\n\"delime\",\n\"delimit\",\n\"delint\",\n\"deliver\",\n\"dell\",\n\"deloul\",\n\"delouse\",\n\"delta\",\n\"deltaic\",\n\"deltal\",\n\"deltic\",\n\"deltoid\",\n\"delude\",\n\"deluder\",\n\"deluge\",\n\"deluxe\",\n\"delve\",\n\"delver\",\n\"demagog\",\n\"demal\",\n\"demand\",\n\"demarch\",\n\"demark\",\n\"demast\",\n\"deme\",\n\"demean\",\n\"demency\",\n\"dement\",\n\"demerit\",\n\"demesne\",\n\"demi\",\n\"demibob\",\n\"demidog\",\n\"demigod\",\n\"demihag\",\n\"demiman\",\n\"demiowl\",\n\"demiox\",\n\"demiram\",\n\"demirep\",\n\"demise\",\n\"demiss\",\n\"demit\",\n\"demivol\",\n\"demob\",\n\"demoded\",\n\"demoid\",\n\"demon\",\n\"demonic\",\n\"demonry\",\n\"demos\",\n\"demote\",\n\"demotic\",\n\"demount\",\n\"demulce\",\n\"demure\",\n\"demy\",\n\"den\",\n\"denaro\",\n\"denary\",\n\"denat\",\n\"denda\",\n\"dendral\",\n\"dendric\",\n\"dendron\",\n\"dene\",\n\"dengue\",\n\"denial\",\n\"denier\",\n\"denim\",\n\"denizen\",\n\"dennet\",\n\"denote\",\n\"dense\",\n\"densely\",\n\"densen\",\n\"densher\",\n\"densify\",\n\"density\",\n\"dent\",\n\"dental\",\n\"dentale\",\n\"dentary\",\n\"dentata\",\n\"dentate\",\n\"dentel\",\n\"denter\",\n\"dentex\",\n\"dentil\",\n\"dentile\",\n\"dentin\",\n\"dentine\",\n\"dentist\",\n\"dentoid\",\n\"denture\",\n\"denty\",\n\"denude\",\n\"denuder\",\n\"deny\",\n\"deodand\",\n\"deodara\",\n\"deota\",\n\"depa\",\n\"depaint\",\n\"depark\",\n\"depart\",\n\"depas\",\n\"depass\",\n\"depend\",\n\"depeter\",\n\"dephase\",\n\"depict\",\n\"deplane\",\n\"deplete\",\n\"deplore\",\n\"deploy\",\n\"deplume\",\n\"deplump\",\n\"depoh\",\n\"depone\",\n\"deport\",\n\"deposal\",\n\"depose\",\n\"deposer\",\n\"deposit\",\n\"depot\",\n\"deprave\",\n\"depress\",\n\"deprint\",\n\"deprive\",\n\"depside\",\n\"depth\",\n\"depthen\",\n\"depute\",\n\"deputy\",\n\"dequeen\",\n\"derah\",\n\"deraign\",\n\"derail\",\n\"derange\",\n\"derat\",\n\"derate\",\n\"derater\",\n\"deray\",\n\"derby\",\n\"dere\",\n\"dereism\",\n\"deric\",\n\"deride\",\n\"derider\",\n\"derival\",\n\"derive\",\n\"derived\",\n\"deriver\",\n\"derm\",\n\"derma\",\n\"dermad\",\n\"dermal\",\n\"dermic\",\n\"dermis\",\n\"dermoid\",\n\"dermol\",\n\"dern\",\n\"dernier\",\n\"derout\",\n\"derrick\",\n\"derride\",\n\"derries\",\n\"derry\",\n\"dertrum\",\n\"derust\",\n\"dervish\",\n\"desalt\",\n\"desand\",\n\"descale\",\n\"descant\",\n\"descend\",\n\"descent\",\n\"descort\",\n\"descry\",\n\"deseed\",\n\"deseret\",\n\"desert\",\n\"deserve\",\n\"desex\",\n\"desi\",\n\"desight\",\n\"design\",\n\"desire\",\n\"desired\",\n\"desirer\",\n\"desist\",\n\"desize\",\n\"desk\",\n\"deslime\",\n\"desma\",\n\"desman\",\n\"desmic\",\n\"desmid\",\n\"desmine\",\n\"desmoid\",\n\"desmoma\",\n\"desmon\",\n\"despair\",\n\"despect\",\n\"despise\",\n\"despite\",\n\"despoil\",\n\"despond\",\n\"despot\",\n\"dess\",\n\"dessa\",\n\"dessert\",\n\"dessil\",\n\"destain\",\n\"destine\",\n\"destiny\",\n\"destour\",\n\"destroy\",\n\"desuete\",\n\"desugar\",\n\"desyl\",\n\"detach\",\n\"detail\",\n\"detain\",\n\"detar\",\n\"detax\",\n\"detect\",\n\"detent\",\n\"deter\",\n\"deterge\",\n\"detest\",\n\"detin\",\n\"detinet\",\n\"detinue\",\n\"detour\",\n\"detract\",\n\"detrain\",\n\"detrude\",\n\"detune\",\n\"detur\",\n\"deuce\",\n\"deuced\",\n\"deul\",\n\"deuton\",\n\"dev\",\n\"deva\",\n\"devall\",\n\"devalue\",\n\"devance\",\n\"devast\",\n\"devata\",\n\"develin\",\n\"develop\",\n\"devest\",\n\"deviant\",\n\"deviate\",\n\"device\",\n\"devil\",\n\"deviled\",\n\"deviler\",\n\"devilet\",\n\"devilry\",\n\"devily\",\n\"devious\",\n\"devisal\",\n\"devise\",\n\"devisee\",\n\"deviser\",\n\"devisor\",\n\"devoice\",\n\"devoid\",\n\"devoir\",\n\"devolve\",\n\"devote\",\n\"devoted\",\n\"devotee\",\n\"devoter\",\n\"devour\",\n\"devout\",\n\"devow\",\n\"devvel\",\n\"dew\",\n\"dewan\",\n\"dewanee\",\n\"dewater\",\n\"dewax\",\n\"dewbeam\",\n\"dewclaw\",\n\"dewcup\",\n\"dewdamp\",\n\"dewdrop\",\n\"dewer\",\n\"dewfall\",\n\"dewily\",\n\"dewlap\",\n\"dewless\",\n\"dewlike\",\n\"dewool\",\n\"deworm\",\n\"dewret\",\n\"dewtry\",\n\"dewworm\",\n\"dewy\",\n\"dexter\",\n\"dextrad\",\n\"dextral\",\n\"dextran\",\n\"dextrin\",\n\"dextro\",\n\"dey\",\n\"deyship\",\n\"dezinc\",\n\"dha\",\n\"dhabb\",\n\"dhai\",\n\"dhak\",\n\"dhamnoo\",\n\"dhan\",\n\"dhangar\",\n\"dhanuk\",\n\"dhanush\",\n\"dharana\",\n\"dharani\",\n\"dharma\",\n\"dharna\",\n\"dhaura\",\n\"dhauri\",\n\"dhava\",\n\"dhaw\",\n\"dheri\",\n\"dhobi\",\n\"dhole\",\n\"dhoni\",\n\"dhoon\",\n\"dhoti\",\n\"dhoul\",\n\"dhow\",\n\"dhu\",\n\"dhunchi\",\n\"dhurra\",\n\"dhyal\",\n\"dhyana\",\n\"di\",\n\"diabase\",\n\"diacid\",\n\"diacle\",\n\"diacope\",\n\"diact\",\n\"diactin\",\n\"diadem\",\n\"diaderm\",\n\"diaene\",\n\"diagram\",\n\"dial\",\n\"dialect\",\n\"dialer\",\n\"dialin\",\n\"dialing\",\n\"dialist\",\n\"dialkyl\",\n\"diallel\",\n\"diallyl\",\n\"dialyze\",\n\"diamb\",\n\"diambic\",\n\"diamide\",\n\"diamine\",\n\"diamond\",\n\"dian\",\n\"diander\",\n\"dianite\",\n\"diapase\",\n\"diapasm\",\n\"diaper\",\n\"diaplex\",\n\"diapsid\",\n\"diarch\",\n\"diarchy\",\n\"diarial\",\n\"diarian\",\n\"diarist\",\n\"diarize\",\n\"diary\",\n\"diastem\",\n\"diaster\",\n\"diasyrm\",\n\"diatom\",\n\"diaulic\",\n\"diaulos\",\n\"diaxial\",\n\"diaxon\",\n\"diazide\",\n\"diazine\",\n\"diazoic\",\n\"diazole\",\n\"diazoma\",\n\"dib\",\n\"dibase\",\n\"dibasic\",\n\"dibatag\",\n\"dibber\",\n\"dibble\",\n\"dibbler\",\n\"dibbuk\",\n\"dibhole\",\n\"dibrach\",\n\"dibrom\",\n\"dibs\",\n\"dicast\",\n\"dice\",\n\"dicebox\",\n\"dicecup\",\n\"diceman\",\n\"dicer\",\n\"dicetyl\",\n\"dich\",\n\"dichas\",\n\"dichord\",\n\"dicing\",\n\"dick\",\n\"dickens\",\n\"dicker\",\n\"dickey\",\n\"dicky\",\n\"dicolic\",\n\"dicolon\",\n\"dicot\",\n\"dicotyl\",\n\"dicta\",\n\"dictate\",\n\"dictic\",\n\"diction\",\n\"dictum\",\n\"dicycle\",\n\"did\",\n\"didder\",\n\"diddle\",\n\"diddler\",\n\"diddy\",\n\"didelph\",\n\"didie\",\n\"didine\",\n\"didle\",\n\"didna\",\n\"didnt\",\n\"didromy\",\n\"didst\",\n\"didym\",\n\"didymia\",\n\"didymus\",\n\"die\",\n\"dieb\",\n\"dieback\",\n\"diedral\",\n\"diedric\",\n\"diehard\",\n\"dielike\",\n\"diem\",\n\"diene\",\n\"dier\",\n\"diesel\",\n\"diesis\",\n\"diet\",\n\"dietal\",\n\"dietary\",\n\"dieter\",\n\"diethyl\",\n\"dietic\",\n\"dietics\",\n\"dietine\",\n\"dietist\",\n\"diewise\",\n\"diffame\",\n\"differ\",\n\"diffide\",\n\"difform\",\n\"diffuse\",\n\"dig\",\n\"digamma\",\n\"digamy\",\n\"digenic\",\n\"digeny\",\n\"digest\",\n\"digger\",\n\"digging\",\n\"dight\",\n\"dighter\",\n\"digit\",\n\"digital\",\n\"digitus\",\n\"diglot\",\n\"diglyph\",\n\"digmeat\",\n\"dignify\",\n\"dignity\",\n\"digram\",\n\"digraph\",\n\"digress\",\n\"digs\",\n\"dihalo\",\n\"diiamb\",\n\"diiodo\",\n\"dika\",\n\"dikage\",\n\"dike\",\n\"diker\",\n\"diketo\",\n\"dikkop\",\n\"dilate\",\n\"dilated\",\n\"dilater\",\n\"dilator\",\n\"dildo\",\n\"dilemma\",\n\"dilker\",\n\"dill\",\n\"dilli\",\n\"dillier\",\n\"dilling\",\n\"dillue\",\n\"dilluer\",\n\"dilly\",\n\"dilo\",\n\"dilogy\",\n\"diluent\",\n\"dilute\",\n\"diluted\",\n\"dilutee\",\n\"diluter\",\n\"dilutor\",\n\"diluvia\",\n\"dim\",\n\"dimber\",\n\"dimble\",\n\"dime\",\n\"dimer\",\n\"dimeran\",\n\"dimeric\",\n\"dimeter\",\n\"dimiss\",\n\"dimit\",\n\"dimity\",\n\"dimly\",\n\"dimmed\",\n\"dimmer\",\n\"dimmest\",\n\"dimmet\",\n\"dimmish\",\n\"dimness\",\n\"dimoric\",\n\"dimorph\",\n\"dimple\",\n\"dimply\",\n\"dimps\",\n\"dimpsy\",\n\"din\",\n\"dinar\",\n\"dinder\",\n\"dindle\",\n\"dine\",\n\"diner\",\n\"dineric\",\n\"dinero\",\n\"dinette\",\n\"ding\",\n\"dingar\",\n\"dingbat\",\n\"dinge\",\n\"dingee\",\n\"dinghee\",\n\"dinghy\",\n\"dingily\",\n\"dingle\",\n\"dingly\",\n\"dingo\",\n\"dingus\",\n\"dingy\",\n\"dinic\",\n\"dinical\",\n\"dining\",\n\"dinitro\",\n\"dink\",\n\"dinkey\",\n\"dinkum\",\n\"dinky\",\n\"dinmont\",\n\"dinner\",\n\"dinnery\",\n\"dinomic\",\n\"dinsome\",\n\"dint\",\n\"dinus\",\n\"diobely\",\n\"diobol\",\n\"diocese\",\n\"diode\",\n\"diodont\",\n\"dioecy\",\n\"diol\",\n\"dionise\",\n\"dionym\",\n\"diopter\",\n\"dioptra\",\n\"dioptry\",\n\"diorama\",\n\"diorite\",\n\"diose\",\n\"diosmin\",\n\"diota\",\n\"diotic\",\n\"dioxane\",\n\"dioxide\",\n\"dioxime\",\n\"dioxy\",\n\"dip\",\n\"dipetto\",\n\"diphase\",\n\"diphead\",\n\"diplex\",\n\"diploe\",\n\"diploic\",\n\"diploid\",\n\"diplois\",\n\"diploma\",\n\"diplont\",\n\"diplopy\",\n\"dipnoan\",\n\"dipnoid\",\n\"dipode\",\n\"dipodic\",\n\"dipody\",\n\"dipolar\",\n\"dipole\",\n\"diporpa\",\n\"dipped\",\n\"dipper\",\n\"dipping\",\n\"dipsas\",\n\"dipsey\",\n\"dipter\",\n\"diptote\",\n\"diptych\",\n\"dipware\",\n\"dipygus\",\n\"dipylon\",\n\"dipyre\",\n\"dird\",\n\"dirdum\",\n\"dire\",\n\"direct\",\n\"direful\",\n\"direly\",\n\"dirempt\",\n\"dirge\",\n\"dirgler\",\n\"dirhem\",\n\"dirk\",\n\"dirl\",\n\"dirndl\",\n\"dirt\",\n\"dirten\",\n\"dirtily\",\n\"dirty\",\n\"dis\",\n\"disable\",\n\"disagio\",\n\"disally\",\n\"disarm\",\n\"disavow\",\n\"disawa\",\n\"disazo\",\n\"disband\",\n\"disbar\",\n\"disbark\",\n\"disbody\",\n\"disbud\",\n\"disbury\",\n\"disc\",\n\"discage\",\n\"discal\",\n\"discard\",\n\"discase\",\n\"discept\",\n\"discern\",\n\"discerp\",\n\"discoid\",\n\"discord\",\n\"discous\",\n\"discus\",\n\"discuss\",\n\"disdain\",\n\"disdub\",\n\"disease\",\n\"disedge\",\n\"diseme\",\n\"disemic\",\n\"disfame\",\n\"disfen\",\n\"disgig\",\n\"disglut\",\n\"disgood\",\n\"disgown\",\n\"disgulf\",\n\"disgust\",\n\"dish\",\n\"dished\",\n\"dishelm\",\n\"disher\",\n\"dishful\",\n\"dishome\",\n\"dishorn\",\n\"dishpan\",\n\"dishrag\",\n\"disject\",\n\"disjoin\",\n\"disjune\",\n\"disk\",\n\"disleaf\",\n\"dislike\",\n\"dislimn\",\n\"dislink\",\n\"dislip\",\n\"disload\",\n\"dislove\",\n\"dismain\",\n\"dismal\",\n\"disman\",\n\"dismark\",\n\"dismask\",\n\"dismast\",\n\"dismay\",\n\"disme\",\n\"dismiss\",\n\"disna\",\n\"disnest\",\n\"disnew\",\n\"disobey\",\n\"disodic\",\n\"disomic\",\n\"disomus\",\n\"disorb\",\n\"disown\",\n\"dispark\",\n\"dispart\",\n\"dispel\",\n\"dispend\",\n\"display\",\n\"dispone\",\n\"dispope\",\n\"disport\",\n\"dispose\",\n\"dispost\",\n\"dispulp\",\n\"dispute\",\n\"disrank\",\n\"disrate\",\n\"disring\",\n\"disrobe\",\n\"disroof\",\n\"disroot\",\n\"disrump\",\n\"disrupt\",\n\"diss\",\n\"disseat\",\n\"dissect\",\n\"dissent\",\n\"dissert\",\n\"dissoul\",\n\"dissuit\",\n\"distad\",\n\"distaff\",\n\"distain\",\n\"distal\",\n\"distale\",\n\"distant\",\n\"distend\",\n\"distent\",\n\"distich\",\n\"distill\",\n\"distome\",\n\"distort\",\n\"distune\",\n\"disturb\",\n\"disturn\",\n\"disuse\",\n\"diswood\",\n\"disyoke\",\n\"dit\",\n\"dita\",\n\"dital\",\n\"ditch\",\n\"ditcher\",\n\"dite\",\n\"diter\",\n\"dither\",\n\"dithery\",\n\"dithion\",\n\"ditolyl\",\n\"ditone\",\n\"dittamy\",\n\"dittany\",\n\"dittay\",\n\"dittied\",\n\"ditto\",\n\"ditty\",\n\"diurnal\",\n\"diurne\",\n\"div\",\n\"diva\",\n\"divan\",\n\"divata\",\n\"dive\",\n\"divel\",\n\"diver\",\n\"diverge\",\n\"divers\",\n\"diverse\",\n\"divert\",\n\"divest\",\n\"divide\",\n\"divided\",\n\"divider\",\n\"divine\",\n\"diviner\",\n\"diving\",\n\"divinyl\",\n\"divisor\",\n\"divorce\",\n\"divot\",\n\"divoto\",\n\"divulge\",\n\"divulse\",\n\"divus\",\n\"divvy\",\n\"diwata\",\n\"dixie\",\n\"dixit\",\n\"dixy\",\n\"dizain\",\n\"dizen\",\n\"dizoic\",\n\"dizzard\",\n\"dizzily\",\n\"dizzy\",\n\"djave\",\n\"djehad\",\n\"djerib\",\n\"djersa\",\n\"do\",\n\"doab\",\n\"doable\",\n\"doarium\",\n\"doat\",\n\"doated\",\n\"doater\",\n\"doating\",\n\"doatish\",\n\"dob\",\n\"dobbed\",\n\"dobber\",\n\"dobbin\",\n\"dobbing\",\n\"dobby\",\n\"dobe\",\n\"dobla\",\n\"doblon\",\n\"dobra\",\n\"dobrao\",\n\"dobson\",\n\"doby\",\n\"doc\",\n\"docent\",\n\"docible\",\n\"docile\",\n\"docity\",\n\"dock\",\n\"dockage\",\n\"docken\",\n\"docker\",\n\"docket\",\n\"dockize\",\n\"dockman\",\n\"docmac\",\n\"doctor\",\n\"doctrix\",\n\"dod\",\n\"dodd\",\n\"doddart\",\n\"dodded\",\n\"dodder\",\n\"doddery\",\n\"doddie\",\n\"dodding\",\n\"doddle\",\n\"doddy\",\n\"dodecyl\",\n\"dodge\",\n\"dodger\",\n\"dodgery\",\n\"dodgily\",\n\"dodgy\",\n\"dodkin\",\n\"dodlet\",\n\"dodman\",\n\"dodo\",\n\"dodoism\",\n\"dodrans\",\n\"doe\",\n\"doebird\",\n\"doeglic\",\n\"doer\",\n\"does\",\n\"doeskin\",\n\"doesnt\",\n\"doest\",\n\"doff\",\n\"doffer\",\n\"dog\",\n\"dogal\",\n\"dogate\",\n\"dogbane\",\n\"dogbite\",\n\"dogblow\",\n\"dogboat\",\n\"dogbolt\",\n\"dogbush\",\n\"dogcart\",\n\"dogdom\",\n\"doge\",\n\"dogedom\",\n\"dogface\",\n\"dogfall\",\n\"dogfish\",\n\"dogfoot\",\n\"dogged\",\n\"dogger\",\n\"doggery\",\n\"doggess\",\n\"doggish\",\n\"doggo\",\n\"doggone\",\n\"doggrel\",\n\"doggy\",\n\"doghead\",\n\"doghole\",\n\"doghood\",\n\"dogie\",\n\"dogless\",\n\"doglike\",\n\"dogly\",\n\"dogma\",\n\"dogman\",\n\"dogmata\",\n\"dogs\",\n\"dogship\",\n\"dogskin\",\n\"dogtail\",\n\"dogtie\",\n\"dogtrot\",\n\"dogvane\",\n\"dogwood\",\n\"dogy\",\n\"doigt\",\n\"doiled\",\n\"doily\",\n\"doina\",\n\"doing\",\n\"doings\",\n\"doit\",\n\"doited\",\n\"doitkin\",\n\"doke\",\n\"dokhma\",\n\"dola\",\n\"dolabra\",\n\"dolcan\",\n\"dolcian\",\n\"dolcino\",\n\"doldrum\",\n\"dole\",\n\"doleful\",\n\"dolent\",\n\"doless\",\n\"doli\",\n\"dolia\",\n\"dolina\",\n\"doline\",\n\"dolium\",\n\"doll\",\n\"dollar\",\n\"dolldom\",\n\"dollier\",\n\"dollish\",\n\"dollop\",\n\"dolly\",\n\"dolman\",\n\"dolmen\",\n\"dolor\",\n\"dolose\",\n\"dolous\",\n\"dolphin\",\n\"dolt\",\n\"doltish\",\n\"dom\",\n\"domain\",\n\"domal\",\n\"domba\",\n\"dome\",\n\"doment\",\n\"domer\",\n\"domett\",\n\"domic\",\n\"domical\",\n\"domine\",\n\"dominie\",\n\"domino\",\n\"dominus\",\n\"domite\",\n\"domitic\",\n\"domn\",\n\"domnei\",\n\"domoid\",\n\"dompt\",\n\"domy\",\n\"don\",\n\"donable\",\n\"donary\",\n\"donate\",\n\"donated\",\n\"donatee\",\n\"donator\",\n\"donax\",\n\"done\",\n\"donee\",\n\"doney\",\n\"dong\",\n\"donga\",\n\"dongon\",\n\"donjon\",\n\"donkey\",\n\"donna\",\n\"donnert\",\n\"donnish\",\n\"donnism\",\n\"donnot\",\n\"donor\",\n\"donship\",\n\"donsie\",\n\"dont\",\n\"donum\",\n\"doob\",\n\"doocot\",\n\"doodab\",\n\"doodad\",\n\"doodle\",\n\"doodler\",\n\"dooja\",\n\"dook\",\n\"dooket\",\n\"dookit\",\n\"dool\",\n\"doolee\",\n\"dooley\",\n\"dooli\",\n\"doolie\",\n\"dooly\",\n\"doom\",\n\"doomage\",\n\"doomer\",\n\"doomful\",\n\"dooms\",\n\"doon\",\n\"door\",\n\"doorba\",\n\"doorboy\",\n\"doored\",\n\"doorman\",\n\"doorway\",\n\"dop\",\n\"dopa\",\n\"dopatta\",\n\"dope\",\n\"doper\",\n\"dopey\",\n\"dopper\",\n\"doppia\",\n\"dor\",\n\"dorab\",\n\"dorad\",\n\"dorado\",\n\"doree\",\n\"dorhawk\",\n\"doria\",\n\"dorje\",\n\"dorlach\",\n\"dorlot\",\n\"dorm\",\n\"dormant\",\n\"dormer\",\n\"dormie\",\n\"dormy\",\n\"dorn\",\n\"dorneck\",\n\"dornic\",\n\"dornick\",\n\"dornock\",\n\"dorp\",\n\"dorsad\",\n\"dorsal\",\n\"dorsale\",\n\"dorsel\",\n\"dorser\",\n\"dorsum\",\n\"dorter\",\n\"dorts\",\n\"dorty\",\n\"doruck\",\n\"dory\",\n\"dos\",\n\"dosa\",\n\"dosadh\",\n\"dosage\",\n\"dose\",\n\"doser\",\n\"dosis\",\n\"doss\",\n\"dossal\",\n\"dossel\",\n\"dosser\",\n\"dossier\",\n\"dossil\",\n\"dossman\",\n\"dot\",\n\"dotage\",\n\"dotal\",\n\"dotard\",\n\"dotardy\",\n\"dotate\",\n\"dotchin\",\n\"dote\",\n\"doted\",\n\"doter\",\n\"doting\",\n\"dotish\",\n\"dotkin\",\n\"dotless\",\n\"dotlike\",\n\"dotted\",\n\"dotter\",\n\"dottily\",\n\"dotting\",\n\"dottle\",\n\"dottler\",\n\"dotty\",\n\"doty\",\n\"douar\",\n\"double\",\n\"doubled\",\n\"doubler\",\n\"doublet\",\n\"doubly\",\n\"doubt\",\n\"doubter\",\n\"douc\",\n\"douce\",\n\"doucely\",\n\"doucet\",\n\"douche\",\n\"doucin\",\n\"doucine\",\n\"doudle\",\n\"dough\",\n\"dought\",\n\"doughty\",\n\"doughy\",\n\"doum\",\n\"doup\",\n\"douping\",\n\"dour\",\n\"dourine\",\n\"dourly\",\n\"douse\",\n\"douser\",\n\"dout\",\n\"douter\",\n\"doutous\",\n\"dove\",\n\"dovecot\",\n\"dovekey\",\n\"dovekie\",\n\"dovelet\",\n\"dover\",\n\"dovish\",\n\"dow\",\n\"dowable\",\n\"dowager\",\n\"dowcet\",\n\"dowd\",\n\"dowdily\",\n\"dowdy\",\n\"dowed\",\n\"dowel\",\n\"dower\",\n\"doweral\",\n\"dowery\",\n\"dowf\",\n\"dowie\",\n\"dowily\",\n\"dowitch\",\n\"dowl\",\n\"dowlas\",\n\"dowless\",\n\"down\",\n\"downby\",\n\"downcry\",\n\"downcut\",\n\"downer\",\n\"downily\",\n\"downlie\",\n\"downset\",\n\"downway\",\n\"downy\",\n\"dowp\",\n\"dowry\",\n\"dowse\",\n\"dowser\",\n\"dowset\",\n\"doxa\",\n\"doxy\",\n\"doze\",\n\"dozed\",\n\"dozen\",\n\"dozener\",\n\"dozenth\",\n\"dozer\",\n\"dozily\",\n\"dozy\",\n\"dozzled\",\n\"drab\",\n\"drabbet\",\n\"drabble\",\n\"drabby\",\n\"drably\",\n\"drachm\",\n\"drachma\",\n\"dracma\",\n\"draff\",\n\"draffy\",\n\"draft\",\n\"draftee\",\n\"drafter\",\n\"drafty\",\n\"drag\",\n\"dragade\",\n\"dragbar\",\n\"dragged\",\n\"dragger\",\n\"draggle\",\n\"draggly\",\n\"draggy\",\n\"dragman\",\n\"dragnet\",\n\"drago\",\n\"dragon\",\n\"dragoon\",\n\"dragsaw\",\n\"drail\",\n\"drain\",\n\"draine\",\n\"drained\",\n\"drainer\",\n\"drake\",\n\"dram\",\n\"drama\",\n\"dramm\",\n\"dramme\",\n\"drammed\",\n\"drammer\",\n\"drang\",\n\"drank\",\n\"drant\",\n\"drape\",\n\"draper\",\n\"drapery\",\n\"drassid\",\n\"drastic\",\n\"drat\",\n\"drate\",\n\"dratted\",\n\"draught\",\n\"dravya\",\n\"draw\",\n\"drawarm\",\n\"drawbar\",\n\"drawboy\",\n\"drawcut\",\n\"drawee\",\n\"drawer\",\n\"drawers\",\n\"drawing\",\n\"drawk\",\n\"drawl\",\n\"drawler\",\n\"drawly\",\n\"drawn\",\n\"drawnet\",\n\"drawoff\",\n\"drawout\",\n\"drawrod\",\n\"dray\",\n\"drayage\",\n\"drayman\",\n\"drazel\",\n\"dread\",\n\"dreader\",\n\"dreadly\",\n\"dream\",\n\"dreamer\",\n\"dreamsy\",\n\"dreamt\",\n\"dreamy\",\n\"drear\",\n\"drearly\",\n\"dreary\",\n\"dredge\",\n\"dredger\",\n\"dree\",\n\"dreep\",\n\"dreepy\",\n\"dreg\",\n\"dreggy\",\n\"dregs\",\n\"drench\",\n\"dreng\",\n\"dress\",\n\"dressed\",\n\"dresser\",\n\"dressy\",\n\"drest\",\n\"drew\",\n\"drewite\",\n\"drias\",\n\"drib\",\n\"dribble\",\n\"driblet\",\n\"driddle\",\n\"dried\",\n\"drier\",\n\"driest\",\n\"drift\",\n\"drifter\",\n\"drifty\",\n\"drill\",\n\"driller\",\n\"drillet\",\n\"dringle\",\n\"drink\",\n\"drinker\",\n\"drinn\",\n\"drip\",\n\"dripper\",\n\"dripple\",\n\"drippy\",\n\"drisk\",\n\"drivage\",\n\"drive\",\n\"drivel\",\n\"driven\",\n\"driver\",\n\"driving\",\n\"drizzle\",\n\"drizzly\",\n\"droddum\",\n\"drogh\",\n\"drogher\",\n\"drogue\",\n\"droit\",\n\"droll\",\n\"drolly\",\n\"drome\",\n\"dromic\",\n\"dromond\",\n\"dromos\",\n\"drona\",\n\"dronage\",\n\"drone\",\n\"droner\",\n\"drongo\",\n\"dronish\",\n\"drony\",\n\"drool\",\n\"droop\",\n\"drooper\",\n\"droopt\",\n\"droopy\",\n\"drop\",\n\"droplet\",\n\"dropman\",\n\"dropout\",\n\"dropper\",\n\"droppy\",\n\"dropsy\",\n\"dropt\",\n\"droshky\",\n\"drosky\",\n\"dross\",\n\"drossel\",\n\"drosser\",\n\"drossy\",\n\"drostdy\",\n\"droud\",\n\"drought\",\n\"drouk\",\n\"drove\",\n\"drover\",\n\"drovy\",\n\"drow\",\n\"drown\",\n\"drowner\",\n\"drowse\",\n\"drowsy\",\n\"drub\",\n\"drubber\",\n\"drubbly\",\n\"drucken\",\n\"drudge\",\n\"drudger\",\n\"druery\",\n\"drug\",\n\"drugger\",\n\"drugget\",\n\"druggy\",\n\"drugman\",\n\"druid\",\n\"druidic\",\n\"druidry\",\n\"druith\",\n\"drum\",\n\"drumble\",\n\"drumlin\",\n\"drumly\",\n\"drummer\",\n\"drummy\",\n\"drung\",\n\"drungar\",\n\"drunk\",\n\"drunken\",\n\"drupal\",\n\"drupe\",\n\"drupel\",\n\"druse\",\n\"drusy\",\n\"druxy\",\n\"dry\",\n\"dryad\",\n\"dryadic\",\n\"dryas\",\n\"drycoal\",\n\"dryfoot\",\n\"drying\",\n\"dryish\",\n\"dryly\",\n\"dryness\",\n\"dryster\",\n\"dryth\",\n\"duad\",\n\"duadic\",\n\"dual\",\n\"duali\",\n\"dualin\",\n\"dualism\",\n\"dualist\",\n\"duality\",\n\"dualize\",\n\"dually\",\n\"duarch\",\n\"duarchy\",\n\"dub\",\n\"dubash\",\n\"dubb\",\n\"dubba\",\n\"dubbah\",\n\"dubber\",\n\"dubbing\",\n\"dubby\",\n\"dubiety\",\n\"dubious\",\n\"dubs\",\n\"ducal\",\n\"ducally\",\n\"ducape\",\n\"ducat\",\n\"ducato\",\n\"ducdame\",\n\"duces\",\n\"duchess\",\n\"duchy\",\n\"duck\",\n\"ducker\",\n\"duckery\",\n\"duckie\",\n\"ducking\",\n\"duckpin\",\n\"duct\",\n\"ducted\",\n\"ductile\",\n\"duction\",\n\"ductor\",\n\"ductule\",\n\"dud\",\n\"dudaim\",\n\"dudder\",\n\"duddery\",\n\"duddies\",\n\"dude\",\n\"dudeen\",\n\"dudgeon\",\n\"dudine\",\n\"dudish\",\n\"dudism\",\n\"dudler\",\n\"dudley\",\n\"dudman\",\n\"due\",\n\"duel\",\n\"dueler\",\n\"dueling\",\n\"duelist\",\n\"duello\",\n\"dueness\",\n\"duenna\",\n\"duer\",\n\"duet\",\n\"duff\",\n\"duffel\",\n\"duffer\",\n\"duffing\",\n\"dufoil\",\n\"dufter\",\n\"duftery\",\n\"dug\",\n\"dugal\",\n\"dugdug\",\n\"duggler\",\n\"dugong\",\n\"dugout\",\n\"dugway\",\n\"duhat\",\n\"duiker\",\n\"duim\",\n\"duit\",\n\"dujan\",\n\"duke\",\n\"dukedom\",\n\"dukely\",\n\"dukery\",\n\"dukhn\",\n\"dukker\",\n\"dulbert\",\n\"dulcet\",\n\"dulcian\",\n\"dulcify\",\n\"dulcose\",\n\"duledge\",\n\"duler\",\n\"dulia\",\n\"dull\",\n\"dullard\",\n\"duller\",\n\"dullery\",\n\"dullify\",\n\"dullish\",\n\"dullity\",\n\"dully\",\n\"dulosis\",\n\"dulotic\",\n\"dulse\",\n\"dult\",\n\"dultie\",\n\"duly\",\n\"dum\",\n\"duma\",\n\"dumaist\",\n\"dumb\",\n\"dumba\",\n\"dumbcow\",\n\"dumbly\",\n\"dumdum\",\n\"dummel\",\n\"dummy\",\n\"dumose\",\n\"dump\",\n\"dumpage\",\n\"dumper\",\n\"dumpily\",\n\"dumping\",\n\"dumpish\",\n\"dumple\",\n\"dumpoke\",\n\"dumpy\",\n\"dumsola\",\n\"dun\",\n\"dunair\",\n\"dunal\",\n\"dunbird\",\n\"dunce\",\n\"duncery\",\n\"dunch\",\n\"duncify\",\n\"duncish\",\n\"dunder\",\n\"dune\",\n\"dunfish\",\n\"dung\",\n\"dungeon\",\n\"dunger\",\n\"dungol\",\n\"dungon\",\n\"dungy\",\n\"dunite\",\n\"dunk\",\n\"dunker\",\n\"dunlin\",\n\"dunnage\",\n\"dunne\",\n\"dunner\",\n\"dunness\",\n\"dunnish\",\n\"dunnite\",\n\"dunnock\",\n\"dunny\",\n\"dunst\",\n\"dunt\",\n\"duntle\",\n\"duny\",\n\"duo\",\n\"duodena\",\n\"duodene\",\n\"duole\",\n\"duopod\",\n\"duopoly\",\n\"duotone\",\n\"duotype\",\n\"dup\",\n\"dupable\",\n\"dupe\",\n\"dupedom\",\n\"duper\",\n\"dupery\",\n\"dupion\",\n\"dupla\",\n\"duple\",\n\"duplet\",\n\"duplex\",\n\"duplify\",\n\"duplone\",\n\"duppy\",\n\"dura\",\n\"durable\",\n\"durably\",\n\"durain\",\n\"dural\",\n\"duramen\",\n\"durance\",\n\"durant\",\n\"durax\",\n\"durbar\",\n\"dure\",\n\"durene\",\n\"durenol\",\n\"duress\",\n\"durgan\",\n\"durian\",\n\"during\",\n\"durity\",\n\"durmast\",\n\"durn\",\n\"duro\",\n\"durra\",\n\"durrie\",\n\"durrin\",\n\"durry\",\n\"durst\",\n\"durwaun\",\n\"duryl\",\n\"dusack\",\n\"duscle\",\n\"dush\",\n\"dusio\",\n\"dusk\",\n\"dusken\",\n\"duskily\",\n\"duskish\",\n\"duskly\",\n\"dusky\",\n\"dust\",\n\"dustbin\",\n\"dustbox\",\n\"dustee\",\n\"duster\",\n\"dustily\",\n\"dusting\",\n\"dustman\",\n\"dustpan\",\n\"dustuck\",\n\"dusty\",\n\"dutch\",\n\"duteous\",\n\"dutied\",\n\"dutiful\",\n\"dutra\",\n\"duty\",\n\"duumvir\",\n\"duvet\",\n\"duvetyn\",\n\"dux\",\n\"duyker\",\n\"dvaita\",\n\"dvandva\",\n\"dwale\",\n\"dwalm\",\n\"dwang\",\n\"dwarf\",\n\"dwarfy\",\n\"dwell\",\n\"dwelled\",\n\"dweller\",\n\"dwelt\",\n\"dwindle\",\n\"dwine\",\n\"dyad\",\n\"dyadic\",\n\"dyarchy\",\n\"dyaster\",\n\"dyce\",\n\"dye\",\n\"dyeable\",\n\"dyeing\",\n\"dyer\",\n\"dyester\",\n\"dyeware\",\n\"dyeweed\",\n\"dyewood\",\n\"dying\",\n\"dyingly\",\n\"dyke\",\n\"dyker\",\n\"dynamic\",\n\"dynamis\",\n\"dynamo\",\n\"dynast\",\n\"dynasty\",\n\"dyne\",\n\"dyphone\",\n\"dyslogy\",\n\"dysnomy\",\n\"dyspnea\",\n\"dystome\",\n\"dysuria\",\n\"dysuric\",\n\"dzeren\",\n\"e\",\n\"ea\",\n\"each\",\n\"eager\",\n\"eagerly\",\n\"eagle\",\n\"eagless\",\n\"eaglet\",\n\"eagre\",\n\"ean\",\n\"ear\",\n\"earache\",\n\"earbob\",\n\"earcap\",\n\"eardrop\",\n\"eardrum\",\n\"eared\",\n\"earful\",\n\"earhole\",\n\"earing\",\n\"earl\",\n\"earlap\",\n\"earldom\",\n\"earless\",\n\"earlet\",\n\"earlike\",\n\"earlish\",\n\"earlock\",\n\"early\",\n\"earmark\",\n\"earn\",\n\"earner\",\n\"earnest\",\n\"earnful\",\n\"earning\",\n\"earpick\",\n\"earplug\",\n\"earring\",\n\"earshot\",\n\"earsore\",\n\"eartab\",\n\"earth\",\n\"earthed\",\n\"earthen\",\n\"earthly\",\n\"earthy\",\n\"earwax\",\n\"earwig\",\n\"earworm\",\n\"earwort\",\n\"ease\",\n\"easeful\",\n\"easel\",\n\"easer\",\n\"easier\",\n\"easiest\",\n\"easily\",\n\"easing\",\n\"east\",\n\"easter\",\n\"eastern\",\n\"easting\",\n\"easy\",\n\"eat\",\n\"eatable\",\n\"eatage\",\n\"eaten\",\n\"eater\",\n\"eatery\",\n\"eating\",\n\"eats\",\n\"eave\",\n\"eaved\",\n\"eaver\",\n\"eaves\",\n\"ebb\",\n\"ebbman\",\n\"eboe\",\n\"ebon\",\n\"ebonist\",\n\"ebonite\",\n\"ebonize\",\n\"ebony\",\n\"ebriate\",\n\"ebriety\",\n\"ebrious\",\n\"ebulus\",\n\"eburine\",\n\"ecad\",\n\"ecanda\",\n\"ecarte\",\n\"ecbatic\",\n\"ecbole\",\n\"ecbolic\",\n\"ecdemic\",\n\"ecderon\",\n\"ecdysis\",\n\"ecesic\",\n\"ecesis\",\n\"eche\",\n\"echea\",\n\"echelon\",\n\"echidna\",\n\"echinal\",\n\"echinid\",\n\"echinus\",\n\"echo\",\n\"echoer\",\n\"echoic\",\n\"echoism\",\n\"echoist\",\n\"echoize\",\n\"ecize\",\n\"ecklein\",\n\"eclair\",\n\"eclat\",\n\"eclegm\",\n\"eclegma\",\n\"eclipse\",\n\"eclogue\",\n\"ecoid\",\n\"ecole\",\n\"ecology\",\n\"economy\",\n\"ecotone\",\n\"ecotype\",\n\"ecphore\",\n\"ecru\",\n\"ecstasy\",\n\"ectad\",\n\"ectal\",\n\"ectally\",\n\"ectasia\",\n\"ectasis\",\n\"ectatic\",\n\"ectene\",\n\"ecthyma\",\n\"ectiris\",\n\"ectopia\",\n\"ectopic\",\n\"ectopy\",\n\"ectozoa\",\n\"ectypal\",\n\"ectype\",\n\"eczema\",\n\"edacity\",\n\"edaphic\",\n\"edaphon\",\n\"edder\",\n\"eddish\",\n\"eddo\",\n\"eddy\",\n\"edea\",\n\"edeagra\",\n\"edeitis\",\n\"edema\",\n\"edemic\",\n\"edenite\",\n\"edental\",\n\"edestan\",\n\"edestin\",\n\"edge\",\n\"edged\",\n\"edgeman\",\n\"edger\",\n\"edging\",\n\"edgrew\",\n\"edgy\",\n\"edh\",\n\"edible\",\n\"edict\",\n\"edictal\",\n\"edicule\",\n\"edifice\",\n\"edifier\",\n\"edify\",\n\"edit\",\n\"edital\",\n\"edition\",\n\"editor\",\n\"educand\",\n\"educate\",\n\"educe\",\n\"educive\",\n\"educt\",\n\"eductor\",\n\"eegrass\",\n\"eel\",\n\"eelboat\",\n\"eelbob\",\n\"eelcake\",\n\"eeler\",\n\"eelery\",\n\"eelfare\",\n\"eelfish\",\n\"eellike\",\n\"eelpot\",\n\"eelpout\",\n\"eelshop\",\n\"eelskin\",\n\"eelware\",\n\"eelworm\",\n\"eely\",\n\"eer\",\n\"eerie\",\n\"eerily\",\n\"effable\",\n\"efface\",\n\"effacer\",\n\"effect\",\n\"effects\",\n\"effendi\",\n\"effete\",\n\"effigy\",\n\"efflate\",\n\"efflux\",\n\"efform\",\n\"effort\",\n\"effulge\",\n\"effund\",\n\"effuse\",\n\"eft\",\n\"eftest\",\n\"egad\",\n\"egality\",\n\"egence\",\n\"egeran\",\n\"egest\",\n\"egesta\",\n\"egg\",\n\"eggcup\",\n\"egger\",\n\"eggfish\",\n\"egghead\",\n\"egghot\",\n\"egging\",\n\"eggler\",\n\"eggless\",\n\"egglike\",\n\"eggnog\",\n\"eggy\",\n\"egilops\",\n\"egipto\",\n\"egma\",\n\"ego\",\n\"egohood\",\n\"egoism\",\n\"egoist\",\n\"egoity\",\n\"egoize\",\n\"egoizer\",\n\"egol\",\n\"egomism\",\n\"egotism\",\n\"egotist\",\n\"egotize\",\n\"egress\",\n\"egret\",\n\"eh\",\n\"eheu\",\n\"ehlite\",\n\"ehuawa\",\n\"eident\",\n\"eider\",\n\"eidetic\",\n\"eidolic\",\n\"eidolon\",\n\"eight\",\n\"eighth\",\n\"eighty\",\n\"eigne\",\n\"eimer\",\n\"einkorn\",\n\"eisodic\",\n\"either\",\n\"eject\",\n\"ejecta\",\n\"ejector\",\n\"ejoo\",\n\"ekaha\",\n\"eke\",\n\"eker\",\n\"ekerite\",\n\"eking\",\n\"ekka\",\n\"ekphore\",\n\"ektene\",\n\"ektenes\",\n\"el\",\n\"elaidic\",\n\"elaidin\",\n\"elain\",\n\"elaine\",\n\"elance\",\n\"eland\",\n\"elanet\",\n\"elapid\",\n\"elapine\",\n\"elapoid\",\n\"elapse\",\n\"elastic\",\n\"elastin\",\n\"elatcha\",\n\"elate\",\n\"elated\",\n\"elater\",\n\"elation\",\n\"elative\",\n\"elator\",\n\"elb\",\n\"elbow\",\n\"elbowed\",\n\"elbower\",\n\"elbowy\",\n\"elcaja\",\n\"elchee\",\n\"eld\",\n\"elder\",\n\"elderly\",\n\"eldest\",\n\"eldin\",\n\"elding\",\n\"eldress\",\n\"elect\",\n\"electee\",\n\"electly\",\n\"elector\",\n\"electro\",\n\"elegant\",\n\"elegiac\",\n\"elegist\",\n\"elegit\",\n\"elegize\",\n\"elegy\",\n\"eleidin\",\n\"element\",\n\"elemi\",\n\"elemin\",\n\"elench\",\n\"elenchi\",\n\"elenge\",\n\"elevate\",\n\"eleven\",\n\"elevon\",\n\"elf\",\n\"elfhood\",\n\"elfic\",\n\"elfin\",\n\"elfish\",\n\"elfkin\",\n\"elfland\",\n\"elflike\",\n\"elflock\",\n\"elfship\",\n\"elfwife\",\n\"elfwort\",\n\"elicit\",\n\"elide\",\n\"elision\",\n\"elisor\",\n\"elite\",\n\"elixir\",\n\"elk\",\n\"elkhorn\",\n\"elkslip\",\n\"elkwood\",\n\"ell\",\n\"ellagic\",\n\"elle\",\n\"elleck\",\n\"ellfish\",\n\"ellipse\",\n\"ellops\",\n\"ellwand\",\n\"elm\",\n\"elmy\",\n\"elocute\",\n\"elod\",\n\"eloge\",\n\"elogium\",\n\"eloign\",\n\"elope\",\n\"eloper\",\n\"elops\",\n\"els\",\n\"else\",\n\"elsehow\",\n\"elsin\",\n\"elt\",\n\"eluate\",\n\"elude\",\n\"eluder\",\n\"elusion\",\n\"elusive\",\n\"elusory\",\n\"elute\",\n\"elution\",\n\"elutor\",\n\"eluvial\",\n\"eluvium\",\n\"elvan\",\n\"elver\",\n\"elves\",\n\"elvet\",\n\"elvish\",\n\"elysia\",\n\"elytral\",\n\"elytrin\",\n\"elytron\",\n\"elytrum\",\n\"em\",\n\"emanant\",\n\"emanate\",\n\"emanium\",\n\"emarcid\",\n\"emball\",\n\"embalm\",\n\"embank\",\n\"embar\",\n\"embargo\",\n\"embark\",\n\"embassy\",\n\"embathe\",\n\"embay\",\n\"embed\",\n\"embelic\",\n\"ember\",\n\"embind\",\n\"embira\",\n\"emblaze\",\n\"emblem\",\n\"emblema\",\n\"emblic\",\n\"embody\",\n\"embog\",\n\"embole\",\n\"embolic\",\n\"embolo\",\n\"embolum\",\n\"embolus\",\n\"emboly\",\n\"embosom\",\n\"emboss\",\n\"embound\",\n\"embow\",\n\"embowed\",\n\"embowel\",\n\"embower\",\n\"embox\",\n\"embrace\",\n\"embrail\",\n\"embroil\",\n\"embrown\",\n\"embryo\",\n\"embryon\",\n\"embuia\",\n\"embus\",\n\"embusk\",\n\"emcee\",\n\"eme\",\n\"emeer\",\n\"emend\",\n\"emender\",\n\"emerald\",\n\"emerge\",\n\"emerize\",\n\"emerse\",\n\"emersed\",\n\"emery\",\n\"emesis\",\n\"emetic\",\n\"emetine\",\n\"emgalla\",\n\"emigree\",\n\"eminent\",\n\"emir\",\n\"emirate\",\n\"emit\",\n\"emitter\",\n\"emma\",\n\"emmenic\",\n\"emmer\",\n\"emmet\",\n\"emodin\",\n\"emoloa\",\n\"emote\",\n\"emotion\",\n\"emotive\",\n\"empall\",\n\"empanel\",\n\"empaper\",\n\"empark\",\n\"empasm\",\n\"empathy\",\n\"emperor\",\n\"empery\",\n\"empire\",\n\"empiric\",\n\"emplace\",\n\"emplane\",\n\"employ\",\n\"emplume\",\n\"emporia\",\n\"empower\",\n\"empress\",\n\"emprise\",\n\"empt\",\n\"emptier\",\n\"emptily\",\n\"emptins\",\n\"emption\",\n\"emptor\",\n\"empty\",\n\"empyema\",\n\"emu\",\n\"emulant\",\n\"emulate\",\n\"emulous\",\n\"emulsin\",\n\"emulsor\",\n\"emyd\",\n\"emydian\",\n\"en\",\n\"enable\",\n\"enabler\",\n\"enact\",\n\"enactor\",\n\"enaena\",\n\"enage\",\n\"enalid\",\n\"enam\",\n\"enamber\",\n\"enamdar\",\n\"enamel\",\n\"enamor\",\n\"enapt\",\n\"enarbor\",\n\"enarch\",\n\"enarm\",\n\"enarme\",\n\"enate\",\n\"enatic\",\n\"enation\",\n\"enbrave\",\n\"encage\",\n\"encake\",\n\"encamp\",\n\"encase\",\n\"encash\",\n\"encauma\",\n\"encave\",\n\"encell\",\n\"enchain\",\n\"enchair\",\n\"enchant\",\n\"enchase\",\n\"enchest\",\n\"encina\",\n\"encinal\",\n\"encist\",\n\"enclasp\",\n\"enclave\",\n\"encloak\",\n\"enclose\",\n\"encloud\",\n\"encoach\",\n\"encode\",\n\"encoil\",\n\"encolor\",\n\"encomia\",\n\"encomic\",\n\"encoop\",\n\"encore\",\n\"encowl\",\n\"encraal\",\n\"encraty\",\n\"encreel\",\n\"encrisp\",\n\"encrown\",\n\"encrust\",\n\"encrypt\",\n\"encup\",\n\"encurl\",\n\"encyst\",\n\"end\",\n\"endable\",\n\"endarch\",\n\"endaze\",\n\"endear\",\n\"ended\",\n\"endemic\",\n\"ender\",\n\"endere\",\n\"enderon\",\n\"endevil\",\n\"endew\",\n\"endgate\",\n\"ending\",\n\"endite\",\n\"endive\",\n\"endless\",\n\"endlong\",\n\"endmost\",\n\"endogen\",\n\"endome\",\n\"endopod\",\n\"endoral\",\n\"endore\",\n\"endorse\",\n\"endoss\",\n\"endotys\",\n\"endow\",\n\"endower\",\n\"endozoa\",\n\"endue\",\n\"endura\",\n\"endure\",\n\"endurer\",\n\"endways\",\n\"endwise\",\n\"endyma\",\n\"endymal\",\n\"endysis\",\n\"enema\",\n\"enemy\",\n\"energic\",\n\"energid\",\n\"energy\",\n\"eneuch\",\n\"eneugh\",\n\"enface\",\n\"enfelon\",\n\"enfeoff\",\n\"enfever\",\n\"enfile\",\n\"enfiled\",\n\"enflesh\",\n\"enfoil\",\n\"enfold\",\n\"enforce\",\n\"enfork\",\n\"enfoul\",\n\"enframe\",\n\"enfree\",\n\"engage\",\n\"engaged\",\n\"engager\",\n\"engaol\",\n\"engarb\",\n\"engaud\",\n\"engaze\",\n\"engem\",\n\"engild\",\n\"engine\",\n\"engird\",\n\"engirt\",\n\"englad\",\n\"englobe\",\n\"engloom\",\n\"englory\",\n\"englut\",\n\"englyn\",\n\"engobe\",\n\"engold\",\n\"engore\",\n\"engorge\",\n\"engrace\",\n\"engraff\",\n\"engraft\",\n\"engrail\",\n\"engrain\",\n\"engram\",\n\"engrasp\",\n\"engrave\",\n\"engreen\",\n\"engross\",\n\"enguard\",\n\"engulf\",\n\"enhalo\",\n\"enhance\",\n\"enhat\",\n\"enhaunt\",\n\"enheart\",\n\"enhedge\",\n\"enhelm\",\n\"enherit\",\n\"enhusk\",\n\"eniac\",\n\"enigma\",\n\"enisle\",\n\"enjail\",\n\"enjamb\",\n\"enjelly\",\n\"enjewel\",\n\"enjoin\",\n\"enjoy\",\n\"enjoyer\",\n\"enkraal\",\n\"enlace\",\n\"enlard\",\n\"enlarge\",\n\"enleaf\",\n\"enlief\",\n\"enlife\",\n\"enlight\",\n\"enlink\",\n\"enlist\",\n\"enliven\",\n\"enlock\",\n\"enlodge\",\n\"enmask\",\n\"enmass\",\n\"enmesh\",\n\"enmist\",\n\"enmity\",\n\"enmoss\",\n\"ennead\",\n\"ennerve\",\n\"enniche\",\n\"ennoble\",\n\"ennoic\",\n\"ennomic\",\n\"ennui\",\n\"enocyte\",\n\"enodal\",\n\"enoil\",\n\"enol\",\n\"enolate\",\n\"enolic\",\n\"enolize\",\n\"enomoty\",\n\"enoplan\",\n\"enorm\",\n\"enough\",\n\"enounce\",\n\"enow\",\n\"enplane\",\n\"enquire\",\n\"enquiry\",\n\"enrace\",\n\"enrage\",\n\"enraged\",\n\"enrange\",\n\"enrank\",\n\"enrapt\",\n\"enray\",\n\"enrib\",\n\"enrich\",\n\"enring\",\n\"enrive\",\n\"enrobe\",\n\"enrober\",\n\"enrol\",\n\"enroll\",\n\"enroot\",\n\"enrough\",\n\"enruin\",\n\"enrut\",\n\"ens\",\n\"ensaint\",\n\"ensand\",\n\"ensate\",\n\"enscene\",\n\"ense\",\n\"enseam\",\n\"enseat\",\n\"enseem\",\n\"enserf\",\n\"ensete\",\n\"enshade\",\n\"enshawl\",\n\"enshell\",\n\"ensign\",\n\"ensile\",\n\"ensky\",\n\"enslave\",\n\"ensmall\",\n\"ensnare\",\n\"ensnarl\",\n\"ensnow\",\n\"ensoul\",\n\"enspell\",\n\"enstamp\",\n\"enstar\",\n\"enstate\",\n\"ensteel\",\n\"enstool\",\n\"enstore\",\n\"ensuant\",\n\"ensue\",\n\"ensuer\",\n\"ensure\",\n\"ensurer\",\n\"ensweep\",\n\"entach\",\n\"entad\",\n\"entail\",\n\"ental\",\n\"entame\",\n\"entasia\",\n\"entasis\",\n\"entelam\",\n\"entente\",\n\"enter\",\n\"enteral\",\n\"enterer\",\n\"enteria\",\n\"enteric\",\n\"enteron\",\n\"entheal\",\n\"enthral\",\n\"enthuse\",\n\"entia\",\n\"entice\",\n\"enticer\",\n\"entify\",\n\"entire\",\n\"entiris\",\n\"entitle\",\n\"entity\",\n\"entoil\",\n\"entomb\",\n\"entomic\",\n\"entone\",\n\"entopic\",\n\"entotic\",\n\"entozoa\",\n\"entrail\",\n\"entrain\",\n\"entrant\",\n\"entrap\",\n\"entreat\",\n\"entree\",\n\"entropy\",\n\"entrust\",\n\"entry\",\n\"entwine\",\n\"entwist\",\n\"enure\",\n\"enurny\",\n\"envapor\",\n\"envault\",\n\"enveil\",\n\"envelop\",\n\"envenom\",\n\"envied\",\n\"envier\",\n\"envious\",\n\"environ\",\n\"envoy\",\n\"envy\",\n\"envying\",\n\"enwiden\",\n\"enwind\",\n\"enwisen\",\n\"enwoman\",\n\"enwomb\",\n\"enwood\",\n\"enwound\",\n\"enwrap\",\n\"enwrite\",\n\"enzone\",\n\"enzooty\",\n\"enzym\",\n\"enzyme\",\n\"enzymic\",\n\"eoan\",\n\"eolith\",\n\"eon\",\n\"eonism\",\n\"eophyte\",\n\"eosate\",\n\"eoside\",\n\"eosin\",\n\"eosinic\",\n\"eozoon\",\n\"epacme\",\n\"epacrid\",\n\"epact\",\n\"epactal\",\n\"epagoge\",\n\"epanody\",\n\"eparch\",\n\"eparchy\",\n\"epaule\",\n\"epaulet\",\n\"epaxial\",\n\"epee\",\n\"epeeist\",\n\"epeiric\",\n\"epeirid\",\n\"epergne\",\n\"epha\",\n\"ephah\",\n\"ephebe\",\n\"ephebic\",\n\"ephebos\",\n\"ephebus\",\n\"ephelis\",\n\"ephetae\",\n\"ephete\",\n\"ephetic\",\n\"ephod\",\n\"ephor\",\n\"ephoral\",\n\"ephoric\",\n\"ephorus\",\n\"ephyra\",\n\"epibole\",\n\"epiboly\",\n\"epic\",\n\"epical\",\n\"epicarp\",\n\"epicede\",\n\"epicele\",\n\"epicene\",\n\"epichil\",\n\"epicism\",\n\"epicist\",\n\"epicly\",\n\"epicure\",\n\"epicyte\",\n\"epidemy\",\n\"epiderm\",\n\"epidote\",\n\"epigeal\",\n\"epigean\",\n\"epigeic\",\n\"epigene\",\n\"epigone\",\n\"epigram\",\n\"epigyne\",\n\"epigyny\",\n\"epihyal\",\n\"epikeia\",\n\"epilate\",\n\"epilobe\",\n\"epimer\",\n\"epimere\",\n\"epimyth\",\n\"epinaos\",\n\"epinine\",\n\"epiotic\",\n\"epipial\",\n\"episode\",\n\"epistle\",\n\"epitaph\",\n\"epitela\",\n\"epithem\",\n\"epithet\",\n\"epitoke\",\n\"epitome\",\n\"epiural\",\n\"epizoa\",\n\"epizoal\",\n\"epizoan\",\n\"epizoic\",\n\"epizoon\",\n\"epoch\",\n\"epocha\",\n\"epochal\",\n\"epode\",\n\"epodic\",\n\"eponym\",\n\"eponymy\",\n\"epopee\",\n\"epopt\",\n\"epoptes\",\n\"epoptic\",\n\"epos\",\n\"epsilon\",\n\"epulary\",\n\"epulis\",\n\"epulo\",\n\"epuloid\",\n\"epural\",\n\"epurate\",\n\"equable\",\n\"equably\",\n\"equal\",\n\"equally\",\n\"equant\",\n\"equate\",\n\"equator\",\n\"equerry\",\n\"equid\",\n\"equine\",\n\"equinia\",\n\"equinox\",\n\"equinus\",\n\"equip\",\n\"equiped\",\n\"equison\",\n\"equites\",\n\"equity\",\n\"equoid\",\n\"er\",\n\"era\",\n\"erade\",\n\"eral\",\n\"eranist\",\n\"erase\",\n\"erased\",\n\"eraser\",\n\"erasion\",\n\"erasure\",\n\"erbia\",\n\"erbium\",\n\"erd\",\n\"erdvark\",\n\"ere\",\n\"erect\",\n\"erecter\",\n\"erectly\",\n\"erector\",\n\"erelong\",\n\"eremic\",\n\"eremite\",\n\"erenach\",\n\"erenow\",\n\"erepsin\",\n\"erept\",\n\"ereptic\",\n\"erethic\",\n\"erg\",\n\"ergal\",\n\"ergasia\",\n\"ergates\",\n\"ergodic\",\n\"ergoism\",\n\"ergon\",\n\"ergot\",\n\"ergoted\",\n\"ergotic\",\n\"ergotin\",\n\"ergusia\",\n\"eria\",\n\"eric\",\n\"ericad\",\n\"erical\",\n\"ericius\",\n\"ericoid\",\n\"erika\",\n\"erikite\",\n\"erineum\",\n\"erinite\",\n\"erinose\",\n\"eristic\",\n\"erizo\",\n\"erlking\",\n\"ermelin\",\n\"ermine\",\n\"ermined\",\n\"erminee\",\n\"ermines\",\n\"erne\",\n\"erode\",\n\"eroded\",\n\"erodent\",\n\"erogeny\",\n\"eros\",\n\"erose\",\n\"erosely\",\n\"erosion\",\n\"erosive\",\n\"eroteme\",\n\"erotic\",\n\"erotica\",\n\"erotism\",\n\"err\",\n\"errable\",\n\"errancy\",\n\"errand\",\n\"errant\",\n\"errata\",\n\"erratic\",\n\"erratum\",\n\"errhine\",\n\"erring\",\n\"errite\",\n\"error\",\n\"ers\",\n\"ersatz\",\n\"erth\",\n\"erthen\",\n\"erthly\",\n\"eruc\",\n\"eruca\",\n\"erucic\",\n\"erucin\",\n\"eruct\",\n\"erudit\",\n\"erudite\",\n\"erugate\",\n\"erupt\",\n\"eryngo\",\n\"es\",\n\"esca\",\n\"escalan\",\n\"escalin\",\n\"escalop\",\n\"escape\",\n\"escapee\",\n\"escaper\",\n\"escarp\",\n\"eschar\",\n\"eschara\",\n\"escheat\",\n\"eschew\",\n\"escoba\",\n\"escolar\",\n\"escort\",\n\"escribe\",\n\"escrol\",\n\"escrow\",\n\"escudo\",\n\"esculin\",\n\"esere\",\n\"eserine\",\n\"esexual\",\n\"eshin\",\n\"esker\",\n\"esne\",\n\"esodic\",\n\"esotery\",\n\"espadon\",\n\"esparto\",\n\"espave\",\n\"espial\",\n\"espier\",\n\"espinal\",\n\"espino\",\n\"esplees\",\n\"espouse\",\n\"espy\",\n\"esquire\",\n\"ess\",\n\"essang\",\n\"essay\",\n\"essayer\",\n\"essed\",\n\"essence\",\n\"essency\",\n\"essling\",\n\"essoin\",\n\"estadal\",\n\"estadio\",\n\"estado\",\n\"estamp\",\n\"estate\",\n\"esteem\",\n\"ester\",\n\"estevin\",\n\"estival\",\n\"estmark\",\n\"estoc\",\n\"estoile\",\n\"estop\",\n\"estrade\",\n\"estray\",\n\"estre\",\n\"estreat\",\n\"estrepe\",\n\"estrin\",\n\"estriol\",\n\"estrone\",\n\"estrous\",\n\"estrual\",\n\"estuary\",\n\"estufa\",\n\"estuous\",\n\"estus\",\n\"eta\",\n\"etacism\",\n\"etacist\",\n\"etalon\",\n\"etamine\",\n\"etch\",\n\"etcher\",\n\"etching\",\n\"eternal\",\n\"etesian\",\n\"ethal\",\n\"ethanal\",\n\"ethane\",\n\"ethanol\",\n\"ethel\",\n\"ethene\",\n\"ethenic\",\n\"ethenol\",\n\"ethenyl\",\n\"ether\",\n\"ethered\",\n\"etheric\",\n\"etherin\",\n\"ethic\",\n\"ethical\",\n\"ethics\",\n\"ethid\",\n\"ethide\",\n\"ethine\",\n\"ethiops\",\n\"ethmoid\",\n\"ethnal\",\n\"ethnic\",\n\"ethnize\",\n\"ethnos\",\n\"ethos\",\n\"ethoxyl\",\n\"ethrog\",\n\"ethyl\",\n\"ethylic\",\n\"ethylin\",\n\"ethyne\",\n\"ethynyl\",\n\"etiolin\",\n\"etna\",\n\"ettle\",\n\"etua\",\n\"etude\",\n\"etui\",\n\"etym\",\n\"etymic\",\n\"etymon\",\n\"etypic\",\n\"eu\",\n\"euaster\",\n\"eucaine\",\n\"euchre\",\n\"euchred\",\n\"euclase\",\n\"eucone\",\n\"euconic\",\n\"eucrasy\",\n\"eucrite\",\n\"euge\",\n\"eugenic\",\n\"eugenol\",\n\"eugeny\",\n\"eulalia\",\n\"eulogia\",\n\"eulogic\",\n\"eulogy\",\n\"eumenid\",\n\"eunicid\",\n\"eunomy\",\n\"eunuch\",\n\"euonym\",\n\"euonymy\",\n\"euouae\",\n\"eupad\",\n\"eupathy\",\n\"eupepsy\",\n\"euphemy\",\n\"euphon\",\n\"euphone\",\n\"euphony\",\n\"euphory\",\n\"euphroe\",\n\"eupione\",\n\"euploid\",\n\"eupnea\",\n\"eureka\",\n\"euripus\",\n\"eurite\",\n\"eurobin\",\n\"euryon\",\n\"eusol\",\n\"eustyle\",\n\"eutaxic\",\n\"eutaxy\",\n\"eutexia\",\n\"eutony\",\n\"evacue\",\n\"evacuee\",\n\"evade\",\n\"evader\",\n\"evalue\",\n\"evangel\",\n\"evanish\",\n\"evase\",\n\"evasion\",\n\"evasive\",\n\"eve\",\n\"evejar\",\n\"evelong\",\n\"even\",\n\"evener\",\n\"evening\",\n\"evenly\",\n\"evens\",\n\"event\",\n\"eveque\",\n\"ever\",\n\"evert\",\n\"evertor\",\n\"everwho\",\n\"every\",\n\"evestar\",\n\"evetide\",\n\"eveweed\",\n\"evict\",\n\"evictor\",\n\"evident\",\n\"evil\",\n\"evilly\",\n\"evince\",\n\"evirate\",\n\"evisite\",\n\"evitate\",\n\"evocate\",\n\"evoe\",\n\"evoke\",\n\"evoker\",\n\"evolute\",\n\"evolve\",\n\"evolver\",\n\"evovae\",\n\"evulse\",\n\"evzone\",\n\"ewder\",\n\"ewe\",\n\"ewer\",\n\"ewerer\",\n\"ewery\",\n\"ewry\",\n\"ex\",\n\"exact\",\n\"exacter\",\n\"exactly\",\n\"exactor\",\n\"exalate\",\n\"exalt\",\n\"exalted\",\n\"exalter\",\n\"exam\",\n\"examen\",\n\"examine\",\n\"example\",\n\"exarate\",\n\"exarch\",\n\"exarchy\",\n\"excamb\",\n\"excave\",\n\"exceed\",\n\"excel\",\n\"except\",\n\"excerpt\",\n\"excess\",\n\"excide\",\n\"exciple\",\n\"excise\",\n\"excisor\",\n\"excite\",\n\"excited\",\n\"exciter\",\n\"excitor\",\n\"exclaim\",\n\"exclave\",\n\"exclude\",\n\"excreta\",\n\"excrete\",\n\"excurse\",\n\"excusal\",\n\"excuse\",\n\"excuser\",\n\"excuss\",\n\"excyst\",\n\"exdie\",\n\"exeat\",\n\"execute\",\n\"exedent\",\n\"exedra\",\n\"exegete\",\n\"exempt\",\n\"exequy\",\n\"exergue\",\n\"exert\",\n\"exes\",\n\"exeunt\",\n\"exflect\",\n\"exhale\",\n\"exhaust\",\n\"exhibit\",\n\"exhort\",\n\"exhume\",\n\"exhumer\",\n\"exigent\",\n\"exile\",\n\"exiler\",\n\"exilian\",\n\"exilic\",\n\"exility\",\n\"exist\",\n\"exister\",\n\"exit\",\n\"exite\",\n\"exition\",\n\"exitus\",\n\"exlex\",\n\"exocarp\",\n\"exocone\",\n\"exode\",\n\"exoderm\",\n\"exodic\",\n\"exodist\",\n\"exodos\",\n\"exodus\",\n\"exody\",\n\"exogamy\",\n\"exogen\",\n\"exogeny\",\n\"exomion\",\n\"exomis\",\n\"exon\",\n\"exoner\",\n\"exopod\",\n\"exordia\",\n\"exormia\",\n\"exosmic\",\n\"exostra\",\n\"exotic\",\n\"exotism\",\n\"expand\",\n\"expanse\",\n\"expect\",\n\"expede\",\n\"expel\",\n\"expend\",\n\"expense\",\n\"expert\",\n\"expiate\",\n\"expire\",\n\"expiree\",\n\"expirer\",\n\"expiry\",\n\"explain\",\n\"explant\",\n\"explode\",\n\"exploit\",\n\"explore\",\n\"expone\",\n\"export\",\n\"exposal\",\n\"expose\",\n\"exposed\",\n\"exposer\",\n\"exposit\",\n\"expound\",\n\"express\",\n\"expugn\",\n\"expulse\",\n\"expunge\",\n\"expurge\",\n\"exradio\",\n\"exscind\",\n\"exsect\",\n\"exsert\",\n\"exship\",\n\"exsurge\",\n\"extant\",\n\"extend\",\n\"extense\",\n\"extent\",\n\"exter\",\n\"extern\",\n\"externe\",\n\"extima\",\n\"extinct\",\n\"extine\",\n\"extol\",\n\"extoll\",\n\"extort\",\n\"extra\",\n\"extract\",\n\"extrait\",\n\"extreme\",\n\"extrude\",\n\"extund\",\n\"exudate\",\n\"exude\",\n\"exult\",\n\"exultet\",\n\"exuviae\",\n\"exuvial\",\n\"ey\",\n\"eyah\",\n\"eyalet\",\n\"eyas\",\n\"eye\",\n\"eyeball\",\n\"eyebalm\",\n\"eyebar\",\n\"eyebeam\",\n\"eyebolt\",\n\"eyebree\",\n\"eyebrow\",\n\"eyecup\",\n\"eyed\",\n\"eyedot\",\n\"eyedrop\",\n\"eyeflap\",\n\"eyeful\",\n\"eyehole\",\n\"eyelash\",\n\"eyeless\",\n\"eyelet\",\n\"eyelid\",\n\"eyelike\",\n\"eyeline\",\n\"eyemark\",\n\"eyen\",\n\"eyepit\",\n\"eyer\",\n\"eyeroot\",\n\"eyeseed\",\n\"eyeshot\",\n\"eyesome\",\n\"eyesore\",\n\"eyespot\",\n\"eyewash\",\n\"eyewear\",\n\"eyewink\",\n\"eyewort\",\n\"eyey\",\n\"eying\",\n\"eyn\",\n\"eyne\",\n\"eyot\",\n\"eyoty\",\n\"eyra\",\n\"eyre\",\n\"eyrie\",\n\"eyrir\",\n\"ezba\",\n\"f\",\n\"fa\",\n\"fabella\",\n\"fabes\",\n\"fable\",\n\"fabled\",\n\"fabler\",\n\"fabliau\",\n\"fabling\",\n\"fabric\",\n\"fabular\",\n\"facadal\",\n\"facade\",\n\"face\",\n\"faced\",\n\"faceman\",\n\"facer\",\n\"facet\",\n\"facete\",\n\"faceted\",\n\"facia\",\n\"facial\",\n\"faciend\",\n\"facient\",\n\"facies\",\n\"facile\",\n\"facing\",\n\"fack\",\n\"fackins\",\n\"facks\",\n\"fact\",\n\"factful\",\n\"faction\",\n\"factish\",\n\"factive\",\n\"factor\",\n\"factory\",\n\"factrix\",\n\"factual\",\n\"factum\",\n\"facture\",\n\"facty\",\n\"facula\",\n\"facular\",\n\"faculty\",\n\"facund\",\n\"facy\",\n\"fad\",\n\"fadable\",\n\"faddish\",\n\"faddism\",\n\"faddist\",\n\"faddle\",\n\"faddy\",\n\"fade\",\n\"faded\",\n\"fadedly\",\n\"faden\",\n\"fader\",\n\"fadge\",\n\"fading\",\n\"fady\",\n\"fae\",\n\"faerie\",\n\"faery\",\n\"faff\",\n\"faffle\",\n\"faffy\",\n\"fag\",\n\"fagald\",\n\"fage\",\n\"fager\",\n\"fagger\",\n\"faggery\",\n\"fagging\",\n\"fagine\",\n\"fagot\",\n\"fagoter\",\n\"fagoty\",\n\"faham\",\n\"fahlerz\",\n\"fahlore\",\n\"faience\",\n\"fail\",\n\"failing\",\n\"faille\",\n\"failure\",\n\"fain\",\n\"fainly\",\n\"fains\",\n\"faint\",\n\"fainter\",\n\"faintly\",\n\"faints\",\n\"fainty\",\n\"faipule\",\n\"fair\",\n\"fairer\",\n\"fairily\",\n\"fairing\",\n\"fairish\",\n\"fairly\",\n\"fairm\",\n\"fairway\",\n\"fairy\",\n\"faith\",\n\"faitour\",\n\"fake\",\n\"faker\",\n\"fakery\",\n\"fakir\",\n\"faky\",\n\"falbala\",\n\"falcade\",\n\"falcate\",\n\"falcer\",\n\"falces\",\n\"falcial\",\n\"falcon\",\n\"falcula\",\n\"faldage\",\n\"faldfee\",\n\"fall\",\n\"fallace\",\n\"fallacy\",\n\"fallage\",\n\"fallen\",\n\"faller\",\n\"falling\",\n\"fallow\",\n\"fallway\",\n\"fally\",\n\"falsary\",\n\"false\",\n\"falsely\",\n\"falsen\",\n\"falser\",\n\"falsie\",\n\"falsify\",\n\"falsism\",\n\"faltche\",\n\"falter\",\n\"falutin\",\n\"falx\",\n\"fam\",\n\"famble\",\n\"fame\",\n\"fameful\",\n\"familia\",\n\"family\",\n\"famine\",\n\"famish\",\n\"famous\",\n\"famulus\",\n\"fan\",\n\"fana\",\n\"fanal\",\n\"fanam\",\n\"fanatic\",\n\"fanback\",\n\"fancied\",\n\"fancier\",\n\"fancify\",\n\"fancy\",\n\"fand\",\n\"fandom\",\n\"fanega\",\n\"fanfare\",\n\"fanfoot\",\n\"fang\",\n\"fanged\",\n\"fangle\",\n\"fangled\",\n\"fanglet\",\n\"fangot\",\n\"fangy\",\n\"fanion\",\n\"fanlike\",\n\"fanman\",\n\"fannel\",\n\"fanner\",\n\"fannier\",\n\"fanning\",\n\"fanon\",\n\"fant\",\n\"fantail\",\n\"fantast\",\n\"fantasy\",\n\"fantod\",\n\"fanweed\",\n\"fanwise\",\n\"fanwork\",\n\"fanwort\",\n\"faon\",\n\"far\",\n\"farad\",\n\"faraday\",\n\"faradic\",\n\"faraway\",\n\"farce\",\n\"farcer\",\n\"farcial\",\n\"farcied\",\n\"farcify\",\n\"farcing\",\n\"farcist\",\n\"farcy\",\n\"farde\",\n\"fardel\",\n\"fardh\",\n\"fardo\",\n\"fare\",\n\"farer\",\n\"farfara\",\n\"farfel\",\n\"fargood\",\n\"farina\",\n\"faring\",\n\"farish\",\n\"farl\",\n\"farleu\",\n\"farm\",\n\"farmage\",\n\"farmer\",\n\"farmery\",\n\"farming\",\n\"farmost\",\n\"farmy\",\n\"farness\",\n\"faro\",\n\"farrago\",\n\"farrand\",\n\"farrier\",\n\"farrow\",\n\"farruca\",\n\"farse\",\n\"farseer\",\n\"farset\",\n\"farther\",\n\"fasces\",\n\"fascet\",\n\"fascia\",\n\"fascial\",\n\"fascine\",\n\"fascis\",\n\"fascism\",\n\"fascist\",\n\"fash\",\n\"fasher\",\n\"fashery\",\n\"fashion\",\n\"fass\",\n\"fast\",\n\"fasten\",\n\"faster\",\n\"fasting\",\n\"fastish\",\n\"fastus\",\n\"fat\",\n\"fatal\",\n\"fatally\",\n\"fatbird\",\n\"fate\",\n\"fated\",\n\"fateful\",\n\"fathead\",\n\"father\",\n\"fathmur\",\n\"fathom\",\n\"fatidic\",\n\"fatigue\",\n\"fatiha\",\n\"fatil\",\n\"fatless\",\n\"fatling\",\n\"fatly\",\n\"fatness\",\n\"fatsia\",\n\"fatten\",\n\"fatter\",\n\"fattily\",\n\"fattish\",\n\"fatty\",\n\"fatuism\",\n\"fatuity\",\n\"fatuoid\",\n\"fatuous\",\n\"fatwood\",\n\"faucal\",\n\"fauces\",\n\"faucet\",\n\"faucial\",\n\"faucre\",\n\"faugh\",\n\"fauld\",\n\"fault\",\n\"faulter\",\n\"faulty\",\n\"faun\",\n\"faunal\",\n\"faunish\",\n\"faunist\",\n\"faunule\",\n\"fause\",\n\"faust\",\n\"fautor\",\n\"fauve\",\n\"favella\",\n\"favilla\",\n\"favism\",\n\"favissa\",\n\"favn\",\n\"favor\",\n\"favored\",\n\"favorer\",\n\"favose\",\n\"favous\",\n\"favus\",\n\"fawn\",\n\"fawner\",\n\"fawnery\",\n\"fawning\",\n\"fawny\",\n\"fay\",\n\"fayles\",\n\"faze\",\n\"fazenda\",\n\"fe\",\n\"feague\",\n\"feak\",\n\"feal\",\n\"fealty\",\n\"fear\",\n\"feared\",\n\"fearer\",\n\"fearful\",\n\"feasor\",\n\"feast\",\n\"feasten\",\n\"feaster\",\n\"feat\",\n\"feather\",\n\"featly\",\n\"featous\",\n\"feature\",\n\"featy\",\n\"feaze\",\n\"febrile\",\n\"fecal\",\n\"feces\",\n\"feck\",\n\"feckful\",\n\"feckly\",\n\"fecula\",\n\"fecund\",\n\"fed\",\n\"feddan\",\n\"federal\",\n\"fee\",\n\"feeable\",\n\"feeble\",\n\"feebly\",\n\"feed\",\n\"feedbin\",\n\"feedbox\",\n\"feeder\",\n\"feeding\",\n\"feedman\",\n\"feedway\",\n\"feedy\",\n\"feel\",\n\"feeler\",\n\"feeless\",\n\"feeling\",\n\"feer\",\n\"feere\",\n\"feering\",\n\"feetage\",\n\"feeze\",\n\"fegary\",\n\"fei\",\n\"feif\",\n\"feigher\",\n\"feign\",\n\"feigned\",\n\"feigner\",\n\"feil\",\n\"feint\",\n\"feis\",\n\"feist\",\n\"feisty\",\n\"felid\",\n\"feline\",\n\"fell\",\n\"fellage\",\n\"fellah\",\n\"fellen\",\n\"feller\",\n\"fellic\",\n\"felling\",\n\"felloe\",\n\"fellow\",\n\"felly\",\n\"feloid\",\n\"felon\",\n\"felonry\",\n\"felony\",\n\"fels\",\n\"felsite\",\n\"felt\",\n\"felted\",\n\"felter\",\n\"felting\",\n\"felty\",\n\"felucca\",\n\"felwort\",\n\"female\",\n\"feme\",\n\"femic\",\n\"feminal\",\n\"feminie\",\n\"feminin\",\n\"femora\",\n\"femoral\",\n\"femur\",\n\"fen\",\n\"fenbank\",\n\"fence\",\n\"fencer\",\n\"fenchyl\",\n\"fencing\",\n\"fend\",\n\"fender\",\n\"fendy\",\n\"fenite\",\n\"fenks\",\n\"fenland\",\n\"fenman\",\n\"fennec\",\n\"fennel\",\n\"fennig\",\n\"fennish\",\n\"fenny\",\n\"fensive\",\n\"fent\",\n\"fenter\",\n\"feod\",\n\"feodal\",\n\"feodary\",\n\"feoff\",\n\"feoffee\",\n\"feoffor\",\n\"feower\",\n\"feral\",\n\"feralin\",\n\"ferash\",\n\"ferdwit\",\n\"ferfet\",\n\"feria\",\n\"ferial\",\n\"feridgi\",\n\"ferie\",\n\"ferine\",\n\"ferity\",\n\"ferk\",\n\"ferling\",\n\"ferly\",\n\"fermail\",\n\"ferme\",\n\"ferment\",\n\"fermery\",\n\"fermila\",\n\"fern\",\n\"ferned\",\n\"fernery\",\n\"ferny\",\n\"feroher\",\n\"ferrado\",\n\"ferrate\",\n\"ferrean\",\n\"ferret\",\n\"ferrety\",\n\"ferri\",\n\"ferric\",\n\"ferrier\",\n\"ferrite\",\n\"ferrous\",\n\"ferrule\",\n\"ferrum\",\n\"ferry\",\n\"fertile\",\n\"feru\",\n\"ferula\",\n\"ferule\",\n\"ferulic\",\n\"fervent\",\n\"fervid\",\n\"fervor\",\n\"fescue\",\n\"fess\",\n\"fessely\",\n\"fest\",\n\"festal\",\n\"fester\",\n\"festine\",\n\"festive\",\n\"festoon\",\n\"festuca\",\n\"fet\",\n\"fetal\",\n\"fetch\",\n\"fetched\",\n\"fetcher\",\n\"fetial\",\n\"fetid\",\n\"fetidly\",\n\"fetish\",\n\"fetlock\",\n\"fetlow\",\n\"fetor\",\n\"fetter\",\n\"fettle\",\n\"fettler\",\n\"fetus\",\n\"feu\",\n\"feuage\",\n\"feuar\",\n\"feucht\",\n\"feud\",\n\"feudal\",\n\"feudee\",\n\"feudist\",\n\"feued\",\n\"feuille\",\n\"fever\",\n\"feveret\",\n\"few\",\n\"fewness\",\n\"fewsome\",\n\"fewter\",\n\"fey\",\n\"feyness\",\n\"fez\",\n\"fezzed\",\n\"fezzy\",\n\"fi\",\n\"fiacre\",\n\"fiance\",\n\"fiancee\",\n\"fiar\",\n\"fiard\",\n\"fiasco\",\n\"fiat\",\n\"fib\",\n\"fibber\",\n\"fibbery\",\n\"fibdom\",\n\"fiber\",\n\"fibered\",\n\"fibril\",\n\"fibrin\",\n\"fibrine\",\n\"fibroid\",\n\"fibroin\",\n\"fibroma\",\n\"fibrose\",\n\"fibrous\",\n\"fibry\",\n\"fibster\",\n\"fibula\",\n\"fibulae\",\n\"fibular\",\n\"ficary\",\n\"fice\",\n\"ficelle\",\n\"fiche\",\n\"fichu\",\n\"fickle\",\n\"fickly\",\n\"fico\",\n\"ficoid\",\n\"fictile\",\n\"fiction\",\n\"fictive\",\n\"fid\",\n\"fidalgo\",\n\"fidate\",\n\"fiddle\",\n\"fiddler\",\n\"fiddley\",\n\"fide\",\n\"fideism\",\n\"fideist\",\n\"fidfad\",\n\"fidge\",\n\"fidget\",\n\"fidgety\",\n\"fiducia\",\n\"fie\",\n\"fiefdom\",\n\"field\",\n\"fielded\",\n\"fielder\",\n\"fieldy\",\n\"fiend\",\n\"fiendly\",\n\"fient\",\n\"fierce\",\n\"fiercen\",\n\"fierily\",\n\"fiery\",\n\"fiesta\",\n\"fife\",\n\"fifer\",\n\"fifie\",\n\"fifish\",\n\"fifo\",\n\"fifteen\",\n\"fifth\",\n\"fifthly\",\n\"fifty\",\n\"fig\",\n\"figaro\",\n\"figbird\",\n\"figent\",\n\"figged\",\n\"figgery\",\n\"figging\",\n\"figgle\",\n\"figgy\",\n\"fight\",\n\"fighter\",\n\"figless\",\n\"figlike\",\n\"figment\",\n\"figural\",\n\"figure\",\n\"figured\",\n\"figurer\",\n\"figury\",\n\"figworm\",\n\"figwort\",\n\"fike\",\n\"fikie\",\n\"filace\",\n\"filacer\",\n\"filao\",\n\"filar\",\n\"filaria\",\n\"filasse\",\n\"filate\",\n\"filator\",\n\"filbert\",\n\"filch\",\n\"filcher\",\n\"file\",\n\"filemot\",\n\"filer\",\n\"filet\",\n\"filial\",\n\"filiate\",\n\"filibeg\",\n\"filical\",\n\"filicic\",\n\"filicin\",\n\"filiety\",\n\"filing\",\n\"filings\",\n\"filippo\",\n\"filite\",\n\"fill\",\n\"filled\",\n\"filler\",\n\"fillet\",\n\"filleul\",\n\"filling\",\n\"fillip\",\n\"fillock\",\n\"filly\",\n\"film\",\n\"filmdom\",\n\"filmet\",\n\"filmic\",\n\"filmily\",\n\"filmish\",\n\"filmist\",\n\"filmize\",\n\"filmy\",\n\"filo\",\n\"filose\",\n\"fils\",\n\"filter\",\n\"filth\",\n\"filthy\",\n\"fimble\",\n\"fimbria\",\n\"fin\",\n\"finable\",\n\"finagle\",\n\"final\",\n\"finale\",\n\"finally\",\n\"finance\",\n\"finback\",\n\"finch\",\n\"finched\",\n\"find\",\n\"findal\",\n\"finder\",\n\"finding\",\n\"findjan\",\n\"fine\",\n\"fineish\",\n\"finely\",\n\"finer\",\n\"finery\",\n\"finesse\",\n\"finetop\",\n\"finfish\",\n\"finfoot\",\n\"fingent\",\n\"finger\",\n\"fingery\",\n\"finial\",\n\"finical\",\n\"finick\",\n\"finific\",\n\"finify\",\n\"finikin\",\n\"fining\",\n\"finis\",\n\"finish\",\n\"finite\",\n\"finity\",\n\"finjan\",\n\"fink\",\n\"finkel\",\n\"finland\",\n\"finless\",\n\"finlet\",\n\"finlike\",\n\"finnac\",\n\"finned\",\n\"finner\",\n\"finnip\",\n\"finny\",\n\"fiord\",\n\"fiorded\",\n\"fiorin\",\n\"fiorite\",\n\"fip\",\n\"fipenny\",\n\"fipple\",\n\"fique\",\n\"fir\",\n\"firca\",\n\"fire\",\n\"firearm\",\n\"firebox\",\n\"fireboy\",\n\"firebug\",\n\"fired\",\n\"firedog\",\n\"firefly\",\n\"firelit\",\n\"fireman\",\n\"firer\",\n\"firetop\",\n\"firing\",\n\"firk\",\n\"firker\",\n\"firkin\",\n\"firlot\",\n\"firm\",\n\"firman\",\n\"firmer\",\n\"firmly\",\n\"firn\",\n\"firring\",\n\"firry\",\n\"first\",\n\"firstly\",\n\"firth\",\n\"fisc\",\n\"fiscal\",\n\"fise\",\n\"fisetin\",\n\"fish\",\n\"fishbed\",\n\"fished\",\n\"fisher\",\n\"fishery\",\n\"fishet\",\n\"fisheye\",\n\"fishful\",\n\"fishgig\",\n\"fishify\",\n\"fishily\",\n\"fishing\",\n\"fishlet\",\n\"fishman\",\n\"fishpot\",\n\"fishway\",\n\"fishy\",\n\"fisnoga\",\n\"fissate\",\n\"fissile\",\n\"fission\",\n\"fissive\",\n\"fissure\",\n\"fissury\",\n\"fist\",\n\"fisted\",\n\"fister\",\n\"fistful\",\n\"fistic\",\n\"fistify\",\n\"fisting\",\n\"fistuca\",\n\"fistula\",\n\"fistule\",\n\"fisty\",\n\"fit\",\n\"fitch\",\n\"fitched\",\n\"fitchee\",\n\"fitcher\",\n\"fitchet\",\n\"fitchew\",\n\"fitful\",\n\"fitly\",\n\"fitment\",\n\"fitness\",\n\"fitout\",\n\"fitroot\",\n\"fittage\",\n\"fitted\",\n\"fitten\",\n\"fitter\",\n\"fitters\",\n\"fittily\",\n\"fitting\",\n\"fitty\",\n\"fitweed\",\n\"five\",\n\"fivebar\",\n\"fiver\",\n\"fives\",\n\"fix\",\n\"fixable\",\n\"fixage\",\n\"fixate\",\n\"fixatif\",\n\"fixator\",\n\"fixed\",\n\"fixedly\",\n\"fixer\",\n\"fixing\",\n\"fixity\",\n\"fixture\",\n\"fixure\",\n\"fizgig\",\n\"fizz\",\n\"fizzer\",\n\"fizzle\",\n\"fizzy\",\n\"fjeld\",\n\"flabby\",\n\"flabrum\",\n\"flaccid\",\n\"flack\",\n\"flacked\",\n\"flacker\",\n\"flacket\",\n\"flaff\",\n\"flaffer\",\n\"flag\",\n\"flagger\",\n\"flaggy\",\n\"flaglet\",\n\"flagman\",\n\"flagon\",\n\"flail\",\n\"flair\",\n\"flaith\",\n\"flak\",\n\"flakage\",\n\"flake\",\n\"flaker\",\n\"flakily\",\n\"flaky\",\n\"flam\",\n\"flamant\",\n\"flamb\",\n\"flame\",\n\"flamed\",\n\"flamen\",\n\"flamer\",\n\"flamfew\",\n\"flaming\",\n\"flamy\",\n\"flan\",\n\"flanch\",\n\"flandan\",\n\"flane\",\n\"flange\",\n\"flanger\",\n\"flank\",\n\"flanked\",\n\"flanker\",\n\"flanky\",\n\"flannel\",\n\"flanque\",\n\"flap\",\n\"flapper\",\n\"flare\",\n\"flaring\",\n\"flary\",\n\"flaser\",\n\"flash\",\n\"flasher\",\n\"flashet\",\n\"flashly\",\n\"flashy\",\n\"flask\",\n\"flasker\",\n\"flasket\",\n\"flasque\",\n\"flat\",\n\"flatcap\",\n\"flatcar\",\n\"flatdom\",\n\"flated\",\n\"flathat\",\n\"flatlet\",\n\"flatly\",\n\"flatman\",\n\"flatten\",\n\"flatter\",\n\"flattie\",\n\"flattop\",\n\"flatus\",\n\"flatway\",\n\"flaught\",\n\"flaunt\",\n\"flaunty\",\n\"flavedo\",\n\"flavic\",\n\"flavid\",\n\"flavin\",\n\"flavine\",\n\"flavo\",\n\"flavone\",\n\"flavor\",\n\"flavory\",\n\"flavour\",\n\"flaw\",\n\"flawed\",\n\"flawful\",\n\"flawn\",\n\"flawy\",\n\"flax\",\n\"flaxen\",\n\"flaxman\",\n\"flaxy\",\n\"flay\",\n\"flayer\",\n\"flea\",\n\"fleam\",\n\"fleay\",\n\"flebile\",\n\"fleche\",\n\"fleck\",\n\"flecken\",\n\"flecker\",\n\"flecky\",\n\"flector\",\n\"fled\",\n\"fledge\",\n\"fledgy\",\n\"flee\",\n\"fleece\",\n\"fleeced\",\n\"fleecer\",\n\"fleech\",\n\"fleecy\",\n\"fleer\",\n\"fleerer\",\n\"fleet\",\n\"fleeter\",\n\"fleetly\",\n\"flemish\",\n\"flench\",\n\"flense\",\n\"flenser\",\n\"flerry\",\n\"flesh\",\n\"fleshed\",\n\"fleshen\",\n\"flesher\",\n\"fleshly\",\n\"fleshy\",\n\"flet\",\n\"fletch\",\n\"flether\",\n\"fleuret\",\n\"fleury\",\n\"flew\",\n\"flewed\",\n\"flewit\",\n\"flews\",\n\"flex\",\n\"flexed\",\n\"flexile\",\n\"flexion\",\n\"flexor\",\n\"flexure\",\n\"fley\",\n\"flick\",\n\"flicker\",\n\"flicky\",\n\"flidder\",\n\"flier\",\n\"fligger\",\n\"flight\",\n\"flighty\",\n\"flimmer\",\n\"flimp\",\n\"flimsy\",\n\"flinch\",\n\"flinder\",\n\"fling\",\n\"flinger\",\n\"flingy\",\n\"flint\",\n\"flinter\",\n\"flinty\",\n\"flioma\",\n\"flip\",\n\"flipe\",\n\"flipper\",\n\"flirt\",\n\"flirter\",\n\"flirty\",\n\"flisk\",\n\"flisky\",\n\"flit\",\n\"flitch\",\n\"flite\",\n\"fliting\",\n\"flitter\",\n\"flivver\",\n\"flix\",\n\"float\",\n\"floater\",\n\"floaty\",\n\"flob\",\n\"flobby\",\n\"floc\",\n\"floccus\",\n\"flock\",\n\"flocker\",\n\"flocky\",\n\"flocoon\",\n\"flodge\",\n\"floe\",\n\"floey\",\n\"flog\",\n\"flogger\",\n\"flokite\",\n\"flong\",\n\"flood\",\n\"flooded\",\n\"flooder\",\n\"floody\",\n\"floor\",\n\"floorer\",\n\"floozy\",\n\"flop\",\n\"flopper\",\n\"floppy\",\n\"flora\",\n\"floral\",\n\"floran\",\n\"florate\",\n\"floreal\",\n\"florent\",\n\"flores\",\n\"floret\",\n\"florid\",\n\"florin\",\n\"florist\",\n\"floroon\",\n\"florula\",\n\"flory\",\n\"flosh\",\n\"floss\",\n\"flosser\",\n\"flossy\",\n\"flot\",\n\"flota\",\n\"flotage\",\n\"flotant\",\n\"flotsam\",\n\"flounce\",\n\"flour\",\n\"floury\",\n\"flouse\",\n\"flout\",\n\"flouter\",\n\"flow\",\n\"flowage\",\n\"flower\",\n\"flowery\",\n\"flowing\",\n\"flown\",\n\"flowoff\",\n\"flu\",\n\"fluate\",\n\"fluavil\",\n\"flub\",\n\"flubdub\",\n\"flucan\",\n\"flue\",\n\"flued\",\n\"flueman\",\n\"fluency\",\n\"fluent\",\n\"fluer\",\n\"fluey\",\n\"fluff\",\n\"fluffer\",\n\"fluffy\",\n\"fluible\",\n\"fluid\",\n\"fluidal\",\n\"fluidic\",\n\"fluidly\",\n\"fluke\",\n\"fluked\",\n\"flukily\",\n\"fluking\",\n\"fluky\",\n\"flume\",\n\"flummer\",\n\"flummox\",\n\"flump\",\n\"flung\",\n\"flunk\",\n\"flunker\",\n\"flunky\",\n\"fluor\",\n\"fluoran\",\n\"fluoric\",\n\"fluoryl\",\n\"flurn\",\n\"flurr\",\n\"flurry\",\n\"flush\",\n\"flusher\",\n\"flushy\",\n\"flusk\",\n\"flusker\",\n\"fluster\",\n\"flute\",\n\"fluted\",\n\"fluter\",\n\"flutina\",\n\"fluting\",\n\"flutist\",\n\"flutter\",\n\"fluty\",\n\"fluvial\",\n\"flux\",\n\"fluxer\",\n\"fluxile\",\n\"fluxion\",\n\"fly\",\n\"flyable\",\n\"flyaway\",\n\"flyback\",\n\"flyball\",\n\"flybane\",\n\"flybelt\",\n\"flyblow\",\n\"flyboat\",\n\"flyboy\",\n\"flyer\",\n\"flyflap\",\n\"flying\",\n\"flyleaf\",\n\"flyless\",\n\"flyman\",\n\"flyness\",\n\"flype\",\n\"flytail\",\n\"flytier\",\n\"flytrap\",\n\"flyway\",\n\"flywort\",\n\"foal\",\n\"foaly\",\n\"foam\",\n\"foambow\",\n\"foamer\",\n\"foamily\",\n\"foaming\",\n\"foamy\",\n\"fob\",\n\"focal\",\n\"focally\",\n\"foci\",\n\"focoids\",\n\"focsle\",\n\"focus\",\n\"focuser\",\n\"fod\",\n\"fodda\",\n\"fodder\",\n\"foder\",\n\"fodge\",\n\"fodgel\",\n\"fodient\",\n\"foe\",\n\"foehn\",\n\"foeish\",\n\"foeless\",\n\"foelike\",\n\"foeman\",\n\"foeship\",\n\"fog\",\n\"fogbow\",\n\"fogdog\",\n\"fogdom\",\n\"fogey\",\n\"foggage\",\n\"fogged\",\n\"fogger\",\n\"foggily\",\n\"foggish\",\n\"foggy\",\n\"foghorn\",\n\"fogle\",\n\"fogless\",\n\"fogman\",\n\"fogo\",\n\"fogon\",\n\"fogou\",\n\"fogram\",\n\"fogus\",\n\"fogy\",\n\"fogydom\",\n\"fogyish\",\n\"fogyism\",\n\"fohat\",\n\"foible\",\n\"foil\",\n\"foiler\",\n\"foiling\",\n\"foining\",\n\"foison\",\n\"foist\",\n\"foister\",\n\"foisty\",\n\"foiter\",\n\"fold\",\n\"foldage\",\n\"folded\",\n\"folden\",\n\"folder\",\n\"folding\",\n\"foldure\",\n\"foldy\",\n\"fole\",\n\"folia\",\n\"foliage\",\n\"folial\",\n\"foliar\",\n\"foliary\",\n\"foliate\",\n\"folie\",\n\"folio\",\n\"foliole\",\n\"foliose\",\n\"foliot\",\n\"folious\",\n\"folium\",\n\"folk\",\n\"folkmot\",\n\"folksy\",\n\"folkway\",\n\"folky\",\n\"folles\",\n\"follis\",\n\"follow\",\n\"folly\",\n\"foment\",\n\"fomes\",\n\"fomites\",\n\"fondak\",\n\"fondant\",\n\"fondish\",\n\"fondle\",\n\"fondler\",\n\"fondly\",\n\"fondu\",\n\"fondue\",\n\"fonduk\",\n\"fonly\",\n\"fonnish\",\n\"fono\",\n\"fons\",\n\"font\",\n\"fontal\",\n\"fonted\",\n\"fontful\",\n\"fontlet\",\n\"foo\",\n\"food\",\n\"fooder\",\n\"foodful\",\n\"foody\",\n\"fool\",\n\"fooldom\",\n\"foolery\",\n\"fooless\",\n\"fooling\",\n\"foolish\",\n\"fooner\",\n\"fooster\",\n\"foot\",\n\"footage\",\n\"footboy\",\n\"footed\",\n\"footer\",\n\"footful\",\n\"foothot\",\n\"footing\",\n\"footle\",\n\"footler\",\n\"footman\",\n\"footpad\",\n\"foots\",\n\"footway\",\n\"footy\",\n\"foozle\",\n\"foozler\",\n\"fop\",\n\"fopling\",\n\"foppery\",\n\"foppish\",\n\"foppy\",\n\"fopship\",\n\"for\",\n\"fora\",\n\"forage\",\n\"forager\",\n\"foramen\",\n\"forane\",\n\"foray\",\n\"forayer\",\n\"forb\",\n\"forbade\",\n\"forbar\",\n\"forbear\",\n\"forbid\",\n\"forbit\",\n\"forbled\",\n\"forblow\",\n\"forbore\",\n\"forbow\",\n\"forby\",\n\"force\",\n\"forced\",\n\"forceps\",\n\"forcer\",\n\"forche\",\n\"forcing\",\n\"ford\",\n\"fordays\",\n\"fording\",\n\"fordo\",\n\"fordone\",\n\"fordy\",\n\"fore\",\n\"foreact\",\n\"forearm\",\n\"forebay\",\n\"forecar\",\n\"foreday\",\n\"forefin\",\n\"forefit\",\n\"forego\",\n\"foreign\",\n\"forel\",\n\"forelay\",\n\"foreleg\",\n\"foreman\",\n\"forepad\",\n\"forepaw\",\n\"foreran\",\n\"forerib\",\n\"forerun\",\n\"foresay\",\n\"foresee\",\n\"foreset\",\n\"foresin\",\n\"forest\",\n\"foresty\",\n\"foretop\",\n\"foreuse\",\n\"forever\",\n\"forevow\",\n\"forfar\",\n\"forfare\",\n\"forfars\",\n\"forfeit\",\n\"forfend\",\n\"forge\",\n\"forged\",\n\"forger\",\n\"forgery\",\n\"forget\",\n\"forgie\",\n\"forging\",\n\"forgive\",\n\"forgo\",\n\"forgoer\",\n\"forgot\",\n\"forgrow\",\n\"forhoo\",\n\"forhooy\",\n\"forhow\",\n\"forint\",\n\"fork\",\n\"forked\",\n\"forker\",\n\"forkful\",\n\"forkman\",\n\"forky\",\n\"forleft\",\n\"forlet\",\n\"forlorn\",\n\"form\",\n\"formal\",\n\"formant\",\n\"format\",\n\"formate\",\n\"forme\",\n\"formed\",\n\"formee\",\n\"formel\",\n\"formene\",\n\"former\",\n\"formful\",\n\"formic\",\n\"formin\",\n\"forming\",\n\"formose\",\n\"formula\",\n\"formule\",\n\"formy\",\n\"formyl\",\n\"fornent\",\n\"fornix\",\n\"forpet\",\n\"forpine\",\n\"forpit\",\n\"forrad\",\n\"forrard\",\n\"forride\",\n\"forrit\",\n\"forrue\",\n\"forsake\",\n\"forset\",\n\"forslow\",\n\"fort\",\n\"forte\",\n\"forth\",\n\"forthgo\",\n\"forthy\",\n\"forties\",\n\"fortify\",\n\"fortin\",\n\"fortis\",\n\"fortlet\",\n\"fortune\",\n\"forty\",\n\"forum\",\n\"forward\",\n\"forwean\",\n\"forwent\",\n\"fosh\",\n\"fosie\",\n\"fossa\",\n\"fossage\",\n\"fossane\",\n\"fosse\",\n\"fossed\",\n\"fossick\",\n\"fossil\",\n\"fossor\",\n\"fossula\",\n\"fossule\",\n\"fostell\",\n\"foster\",\n\"fot\",\n\"fotch\",\n\"fother\",\n\"fotmal\",\n\"fotui\",\n\"fou\",\n\"foud\",\n\"fouette\",\n\"fougade\",\n\"fought\",\n\"foughty\",\n\"foujdar\",\n\"foul\",\n\"foulage\",\n\"foulard\",\n\"fouler\",\n\"fouling\",\n\"foulish\",\n\"foully\",\n\"foumart\",\n\"foun\",\n\"found\",\n\"founder\",\n\"foundry\",\n\"fount\",\n\"four\",\n\"fourble\",\n\"fourche\",\n\"fourer\",\n\"fourre\",\n\"fourth\",\n\"foussa\",\n\"foute\",\n\"fouter\",\n\"fouth\",\n\"fovea\",\n\"foveal\",\n\"foveate\",\n\"foveola\",\n\"foveole\",\n\"fow\",\n\"fowk\",\n\"fowl\",\n\"fowler\",\n\"fowlery\",\n\"fowling\",\n\"fox\",\n\"foxbane\",\n\"foxchop\",\n\"foxer\",\n\"foxery\",\n\"foxfeet\",\n\"foxfish\",\n\"foxhole\",\n\"foxily\",\n\"foxing\",\n\"foxish\",\n\"foxlike\",\n\"foxship\",\n\"foxskin\",\n\"foxtail\",\n\"foxwood\",\n\"foxy\",\n\"foy\",\n\"foyaite\",\n\"foyboat\",\n\"foyer\",\n\"fozy\",\n\"fra\",\n\"frab\",\n\"frabbit\",\n\"frabous\",\n\"fracas\",\n\"frache\",\n\"frack\",\n\"fracted\",\n\"frae\",\n\"fraghan\",\n\"fragile\",\n\"fraid\",\n\"fraik\",\n\"frail\",\n\"frailly\",\n\"frailty\",\n\"fraise\",\n\"fraiser\",\n\"frame\",\n\"framea\",\n\"framed\",\n\"framer\",\n\"framing\",\n\"frammit\",\n\"franc\",\n\"franco\",\n\"frank\",\n\"franker\",\n\"frankly\",\n\"frantic\",\n\"franzy\",\n\"frap\",\n\"frappe\",\n\"frasco\",\n\"frase\",\n\"frasier\",\n\"frass\",\n\"frat\",\n\"fratch\",\n\"fratchy\",\n\"frater\",\n\"fratery\",\n\"fratry\",\n\"fraud\",\n\"fraught\",\n\"frawn\",\n\"fraxin\",\n\"fray\",\n\"frayed\",\n\"fraying\",\n\"frayn\",\n\"fraze\",\n\"frazer\",\n\"frazil\",\n\"frazzle\",\n\"freak\",\n\"freaky\",\n\"fream\",\n\"freath\",\n\"freck\",\n\"frecken\",\n\"frecket\",\n\"freckle\",\n\"freckly\",\n\"free\",\n\"freed\",\n\"freedom\",\n\"freeing\",\n\"freeish\",\n\"freely\",\n\"freeman\",\n\"freer\",\n\"freet\",\n\"freety\",\n\"freeway\",\n\"freeze\",\n\"freezer\",\n\"freight\",\n\"freir\",\n\"freit\",\n\"freity\",\n\"fremd\",\n\"fremdly\",\n\"frenal\",\n\"frenate\",\n\"frenum\",\n\"frenzy\",\n\"fresco\",\n\"fresh\",\n\"freshen\",\n\"freshet\",\n\"freshly\",\n\"fresnel\",\n\"fresno\",\n\"fret\",\n\"fretful\",\n\"frett\",\n\"frette\",\n\"fretted\",\n\"fretter\",\n\"fretty\",\n\"fretum\",\n\"friable\",\n\"friand\",\n\"friar\",\n\"friarly\",\n\"friary\",\n\"frib\",\n\"fribble\",\n\"fribby\",\n\"fried\",\n\"friend\",\n\"frier\",\n\"frieze\",\n\"friezer\",\n\"friezy\",\n\"frig\",\n\"frigate\",\n\"friggle\",\n\"fright\",\n\"frighty\",\n\"frigid\",\n\"frijol\",\n\"frike\",\n\"frill\",\n\"frilled\",\n\"friller\",\n\"frilly\",\n\"frim\",\n\"fringe\",\n\"fringed\",\n\"fringy\",\n\"frisca\",\n\"frisk\",\n\"frisker\",\n\"frisket\",\n\"frisky\",\n\"frison\",\n\"frist\",\n\"frisure\",\n\"frit\",\n\"frith\",\n\"fritt\",\n\"fritter\",\n\"frivol\",\n\"frixion\",\n\"friz\",\n\"frize\",\n\"frizer\",\n\"frizz\",\n\"frizzer\",\n\"frizzle\",\n\"frizzly\",\n\"frizzy\",\n\"fro\",\n\"frock\",\n\"froe\",\n\"frog\",\n\"frogbit\",\n\"frogeye\",\n\"frogged\",\n\"froggy\",\n\"frogleg\",\n\"froglet\",\n\"frogman\",\n\"froise\",\n\"frolic\",\n\"from\",\n\"frond\",\n\"fronded\",\n\"front\",\n\"frontad\",\n\"frontal\",\n\"fronted\",\n\"fronter\",\n\"froom\",\n\"frore\",\n\"frory\",\n\"frosh\",\n\"frost\",\n\"frosted\",\n\"froster\",\n\"frosty\",\n\"frot\",\n\"froth\",\n\"frother\",\n\"frothy\",\n\"frotton\",\n\"frough\",\n\"froughy\",\n\"frounce\",\n\"frow\",\n\"froward\",\n\"frower\",\n\"frowl\",\n\"frown\",\n\"frowner\",\n\"frowny\",\n\"frowst\",\n\"frowsty\",\n\"frowy\",\n\"frowze\",\n\"frowzly\",\n\"frowzy\",\n\"froze\",\n\"frozen\",\n\"fructed\",\n\"frugal\",\n\"fruggan\",\n\"fruit\",\n\"fruited\",\n\"fruiter\",\n\"fruity\",\n\"frump\",\n\"frumple\",\n\"frumpy\",\n\"frush\",\n\"frustum\",\n\"frutify\",\n\"fry\",\n\"fryer\",\n\"fu\",\n\"fub\",\n\"fubby\",\n\"fubsy\",\n\"fucate\",\n\"fuchsin\",\n\"fuci\",\n\"fucoid\",\n\"fucosan\",\n\"fucose\",\n\"fucous\",\n\"fucus\",\n\"fud\",\n\"fuddle\",\n\"fuddler\",\n\"fuder\",\n\"fudge\",\n\"fudger\",\n\"fudgy\",\n\"fuel\",\n\"fueler\",\n\"fuerte\",\n\"fuff\",\n\"fuffy\",\n\"fugal\",\n\"fugally\",\n\"fuggy\",\n\"fugient\",\n\"fugle\",\n\"fugler\",\n\"fugu\",\n\"fugue\",\n\"fuguist\",\n\"fuidhir\",\n\"fuji\",\n\"fulcral\",\n\"fulcrum\",\n\"fulfill\",\n\"fulgent\",\n\"fulgid\",\n\"fulgide\",\n\"fulgor\",\n\"fulham\",\n\"fulk\",\n\"full\",\n\"fullam\",\n\"fuller\",\n\"fullery\",\n\"fulling\",\n\"fullish\",\n\"fullom\",\n\"fully\",\n\"fulmar\",\n\"fulmine\",\n\"fulsome\",\n\"fulth\",\n\"fulvene\",\n\"fulvid\",\n\"fulvous\",\n\"fulwa\",\n\"fulyie\",\n\"fulzie\",\n\"fum\",\n\"fumado\",\n\"fumage\",\n\"fumaric\",\n\"fumaryl\",\n\"fumble\",\n\"fumbler\",\n\"fume\",\n\"fumer\",\n\"fumet\",\n\"fumette\",\n\"fumily\",\n\"fuming\",\n\"fumose\",\n\"fumous\",\n\"fumy\",\n\"fun\",\n\"fund\",\n\"fundal\",\n\"funded\",\n\"funder\",\n\"fundi\",\n\"fundic\",\n\"funds\",\n\"fundus\",\n\"funeral\",\n\"funest\",\n\"fungal\",\n\"fungate\",\n\"fungi\",\n\"fungian\",\n\"fungic\",\n\"fungin\",\n\"fungo\",\n\"fungoid\",\n\"fungose\",\n\"fungous\",\n\"fungus\",\n\"fungusy\",\n\"funicle\",\n\"funis\",\n\"funk\",\n\"funker\",\n\"funky\",\n\"funnel\",\n\"funnily\",\n\"funny\",\n\"funori\",\n\"funt\",\n\"fur\",\n\"fural\",\n\"furan\",\n\"furazan\",\n\"furbish\",\n\"furca\",\n\"furcal\",\n\"furcate\",\n\"furcula\",\n\"furdel\",\n\"furfur\",\n\"furiant\",\n\"furied\",\n\"furify\",\n\"furil\",\n\"furilic\",\n\"furiosa\",\n\"furioso\",\n\"furious\",\n\"furison\",\n\"furl\",\n\"furler\",\n\"furless\",\n\"furlong\",\n\"furnace\",\n\"furnage\",\n\"furner\",\n\"furnish\",\n\"furoic\",\n\"furoid\",\n\"furoin\",\n\"furole\",\n\"furor\",\n\"furore\",\n\"furphy\",\n\"furred\",\n\"furrier\",\n\"furrily\",\n\"furring\",\n\"furrow\",\n\"furrowy\",\n\"furry\",\n\"further\",\n\"furtive\",\n\"fury\",\n\"furyl\",\n\"furze\",\n\"furzed\",\n\"furzery\",\n\"furzy\",\n\"fusain\",\n\"fusate\",\n\"fusc\",\n\"fuscin\",\n\"fuscous\",\n\"fuse\",\n\"fused\",\n\"fusee\",\n\"fusht\",\n\"fusible\",\n\"fusibly\",\n\"fusil\",\n\"fusilly\",\n\"fusion\",\n\"fusoid\",\n\"fuss\",\n\"fusser\",\n\"fussify\",\n\"fussily\",\n\"fussock\",\n\"fussy\",\n\"fust\",\n\"fustee\",\n\"fustet\",\n\"fustian\",\n\"fustic\",\n\"fustily\",\n\"fustin\",\n\"fustle\",\n\"fusty\",\n\"fusuma\",\n\"fusure\",\n\"fut\",\n\"futchel\",\n\"fute\",\n\"futhorc\",\n\"futile\",\n\"futtock\",\n\"futural\",\n\"future\",\n\"futuric\",\n\"futwa\",\n\"fuye\",\n\"fuze\",\n\"fuzz\",\n\"fuzzily\",\n\"fuzzy\",\n\"fyke\",\n\"fylfot\",\n\"fyrd\",\n\"g\",\n\"ga\",\n\"gab\",\n\"gabbard\",\n\"gabber\",\n\"gabble\",\n\"gabbler\",\n\"gabbro\",\n\"gabby\",\n\"gabelle\",\n\"gabgab\",\n\"gabi\",\n\"gabion\",\n\"gable\",\n\"gablet\",\n\"gablock\",\n\"gaby\",\n\"gad\",\n\"gadbee\",\n\"gadbush\",\n\"gadded\",\n\"gadder\",\n\"gaddi\",\n\"gadding\",\n\"gaddish\",\n\"gade\",\n\"gadfly\",\n\"gadge\",\n\"gadger\",\n\"gadget\",\n\"gadid\",\n\"gadling\",\n\"gadman\",\n\"gadoid\",\n\"gadroon\",\n\"gadsman\",\n\"gaduin\",\n\"gadwall\",\n\"gaen\",\n\"gaet\",\n\"gaff\",\n\"gaffe\",\n\"gaffer\",\n\"gaffle\",\n\"gag\",\n\"gagate\",\n\"gage\",\n\"gagee\",\n\"gageite\",\n\"gager\",\n\"gagger\",\n\"gaggery\",\n\"gaggle\",\n\"gaggler\",\n\"gagman\",\n\"gagor\",\n\"gagroot\",\n\"gahnite\",\n\"gaiassa\",\n\"gaiety\",\n\"gaily\",\n\"gain\",\n\"gainage\",\n\"gaine\",\n\"gainer\",\n\"gainful\",\n\"gaining\",\n\"gainly\",\n\"gains\",\n\"gainsay\",\n\"gainset\",\n\"gainst\",\n\"gair\",\n\"gait\",\n\"gaited\",\n\"gaiter\",\n\"gaiting\",\n\"gaize\",\n\"gaj\",\n\"gal\",\n\"gala\",\n\"galah\",\n\"galanas\",\n\"galanga\",\n\"galant\",\n\"galany\",\n\"galatea\",\n\"galaxy\",\n\"galban\",\n\"gale\",\n\"galea\",\n\"galeage\",\n\"galeate\",\n\"galee\",\n\"galeeny\",\n\"galeid\",\n\"galena\",\n\"galenic\",\n\"galeoid\",\n\"galera\",\n\"galerum\",\n\"galerus\",\n\"galet\",\n\"galey\",\n\"galgal\",\n\"gali\",\n\"galilee\",\n\"galiot\",\n\"galipot\",\n\"gall\",\n\"galla\",\n\"gallah\",\n\"gallant\",\n\"gallate\",\n\"galled\",\n\"gallein\",\n\"galleon\",\n\"galler\",\n\"gallery\",\n\"gallet\",\n\"galley\",\n\"gallfly\",\n\"gallic\",\n\"galline\",\n\"galling\",\n\"gallium\",\n\"gallnut\",\n\"gallon\",\n\"galloon\",\n\"gallop\",\n\"gallous\",\n\"gallows\",\n\"gally\",\n\"galoot\",\n\"galop\",\n\"galore\",\n\"galosh\",\n\"galp\",\n\"galt\",\n\"galumph\",\n\"galuth\",\n\"galyac\",\n\"galyak\",\n\"gam\",\n\"gamahe\",\n\"gamasid\",\n\"gamb\",\n\"gamba\",\n\"gambade\",\n\"gambado\",\n\"gambang\",\n\"gambeer\",\n\"gambet\",\n\"gambia\",\n\"gambier\",\n\"gambist\",\n\"gambit\",\n\"gamble\",\n\"gambler\",\n\"gamboge\",\n\"gambol\",\n\"gambrel\",\n\"game\",\n\"gamebag\",\n\"gameful\",\n\"gamely\",\n\"gamene\",\n\"gametal\",\n\"gamete\",\n\"gametic\",\n\"gamic\",\n\"gamily\",\n\"gamin\",\n\"gaming\",\n\"gamma\",\n\"gammer\",\n\"gammick\",\n\"gammock\",\n\"gammon\",\n\"gammy\",\n\"gamont\",\n\"gamori\",\n\"gamp\",\n\"gamut\",\n\"gamy\",\n\"gan\",\n\"ganam\",\n\"ganch\",\n\"gander\",\n\"gandul\",\n\"gandum\",\n\"gane\",\n\"ganef\",\n\"gang\",\n\"ganga\",\n\"gangan\",\n\"gangava\",\n\"gangdom\",\n\"gange\",\n\"ganger\",\n\"ganging\",\n\"gangism\",\n\"ganglia\",\n\"gangly\",\n\"gangman\",\n\"gangrel\",\n\"gangue\",\n\"gangway\",\n\"ganja\",\n\"ganner\",\n\"gannet\",\n\"ganoid\",\n\"ganoin\",\n\"ganosis\",\n\"gansel\",\n\"gansey\",\n\"gansy\",\n\"gant\",\n\"ganta\",\n\"gantang\",\n\"gantlet\",\n\"ganton\",\n\"gantry\",\n\"gantsl\",\n\"ganza\",\n\"ganzie\",\n\"gaol\",\n\"gaoler\",\n\"gap\",\n\"gapa\",\n\"gape\",\n\"gaper\",\n\"gapes\",\n\"gaping\",\n\"gapo\",\n\"gappy\",\n\"gapy\",\n\"gar\",\n\"gara\",\n\"garad\",\n\"garage\",\n\"garance\",\n\"garava\",\n\"garawi\",\n\"garb\",\n\"garbage\",\n\"garbel\",\n\"garbell\",\n\"garbill\",\n\"garble\",\n\"garbler\",\n\"garboil\",\n\"garbure\",\n\"garce\",\n\"gardant\",\n\"gardeen\",\n\"garden\",\n\"gardeny\",\n\"gardy\",\n\"gare\",\n\"gareh\",\n\"garetta\",\n\"garfish\",\n\"garget\",\n\"gargety\",\n\"gargle\",\n\"gargol\",\n\"garial\",\n\"gariba\",\n\"garish\",\n\"garland\",\n\"garle\",\n\"garlic\",\n\"garment\",\n\"garn\",\n\"garnel\",\n\"garner\",\n\"garnet\",\n\"garnets\",\n\"garnett\",\n\"garnetz\",\n\"garnice\",\n\"garniec\",\n\"garnish\",\n\"garoo\",\n\"garrafa\",\n\"garran\",\n\"garret\",\n\"garrot\",\n\"garrote\",\n\"garrupa\",\n\"garse\",\n\"garsil\",\n\"garston\",\n\"garten\",\n\"garter\",\n\"garth\",\n\"garum\",\n\"garvey\",\n\"garvock\",\n\"gas\",\n\"gasbag\",\n\"gaseity\",\n\"gaseous\",\n\"gash\",\n\"gashes\",\n\"gashful\",\n\"gashly\",\n\"gashy\",\n\"gasify\",\n\"gasket\",\n\"gaskin\",\n\"gasking\",\n\"gaskins\",\n\"gasless\",\n\"gaslit\",\n\"gaslock\",\n\"gasman\",\n\"gasp\",\n\"gasper\",\n\"gasping\",\n\"gaspy\",\n\"gasser\",\n\"gassing\",\n\"gassy\",\n\"gast\",\n\"gaster\",\n\"gastral\",\n\"gastric\",\n\"gastrin\",\n\"gat\",\n\"gata\",\n\"gatch\",\n\"gate\",\n\"gateado\",\n\"gateage\",\n\"gated\",\n\"gateman\",\n\"gater\",\n\"gateway\",\n\"gather\",\n\"gating\",\n\"gator\",\n\"gatter\",\n\"gau\",\n\"gaub\",\n\"gauby\",\n\"gauche\",\n\"gaud\",\n\"gaudery\",\n\"gaudful\",\n\"gaudily\",\n\"gaudy\",\n\"gaufer\",\n\"gauffer\",\n\"gauffre\",\n\"gaufre\",\n\"gauge\",\n\"gauger\",\n\"gauging\",\n\"gaulin\",\n\"gault\",\n\"gaulter\",\n\"gaum\",\n\"gaumish\",\n\"gaumy\",\n\"gaun\",\n\"gaunt\",\n\"gaunted\",\n\"gauntly\",\n\"gauntry\",\n\"gaunty\",\n\"gaup\",\n\"gaupus\",\n\"gaur\",\n\"gaus\",\n\"gauss\",\n\"gauster\",\n\"gaut\",\n\"gauze\",\n\"gauzily\",\n\"gauzy\",\n\"gavall\",\n\"gave\",\n\"gavel\",\n\"gaveler\",\n\"gavial\",\n\"gavotte\",\n\"gavyuti\",\n\"gaw\",\n\"gawby\",\n\"gawcie\",\n\"gawk\",\n\"gawkily\",\n\"gawkish\",\n\"gawky\",\n\"gawm\",\n\"gawn\",\n\"gawney\",\n\"gawsie\",\n\"gay\",\n\"gayal\",\n\"gayatri\",\n\"gaybine\",\n\"gaycat\",\n\"gayish\",\n\"gayment\",\n\"gayness\",\n\"gaysome\",\n\"gayyou\",\n\"gaz\",\n\"gazabo\",\n\"gaze\",\n\"gazebo\",\n\"gazee\",\n\"gazel\",\n\"gazelle\",\n\"gazer\",\n\"gazette\",\n\"gazi\",\n\"gazing\",\n\"gazon\",\n\"gazy\",\n\"ge\",\n\"geal\",\n\"gean\",\n\"gear\",\n\"gearbox\",\n\"geared\",\n\"gearing\",\n\"gearman\",\n\"gearset\",\n\"gease\",\n\"geason\",\n\"geat\",\n\"gebang\",\n\"gebanga\",\n\"gebbie\",\n\"gebur\",\n\"geck\",\n\"gecko\",\n\"geckoid\",\n\"ged\",\n\"gedackt\",\n\"gedder\",\n\"gedeckt\",\n\"gedrite\",\n\"gee\",\n\"geebong\",\n\"geebung\",\n\"geejee\",\n\"geek\",\n\"geelbec\",\n\"geerah\",\n\"geest\",\n\"geet\",\n\"geezer\",\n\"gegg\",\n\"geggee\",\n\"gegger\",\n\"geggery\",\n\"gein\",\n\"geira\",\n\"geisha\",\n\"geison\",\n\"geitjie\",\n\"gel\",\n\"gelable\",\n\"gelada\",\n\"gelatin\",\n\"geld\",\n\"geldant\",\n\"gelder\",\n\"gelding\",\n\"gelid\",\n\"gelidly\",\n\"gelilah\",\n\"gell\",\n\"gelly\",\n\"gelong\",\n\"gelose\",\n\"gelosin\",\n\"gelt\",\n\"gem\",\n\"gemauve\",\n\"gemel\",\n\"gemeled\",\n\"gemless\",\n\"gemlike\",\n\"gemma\",\n\"gemmae\",\n\"gemmate\",\n\"gemmer\",\n\"gemmily\",\n\"gemmoid\",\n\"gemmula\",\n\"gemmule\",\n\"gemmy\",\n\"gemot\",\n\"gemsbok\",\n\"gemul\",\n\"gemuti\",\n\"gemwork\",\n\"gen\",\n\"gena\",\n\"genal\",\n\"genapp\",\n\"genarch\",\n\"gender\",\n\"gene\",\n\"genear\",\n\"geneat\",\n\"geneki\",\n\"genep\",\n\"genera\",\n\"general\",\n\"generic\",\n\"genesic\",\n\"genesis\",\n\"genet\",\n\"genetic\",\n\"geneva\",\n\"genial\",\n\"genian\",\n\"genic\",\n\"genie\",\n\"genii\",\n\"genin\",\n\"genion\",\n\"genip\",\n\"genipa\",\n\"genipap\",\n\"genista\",\n\"genital\",\n\"genitor\",\n\"genius\",\n\"genizah\",\n\"genoese\",\n\"genom\",\n\"genome\",\n\"genomic\",\n\"genos\",\n\"genre\",\n\"genro\",\n\"gens\",\n\"genson\",\n\"gent\",\n\"genteel\",\n\"gentes\",\n\"gentian\",\n\"gentile\",\n\"gentle\",\n\"gently\",\n\"gentman\",\n\"gentry\",\n\"genty\",\n\"genu\",\n\"genua\",\n\"genual\",\n\"genuine\",\n\"genus\",\n\"genys\",\n\"geo\",\n\"geobios\",\n\"geodal\",\n\"geode\",\n\"geodesy\",\n\"geodete\",\n\"geodic\",\n\"geodist\",\n\"geoduck\",\n\"geoform\",\n\"geogeny\",\n\"geogony\",\n\"geoid\",\n\"geoidal\",\n\"geology\",\n\"geomaly\",\n\"geomant\",\n\"geomyid\",\n\"geonoma\",\n\"geopony\",\n\"georama\",\n\"georgic\",\n\"geosid\",\n\"geoside\",\n\"geotaxy\",\n\"geotic\",\n\"geoty\",\n\"ger\",\n\"gerah\",\n\"geranic\",\n\"geranyl\",\n\"gerate\",\n\"gerated\",\n\"geratic\",\n\"geraty\",\n\"gerb\",\n\"gerbe\",\n\"gerbil\",\n\"gercrow\",\n\"gerefa\",\n\"gerenda\",\n\"gerent\",\n\"gerenuk\",\n\"gerim\",\n\"gerip\",\n\"germ\",\n\"germal\",\n\"german\",\n\"germane\",\n\"germen\",\n\"germin\",\n\"germina\",\n\"germing\",\n\"germon\",\n\"germule\",\n\"germy\",\n\"gernitz\",\n\"geront\",\n\"geronto\",\n\"gers\",\n\"gersum\",\n\"gerund\",\n\"gerusia\",\n\"gervao\",\n\"gesith\",\n\"gesning\",\n\"gesso\",\n\"gest\",\n\"gestant\",\n\"gestate\",\n\"geste\",\n\"gested\",\n\"gesten\",\n\"gestic\",\n\"gestion\",\n\"gesture\",\n\"get\",\n\"geta\",\n\"getah\",\n\"getaway\",\n\"gether\",\n\"getling\",\n\"getter\",\n\"getting\",\n\"getup\",\n\"geum\",\n\"gewgaw\",\n\"gewgawy\",\n\"gey\",\n\"geyan\",\n\"geyser\",\n\"gez\",\n\"ghafir\",\n\"ghaist\",\n\"ghalva\",\n\"gharial\",\n\"gharnao\",\n\"gharry\",\n\"ghastly\",\n\"ghat\",\n\"ghatti\",\n\"ghatwal\",\n\"ghazi\",\n\"ghazism\",\n\"ghebeta\",\n\"ghee\",\n\"gheleem\",\n\"gherkin\",\n\"ghetti\",\n\"ghetto\",\n\"ghizite\",\n\"ghoom\",\n\"ghost\",\n\"ghoster\",\n\"ghostly\",\n\"ghosty\",\n\"ghoul\",\n\"ghrush\",\n\"ghurry\",\n\"giant\",\n\"giantly\",\n\"giantry\",\n\"giardia\",\n\"giarra\",\n\"giarre\",\n\"gib\",\n\"gibaro\",\n\"gibbals\",\n\"gibbed\",\n\"gibber\",\n\"gibbet\",\n\"gibbles\",\n\"gibbon\",\n\"gibbose\",\n\"gibbous\",\n\"gibbus\",\n\"gibby\",\n\"gibe\",\n\"gibel\",\n\"giber\",\n\"gibing\",\n\"gibleh\",\n\"giblet\",\n\"giblets\",\n\"gibus\",\n\"gid\",\n\"giddap\",\n\"giddea\",\n\"giddify\",\n\"giddily\",\n\"giddy\",\n\"gidgee\",\n\"gie\",\n\"gied\",\n\"gien\",\n\"gif\",\n\"gift\",\n\"gifted\",\n\"giftie\",\n\"gig\",\n\"gigback\",\n\"gigeria\",\n\"gigful\",\n\"gigger\",\n\"giggish\",\n\"giggit\",\n\"giggle\",\n\"giggler\",\n\"giggly\",\n\"giglet\",\n\"giglot\",\n\"gigman\",\n\"gignate\",\n\"gigolo\",\n\"gigot\",\n\"gigsman\",\n\"gigster\",\n\"gigtree\",\n\"gigunu\",\n\"gilbert\",\n\"gild\",\n\"gilded\",\n\"gilden\",\n\"gilder\",\n\"gilding\",\n\"gilguy\",\n\"gilia\",\n\"gilim\",\n\"gill\",\n\"gilled\",\n\"giller\",\n\"gillie\",\n\"gilling\",\n\"gilly\",\n\"gilo\",\n\"gilpy\",\n\"gilse\",\n\"gilt\",\n\"giltcup\",\n\"gim\",\n\"gimbal\",\n\"gimble\",\n\"gimel\",\n\"gimlet\",\n\"gimlety\",\n\"gimmal\",\n\"gimmer\",\n\"gimmick\",\n\"gimp\",\n\"gimped\",\n\"gimper\",\n\"gimping\",\n\"gin\",\n\"ging\",\n\"ginger\",\n\"gingery\",\n\"gingham\",\n\"gingili\",\n\"gingiva\",\n\"gink\",\n\"ginkgo\",\n\"ginned\",\n\"ginner\",\n\"ginners\",\n\"ginnery\",\n\"ginney\",\n\"ginning\",\n\"ginnle\",\n\"ginny\",\n\"ginseng\",\n\"ginward\",\n\"gio\",\n\"gip\",\n\"gipon\",\n\"gipper\",\n\"gipser\",\n\"gipsire\",\n\"giraffe\",\n\"girasol\",\n\"girba\",\n\"gird\",\n\"girder\",\n\"girding\",\n\"girdle\",\n\"girdler\",\n\"girl\",\n\"girleen\",\n\"girlery\",\n\"girlie\",\n\"girling\",\n\"girlish\",\n\"girlism\",\n\"girly\",\n\"girn\",\n\"girny\",\n\"giro\",\n\"girr\",\n\"girse\",\n\"girsh\",\n\"girsle\",\n\"girt\",\n\"girth\",\n\"gisarme\",\n\"gish\",\n\"gisla\",\n\"gisler\",\n\"gist\",\n\"git\",\n\"gitalin\",\n\"gith\",\n\"gitonin\",\n\"gitoxin\",\n\"gittern\",\n\"gittith\",\n\"give\",\n\"given\",\n\"giver\",\n\"givey\",\n\"giving\",\n\"gizz\",\n\"gizzard\",\n\"gizzen\",\n\"gizzern\",\n\"glace\",\n\"glaceed\",\n\"glacial\",\n\"glacier\",\n\"glacis\",\n\"glack\",\n\"glad\",\n\"gladden\",\n\"gladdon\",\n\"gladdy\",\n\"glade\",\n\"gladeye\",\n\"gladful\",\n\"gladify\",\n\"gladii\",\n\"gladius\",\n\"gladly\",\n\"glady\",\n\"glaga\",\n\"glaieul\",\n\"glaik\",\n\"glaiket\",\n\"glair\",\n\"glairy\",\n\"glaive\",\n\"glaived\",\n\"glaked\",\n\"glaky\",\n\"glam\",\n\"glamour\",\n\"glance\",\n\"glancer\",\n\"gland\",\n\"glandes\",\n\"glans\",\n\"glar\",\n\"glare\",\n\"glarily\",\n\"glaring\",\n\"glarry\",\n\"glary\",\n\"glashan\",\n\"glass\",\n\"glassen\",\n\"glasser\",\n\"glasses\",\n\"glassie\",\n\"glassy\",\n\"glaucin\",\n\"glaum\",\n\"glaur\",\n\"glaury\",\n\"glaver\",\n\"glaze\",\n\"glazed\",\n\"glazen\",\n\"glazer\",\n\"glazier\",\n\"glazily\",\n\"glazing\",\n\"glazy\",\n\"gleam\",\n\"gleamy\",\n\"glean\",\n\"gleaner\",\n\"gleary\",\n\"gleba\",\n\"glebal\",\n\"glebe\",\n\"glebous\",\n\"glede\",\n\"gledy\",\n\"glee\",\n\"gleed\",\n\"gleeful\",\n\"gleek\",\n\"gleeman\",\n\"gleet\",\n\"gleety\",\n\"gleg\",\n\"glegly\",\n\"glen\",\n\"glenoid\",\n\"glent\",\n\"gleyde\",\n\"glia\",\n\"gliadin\",\n\"glial\",\n\"glib\",\n\"glibly\",\n\"glidder\",\n\"glide\",\n\"glider\",\n\"gliding\",\n\"gliff\",\n\"glime\",\n\"glimmer\",\n\"glimpse\",\n\"glink\",\n\"glint\",\n\"glioma\",\n\"gliosa\",\n\"gliosis\",\n\"glirine\",\n\"glisk\",\n\"glisky\",\n\"glisten\",\n\"glister\",\n\"glitter\",\n\"gloam\",\n\"gloat\",\n\"gloater\",\n\"global\",\n\"globate\",\n\"globe\",\n\"globed\",\n\"globin\",\n\"globoid\",\n\"globose\",\n\"globous\",\n\"globule\",\n\"globy\",\n\"glochid\",\n\"glochis\",\n\"gloea\",\n\"gloeal\",\n\"glom\",\n\"glome\",\n\"glommox\",\n\"glomus\",\n\"glonoin\",\n\"gloom\",\n\"gloomth\",\n\"gloomy\",\n\"glop\",\n\"gloppen\",\n\"glor\",\n\"glore\",\n\"glorify\",\n\"glory\",\n\"gloss\",\n\"glossa\",\n\"glossal\",\n\"glossed\",\n\"glosser\",\n\"glossic\",\n\"glossy\",\n\"glost\",\n\"glottal\",\n\"glottic\",\n\"glottid\",\n\"glottis\",\n\"glout\",\n\"glove\",\n\"glover\",\n\"glovey\",\n\"gloving\",\n\"glow\",\n\"glower\",\n\"glowfly\",\n\"glowing\",\n\"gloy\",\n\"gloze\",\n\"glozing\",\n\"glub\",\n\"glucase\",\n\"glucid\",\n\"glucide\",\n\"glucina\",\n\"glucine\",\n\"gluck\",\n\"glucose\",\n\"glue\",\n\"glued\",\n\"gluepot\",\n\"gluer\",\n\"gluey\",\n\"glug\",\n\"gluish\",\n\"glum\",\n\"gluma\",\n\"glumal\",\n\"glume\",\n\"glumly\",\n\"glummy\",\n\"glumose\",\n\"glump\",\n\"glumpy\",\n\"glunch\",\n\"glusid\",\n\"gluside\",\n\"glut\",\n\"glutch\",\n\"gluteal\",\n\"gluten\",\n\"gluteus\",\n\"glutin\",\n\"glutoid\",\n\"glutose\",\n\"glutter\",\n\"glutton\",\n\"glycid\",\n\"glycide\",\n\"glycine\",\n\"glycol\",\n\"glycose\",\n\"glycyl\",\n\"glyoxal\",\n\"glyoxim\",\n\"glyoxyl\",\n\"glyph\",\n\"glyphic\",\n\"glyptic\",\n\"glyster\",\n\"gnabble\",\n\"gnar\",\n\"gnarl\",\n\"gnarled\",\n\"gnarly\",\n\"gnash\",\n\"gnat\",\n\"gnathal\",\n\"gnathic\",\n\"gnatter\",\n\"gnatty\",\n\"gnaw\",\n\"gnawer\",\n\"gnawing\",\n\"gnawn\",\n\"gneiss\",\n\"gneissy\",\n\"gnome\",\n\"gnomed\",\n\"gnomic\",\n\"gnomide\",\n\"gnomish\",\n\"gnomist\",\n\"gnomon\",\n\"gnosis\",\n\"gnostic\",\n\"gnu\",\n\"go\",\n\"goa\",\n\"goad\",\n\"goaf\",\n\"goal\",\n\"goalage\",\n\"goalee\",\n\"goalie\",\n\"goanna\",\n\"goat\",\n\"goatee\",\n\"goateed\",\n\"goatish\",\n\"goatly\",\n\"goaty\",\n\"goave\",\n\"gob\",\n\"goback\",\n\"goban\",\n\"gobang\",\n\"gobbe\",\n\"gobber\",\n\"gobbet\",\n\"gobbin\",\n\"gobbing\",\n\"gobble\",\n\"gobbler\",\n\"gobby\",\n\"gobelin\",\n\"gobi\",\n\"gobiid\",\n\"gobioid\",\n\"goblet\",\n\"goblin\",\n\"gobline\",\n\"gobo\",\n\"gobony\",\n\"goburra\",\n\"goby\",\n\"gocart\",\n\"god\",\n\"goddard\",\n\"godded\",\n\"goddess\",\n\"goddize\",\n\"gode\",\n\"godet\",\n\"godhead\",\n\"godhood\",\n\"godkin\",\n\"godless\",\n\"godlet\",\n\"godlike\",\n\"godlily\",\n\"godling\",\n\"godly\",\n\"godown\",\n\"godpapa\",\n\"godsend\",\n\"godship\",\n\"godson\",\n\"godwit\",\n\"goeduck\",\n\"goel\",\n\"goelism\",\n\"goer\",\n\"goes\",\n\"goetia\",\n\"goetic\",\n\"goety\",\n\"goff\",\n\"goffer\",\n\"goffle\",\n\"gog\",\n\"gogga\",\n\"goggan\",\n\"goggle\",\n\"goggled\",\n\"goggler\",\n\"goggly\",\n\"goglet\",\n\"gogo\",\n\"goi\",\n\"going\",\n\"goitcho\",\n\"goiter\",\n\"goitral\",\n\"gol\",\n\"gola\",\n\"golach\",\n\"goladar\",\n\"gold\",\n\"goldbug\",\n\"goldcup\",\n\"golden\",\n\"golder\",\n\"goldie\",\n\"goldin\",\n\"goldish\",\n\"goldtit\",\n\"goldy\",\n\"golee\",\n\"golem\",\n\"golf\",\n\"golfdom\",\n\"golfer\",\n\"goli\",\n\"goliard\",\n\"goliath\",\n\"golland\",\n\"gollar\",\n\"golly\",\n\"goloe\",\n\"golpe\",\n\"gomari\",\n\"gomart\",\n\"gomavel\",\n\"gombay\",\n\"gombeen\",\n\"gomer\",\n\"gomeral\",\n\"gomlah\",\n\"gomuti\",\n\"gon\",\n\"gonad\",\n\"gonadal\",\n\"gonadic\",\n\"gonagra\",\n\"gonakie\",\n\"gonal\",\n\"gonapod\",\n\"gondang\",\n\"gondite\",\n\"gondola\",\n\"gone\",\n\"goner\",\n\"gong\",\n\"gongman\",\n\"gonia\",\n\"goniac\",\n\"gonial\",\n\"goniale\",\n\"gonid\",\n\"gonidia\",\n\"gonidic\",\n\"gonimic\",\n\"gonion\",\n\"gonitis\",\n\"gonium\",\n\"gonne\",\n\"gony\",\n\"gonys\",\n\"goo\",\n\"goober\",\n\"good\",\n\"gooding\",\n\"goodish\",\n\"goodly\",\n\"goodman\",\n\"goods\",\n\"goody\",\n\"goof\",\n\"goofer\",\n\"goofily\",\n\"goofy\",\n\"googly\",\n\"googol\",\n\"googul\",\n\"gook\",\n\"gool\",\n\"goolah\",\n\"gools\",\n\"gooma\",\n\"goon\",\n\"goondie\",\n\"goonie\",\n\"goose\",\n\"goosery\",\n\"goosish\",\n\"goosy\",\n\"gopher\",\n\"gopura\",\n\"gor\",\n\"gora\",\n\"goracco\",\n\"goral\",\n\"goran\",\n\"gorb\",\n\"gorbal\",\n\"gorbet\",\n\"gorble\",\n\"gorce\",\n\"gorcock\",\n\"gorcrow\",\n\"gore\",\n\"gorer\",\n\"gorevan\",\n\"gorfly\",\n\"gorge\",\n\"gorged\",\n\"gorger\",\n\"gorget\",\n\"gorglin\",\n\"gorhen\",\n\"goric\",\n\"gorilla\",\n\"gorily\",\n\"goring\",\n\"gorlin\",\n\"gorlois\",\n\"gormaw\",\n\"gormed\",\n\"gorra\",\n\"gorraf\",\n\"gorry\",\n\"gorse\",\n\"gorsedd\",\n\"gorsy\",\n\"gory\",\n\"gos\",\n\"gosain\",\n\"goschen\",\n\"gosh\",\n\"goshawk\",\n\"goslet\",\n\"gosling\",\n\"gosmore\",\n\"gospel\",\n\"gosport\",\n\"gossan\",\n\"gossard\",\n\"gossip\",\n\"gossipy\",\n\"gossoon\",\n\"gossy\",\n\"got\",\n\"gotch\",\n\"gote\",\n\"gothite\",\n\"gotra\",\n\"gotraja\",\n\"gotten\",\n\"gouaree\",\n\"gouge\",\n\"gouger\",\n\"goujon\",\n\"goulash\",\n\"goumi\",\n\"goup\",\n\"gourami\",\n\"gourd\",\n\"gourde\",\n\"gourdy\",\n\"gourmet\",\n\"gousty\",\n\"gout\",\n\"goutify\",\n\"goutily\",\n\"goutish\",\n\"goutte\",\n\"gouty\",\n\"gove\",\n\"govern\",\n\"gowan\",\n\"gowdnie\",\n\"gowf\",\n\"gowfer\",\n\"gowk\",\n\"gowked\",\n\"gowkit\",\n\"gowl\",\n\"gown\",\n\"gownlet\",\n\"gowpen\",\n\"goy\",\n\"goyim\",\n\"goyin\",\n\"goyle\",\n\"gozell\",\n\"gozzard\",\n\"gra\",\n\"grab\",\n\"grabber\",\n\"grabble\",\n\"graben\",\n\"grace\",\n\"gracer\",\n\"gracile\",\n\"grackle\",\n\"grad\",\n\"gradal\",\n\"gradate\",\n\"graddan\",\n\"grade\",\n\"graded\",\n\"gradely\",\n\"grader\",\n\"gradin\",\n\"gradine\",\n\"grading\",\n\"gradual\",\n\"gradus\",\n\"graff\",\n\"graffer\",\n\"graft\",\n\"grafted\",\n\"grafter\",\n\"graham\",\n\"grail\",\n\"grailer\",\n\"grain\",\n\"grained\",\n\"grainer\",\n\"grainy\",\n\"graip\",\n\"graisse\",\n\"graith\",\n\"grallic\",\n\"gram\",\n\"grama\",\n\"grame\",\n\"grammar\",\n\"gramme\",\n\"gramp\",\n\"grampa\",\n\"grampus\",\n\"granada\",\n\"granage\",\n\"granary\",\n\"granate\",\n\"granch\",\n\"grand\",\n\"grandam\",\n\"grandee\",\n\"grandly\",\n\"grandma\",\n\"grandpa\",\n\"grane\",\n\"grange\",\n\"granger\",\n\"granite\",\n\"grank\",\n\"grannom\",\n\"granny\",\n\"grano\",\n\"granose\",\n\"grant\",\n\"grantee\",\n\"granter\",\n\"grantor\",\n\"granula\",\n\"granule\",\n\"granza\",\n\"grape\",\n\"graped\",\n\"grapery\",\n\"graph\",\n\"graphic\",\n\"graphy\",\n\"graping\",\n\"grapnel\",\n\"grappa\",\n\"grapple\",\n\"grapy\",\n\"grasp\",\n\"grasper\",\n\"grass\",\n\"grassed\",\n\"grasser\",\n\"grasset\",\n\"grassy\",\n\"grat\",\n\"grate\",\n\"grater\",\n\"grather\",\n\"gratify\",\n\"grating\",\n\"gratis\",\n\"gratten\",\n\"graupel\",\n\"grave\",\n\"graved\",\n\"gravel\",\n\"gravely\",\n\"graven\",\n\"graver\",\n\"gravic\",\n\"gravid\",\n\"graving\",\n\"gravity\",\n\"gravure\",\n\"gravy\",\n\"grawls\",\n\"gray\",\n\"grayfly\",\n\"grayish\",\n\"graylag\",\n\"grayly\",\n\"graze\",\n\"grazer\",\n\"grazier\",\n\"grazing\",\n\"grease\",\n\"greaser\",\n\"greasy\",\n\"great\",\n\"greaten\",\n\"greater\",\n\"greatly\",\n\"greave\",\n\"greaved\",\n\"greaves\",\n\"grebe\",\n\"grece\",\n\"gree\",\n\"greed\",\n\"greedy\",\n\"green\",\n\"greener\",\n\"greeney\",\n\"greenly\",\n\"greenth\",\n\"greenuk\",\n\"greeny\",\n\"greet\",\n\"greeter\",\n\"gregal\",\n\"gregale\",\n\"grege\",\n\"greggle\",\n\"grego\",\n\"greige\",\n\"grein\",\n\"greisen\",\n\"gremial\",\n\"gremlin\",\n\"grenade\",\n\"greund\",\n\"grew\",\n\"grey\",\n\"greyly\",\n\"gribble\",\n\"grice\",\n\"grid\",\n\"griddle\",\n\"gride\",\n\"griece\",\n\"grieced\",\n\"grief\",\n\"grieve\",\n\"grieved\",\n\"griever\",\n\"griff\",\n\"griffe\",\n\"griffin\",\n\"griffon\",\n\"grift\",\n\"grifter\",\n\"grig\",\n\"grignet\",\n\"grigri\",\n\"grike\",\n\"grill\",\n\"grille\",\n\"grilled\",\n\"griller\",\n\"grilse\",\n\"grim\",\n\"grimace\",\n\"grime\",\n\"grimful\",\n\"grimily\",\n\"grimly\",\n\"grimme\",\n\"grimp\",\n\"grimy\",\n\"grin\",\n\"grinch\",\n\"grind\",\n\"grinder\",\n\"grindle\",\n\"gringo\",\n\"grinner\",\n\"grinny\",\n\"grip\",\n\"gripe\",\n\"griper\",\n\"griping\",\n\"gripman\",\n\"grippal\",\n\"grippe\",\n\"gripper\",\n\"gripple\",\n\"grippy\",\n\"gripy\",\n\"gris\",\n\"grisard\",\n\"griskin\",\n\"grisly\",\n\"grison\",\n\"grist\",\n\"grister\",\n\"gristle\",\n\"gristly\",\n\"gristy\",\n\"grit\",\n\"grith\",\n\"grits\",\n\"gritten\",\n\"gritter\",\n\"grittle\",\n\"gritty\",\n\"grivet\",\n\"grivna\",\n\"grizzle\",\n\"grizzly\",\n\"groan\",\n\"groaner\",\n\"groat\",\n\"groats\",\n\"grobian\",\n\"grocer\",\n\"grocery\",\n\"groff\",\n\"grog\",\n\"groggy\",\n\"grogram\",\n\"groin\",\n\"groined\",\n\"grommet\",\n\"groom\",\n\"groomer\",\n\"groomy\",\n\"groop\",\n\"groose\",\n\"groot\",\n\"grooty\",\n\"groove\",\n\"groover\",\n\"groovy\",\n\"grope\",\n\"groper\",\n\"groping\",\n\"gropple\",\n\"gros\",\n\"groser\",\n\"groset\",\n\"gross\",\n\"grossen\",\n\"grosser\",\n\"grossly\",\n\"grosso\",\n\"grosz\",\n\"groszy\",\n\"grot\",\n\"grotto\",\n\"grouch\",\n\"grouchy\",\n\"grouf\",\n\"grough\",\n\"ground\",\n\"grounds\",\n\"groundy\",\n\"group\",\n\"grouped\",\n\"grouper\",\n\"grouse\",\n\"grouser\",\n\"grousy\",\n\"grout\",\n\"grouter\",\n\"grouts\",\n\"grouty\",\n\"grouze\",\n\"grove\",\n\"groved\",\n\"grovel\",\n\"grovy\",\n\"grow\",\n\"growan\",\n\"growed\",\n\"grower\",\n\"growing\",\n\"growl\",\n\"growler\",\n\"growly\",\n\"grown\",\n\"grownup\",\n\"growse\",\n\"growth\",\n\"growthy\",\n\"grozart\",\n\"grozet\",\n\"grr\",\n\"grub\",\n\"grubbed\",\n\"grubber\",\n\"grubby\",\n\"grubs\",\n\"grudge\",\n\"grudger\",\n\"grue\",\n\"gruel\",\n\"grueler\",\n\"gruelly\",\n\"gruff\",\n\"gruffly\",\n\"gruffs\",\n\"gruffy\",\n\"grufted\",\n\"grugru\",\n\"gruine\",\n\"grum\",\n\"grumble\",\n\"grumbly\",\n\"grume\",\n\"grumly\",\n\"grummel\",\n\"grummet\",\n\"grumose\",\n\"grumous\",\n\"grump\",\n\"grumph\",\n\"grumphy\",\n\"grumpy\",\n\"grun\",\n\"grundy\",\n\"grunion\",\n\"grunt\",\n\"grunter\",\n\"gruntle\",\n\"grush\",\n\"grushie\",\n\"gruss\",\n\"grutch\",\n\"grutten\",\n\"gryde\",\n\"grylli\",\n\"gryllid\",\n\"gryllos\",\n\"gryllus\",\n\"grysbok\",\n\"guaba\",\n\"guacimo\",\n\"guacin\",\n\"guaco\",\n\"guaiac\",\n\"guaiol\",\n\"guaka\",\n\"guama\",\n\"guan\",\n\"guana\",\n\"guanaco\",\n\"guanase\",\n\"guanay\",\n\"guango\",\n\"guanine\",\n\"guanize\",\n\"guano\",\n\"guanyl\",\n\"guao\",\n\"guapena\",\n\"guar\",\n\"guara\",\n\"guarabu\",\n\"guarana\",\n\"guarani\",\n\"guard\",\n\"guarded\",\n\"guarder\",\n\"guardo\",\n\"guariba\",\n\"guarri\",\n\"guasa\",\n\"guava\",\n\"guavina\",\n\"guayaba\",\n\"guayabi\",\n\"guayabo\",\n\"guayule\",\n\"guaza\",\n\"gubbo\",\n\"gucki\",\n\"gud\",\n\"gudame\",\n\"guddle\",\n\"gude\",\n\"gudge\",\n\"gudgeon\",\n\"gudget\",\n\"gudok\",\n\"gue\",\n\"guebucu\",\n\"guemal\",\n\"guenepe\",\n\"guenon\",\n\"guepard\",\n\"guerdon\",\n\"guereza\",\n\"guess\",\n\"guesser\",\n\"guest\",\n\"guesten\",\n\"guester\",\n\"gufa\",\n\"guff\",\n\"guffaw\",\n\"guffer\",\n\"guffin\",\n\"guffy\",\n\"gugal\",\n\"guggle\",\n\"gugglet\",\n\"guglet\",\n\"guglia\",\n\"guglio\",\n\"gugu\",\n\"guhr\",\n\"guib\",\n\"guiba\",\n\"guidage\",\n\"guide\",\n\"guider\",\n\"guidman\",\n\"guidon\",\n\"guige\",\n\"guignol\",\n\"guijo\",\n\"guild\",\n\"guilder\",\n\"guildic\",\n\"guildry\",\n\"guile\",\n\"guilery\",\n\"guilt\",\n\"guilty\",\n\"guily\",\n\"guimpe\",\n\"guinea\",\n\"guipure\",\n\"guisard\",\n\"guise\",\n\"guiser\",\n\"guising\",\n\"guitar\",\n\"gul\",\n\"gula\",\n\"gulae\",\n\"gulaman\",\n\"gular\",\n\"gularis\",\n\"gulch\",\n\"gulden\",\n\"gule\",\n\"gules\",\n\"gulf\",\n\"gulfy\",\n\"gulgul\",\n\"gulix\",\n\"gull\",\n\"gullery\",\n\"gullet\",\n\"gullion\",\n\"gullish\",\n\"gully\",\n\"gulonic\",\n\"gulose\",\n\"gulp\",\n\"gulper\",\n\"gulpin\",\n\"gulping\",\n\"gulpy\",\n\"gulsach\",\n\"gum\",\n\"gumbo\",\n\"gumboil\",\n\"gumby\",\n\"gumdrop\",\n\"gumihan\",\n\"gumless\",\n\"gumlike\",\n\"gumly\",\n\"gumma\",\n\"gummage\",\n\"gummata\",\n\"gummed\",\n\"gummer\",\n\"gumming\",\n\"gummite\",\n\"gummose\",\n\"gummous\",\n\"gummy\",\n\"gump\",\n\"gumpus\",\n\"gumshoe\",\n\"gumweed\",\n\"gumwood\",\n\"gun\",\n\"guna\",\n\"gunate\",\n\"gunboat\",\n\"gundi\",\n\"gundy\",\n\"gunebo\",\n\"gunfire\",\n\"gunge\",\n\"gunite\",\n\"gunj\",\n\"gunk\",\n\"gunl\",\n\"gunless\",\n\"gunlock\",\n\"gunman\",\n\"gunnage\",\n\"gunne\",\n\"gunnel\",\n\"gunner\",\n\"gunnery\",\n\"gunnies\",\n\"gunning\",\n\"gunnung\",\n\"gunny\",\n\"gunong\",\n\"gunplay\",\n\"gunrack\",\n\"gunsel\",\n\"gunshop\",\n\"gunshot\",\n\"gunsman\",\n\"gunster\",\n\"gunter\",\n\"gunwale\",\n\"gunyah\",\n\"gunyang\",\n\"gunyeh\",\n\"gup\",\n\"guppy\",\n\"gur\",\n\"gurdle\",\n\"gurge\",\n\"gurgeon\",\n\"gurges\",\n\"gurgle\",\n\"gurglet\",\n\"gurgly\",\n\"gurjun\",\n\"gurk\",\n\"gurl\",\n\"gurly\",\n\"gurnard\",\n\"gurnet\",\n\"gurniad\",\n\"gurr\",\n\"gurrah\",\n\"gurry\",\n\"gurt\",\n\"guru\",\n\"gush\",\n\"gusher\",\n\"gushet\",\n\"gushily\",\n\"gushing\",\n\"gushy\",\n\"gusla\",\n\"gusle\",\n\"guss\",\n\"gusset\",\n\"gussie\",\n\"gust\",\n\"gustful\",\n\"gustily\",\n\"gusto\",\n\"gusty\",\n\"gut\",\n\"gutless\",\n\"gutlike\",\n\"gutling\",\n\"gutt\",\n\"gutta\",\n\"guttate\",\n\"gutte\",\n\"gutter\",\n\"guttery\",\n\"gutti\",\n\"guttide\",\n\"guttie\",\n\"guttle\",\n\"guttler\",\n\"guttula\",\n\"guttule\",\n\"guttus\",\n\"gutty\",\n\"gutweed\",\n\"gutwise\",\n\"gutwort\",\n\"guy\",\n\"guydom\",\n\"guyer\",\n\"guz\",\n\"guze\",\n\"guzzle\",\n\"guzzler\",\n\"gwag\",\n\"gweduc\",\n\"gweed\",\n\"gweeon\",\n\"gwely\",\n\"gwine\",\n\"gwyniad\",\n\"gyle\",\n\"gym\",\n\"gymel\",\n\"gymnast\",\n\"gymnic\",\n\"gymnics\",\n\"gymnite\",\n\"gymnure\",\n\"gympie\",\n\"gyn\",\n\"gyne\",\n\"gynecic\",\n\"gynic\",\n\"gynics\",\n\"gyp\",\n\"gype\",\n\"gypper\",\n\"gyps\",\n\"gypsine\",\n\"gypsite\",\n\"gypsous\",\n\"gypster\",\n\"gypsum\",\n\"gypsy\",\n\"gypsyfy\",\n\"gypsyry\",\n\"gyral\",\n\"gyrally\",\n\"gyrant\",\n\"gyrate\",\n\"gyrator\",\n\"gyre\",\n\"gyrene\",\n\"gyri\",\n\"gyric\",\n\"gyrinid\",\n\"gyro\",\n\"gyrocar\",\n\"gyroma\",\n\"gyron\",\n\"gyronny\",\n\"gyrose\",\n\"gyrous\",\n\"gyrus\",\n\"gyte\",\n\"gytling\",\n\"gyve\",\n\"h\",\n\"ha\",\n\"haab\",\n\"haaf\",\n\"habble\",\n\"habeas\",\n\"habena\",\n\"habenal\",\n\"habenar\",\n\"habile\",\n\"habille\",\n\"habit\",\n\"habitan\",\n\"habitat\",\n\"habited\",\n\"habitue\",\n\"habitus\",\n\"habnab\",\n\"haboob\",\n\"habu\",\n\"habutai\",\n\"hache\",\n\"hachure\",\n\"hack\",\n\"hackbut\",\n\"hacked\",\n\"hackee\",\n\"hacker\",\n\"hackery\",\n\"hackin\",\n\"hacking\",\n\"hackle\",\n\"hackler\",\n\"hacklog\",\n\"hackly\",\n\"hackman\",\n\"hackney\",\n\"hacksaw\",\n\"hacky\",\n\"had\",\n\"hadbot\",\n\"hadden\",\n\"haddie\",\n\"haddo\",\n\"haddock\",\n\"hade\",\n\"hading\",\n\"hadj\",\n\"hadji\",\n\"hadland\",\n\"hadrome\",\n\"haec\",\n\"haem\",\n\"haemony\",\n\"haet\",\n\"haff\",\n\"haffet\",\n\"haffle\",\n\"hafiz\",\n\"hafnium\",\n\"hafnyl\",\n\"haft\",\n\"hafter\",\n\"hag\",\n\"hagboat\",\n\"hagborn\",\n\"hagbush\",\n\"hagdon\",\n\"hageen\",\n\"hagfish\",\n\"haggada\",\n\"haggard\",\n\"hagged\",\n\"hagger\",\n\"haggis\",\n\"haggish\",\n\"haggle\",\n\"haggler\",\n\"haggly\",\n\"haggy\",\n\"hagi\",\n\"hagia\",\n\"haglet\",\n\"haglike\",\n\"haglin\",\n\"hagride\",\n\"hagrope\",\n\"hagseed\",\n\"hagship\",\n\"hagweed\",\n\"hagworm\",\n\"hah\",\n\"haik\",\n\"haikai\",\n\"haikal\",\n\"haikwan\",\n\"hail\",\n\"hailer\",\n\"hailse\",\n\"haily\",\n\"hain\",\n\"haine\",\n\"hair\",\n\"haircut\",\n\"hairdo\",\n\"haire\",\n\"haired\",\n\"hairen\",\n\"hairif\",\n\"hairlet\",\n\"hairpin\",\n\"hairup\",\n\"hairy\",\n\"haje\",\n\"hajib\",\n\"hajilij\",\n\"hak\",\n\"hakam\",\n\"hakdar\",\n\"hake\",\n\"hakeem\",\n\"hakim\",\n\"hako\",\n\"haku\",\n\"hala\",\n\"halakah\",\n\"halakic\",\n\"halal\",\n\"halberd\",\n\"halbert\",\n\"halch\",\n\"halcyon\",\n\"hale\",\n\"halebi\",\n\"haler\",\n\"halerz\",\n\"half\",\n\"halfer\",\n\"halfman\",\n\"halfway\",\n\"halibiu\",\n\"halibut\",\n\"halide\",\n\"halidom\",\n\"halite\",\n\"halitus\",\n\"hall\",\n\"hallage\",\n\"hallah\",\n\"hallan\",\n\"hallel\",\n\"hallex\",\n\"halling\",\n\"hallman\",\n\"halloo\",\n\"hallow\",\n\"hallux\",\n\"hallway\",\n\"halma\",\n\"halo\",\n\"halogen\",\n\"haloid\",\n\"hals\",\n\"halse\",\n\"halsen\",\n\"halt\",\n\"halter\",\n\"halting\",\n\"halurgy\",\n\"halutz\",\n\"halvans\",\n\"halve\",\n\"halved\",\n\"halver\",\n\"halves\",\n\"halyard\",\n\"ham\",\n\"hamal\",\n\"hamald\",\n\"hamate\",\n\"hamated\",\n\"hamatum\",\n\"hamble\",\n\"hame\",\n\"hameil\",\n\"hamel\",\n\"hamfat\",\n\"hami\",\n\"hamlah\",\n\"hamlet\",\n\"hammada\",\n\"hammam\",\n\"hammer\",\n\"hammock\",\n\"hammy\",\n\"hamose\",\n\"hamous\",\n\"hamper\",\n\"hamsa\",\n\"hamster\",\n\"hamular\",\n\"hamule\",\n\"hamulus\",\n\"hamus\",\n\"hamza\",\n\"han\",\n\"hanaper\",\n\"hanbury\",\n\"hance\",\n\"hanced\",\n\"hanch\",\n\"hand\",\n\"handbag\",\n\"handbow\",\n\"handcar\",\n\"handed\",\n\"hander\",\n\"handful\",\n\"handgun\",\n\"handily\",\n\"handle\",\n\"handled\",\n\"handler\",\n\"handout\",\n\"handsaw\",\n\"handsel\",\n\"handset\",\n\"handy\",\n\"hangar\",\n\"hangby\",\n\"hangdog\",\n\"hange\",\n\"hangee\",\n\"hanger\",\n\"hangie\",\n\"hanging\",\n\"hangle\",\n\"hangman\",\n\"hangout\",\n\"hangul\",\n\"hanif\",\n\"hank\",\n\"hanker\",\n\"hankie\",\n\"hankle\",\n\"hanky\",\n\"hanna\",\n\"hansa\",\n\"hanse\",\n\"hansel\",\n\"hansom\",\n\"hant\",\n\"hantle\",\n\"hao\",\n\"haole\",\n\"haoma\",\n\"haori\",\n\"hap\",\n\"hapless\",\n\"haplite\",\n\"haploid\",\n\"haploma\",\n\"haplont\",\n\"haply\",\n\"happen\",\n\"happier\",\n\"happify\",\n\"happily\",\n\"happing\",\n\"happy\",\n\"hapten\",\n\"haptene\",\n\"haptere\",\n\"haptic\",\n\"haptics\",\n\"hapu\",\n\"hapuku\",\n\"harass\",\n\"haratch\",\n\"harbi\",\n\"harbor\",\n\"hard\",\n\"harden\",\n\"harder\",\n\"hardily\",\n\"hardim\",\n\"hardish\",\n\"hardly\",\n\"hardock\",\n\"hardpan\",\n\"hardy\",\n\"hare\",\n\"harebur\",\n\"harelip\",\n\"harem\",\n\"harfang\",\n\"haricot\",\n\"harish\",\n\"hark\",\n\"harka\",\n\"harl\",\n\"harling\",\n\"harlock\",\n\"harlot\",\n\"harm\",\n\"harmal\",\n\"harmala\",\n\"harman\",\n\"harmel\",\n\"harmer\",\n\"harmful\",\n\"harmine\",\n\"harmony\",\n\"harmost\",\n\"harn\",\n\"harness\",\n\"harnpan\",\n\"harp\",\n\"harpago\",\n\"harper\",\n\"harpier\",\n\"harpist\",\n\"harpoon\",\n\"harpula\",\n\"harr\",\n\"harrier\",\n\"harrow\",\n\"harry\",\n\"harsh\",\n\"harshen\",\n\"harshly\",\n\"hart\",\n\"hartal\",\n\"hartin\",\n\"hartite\",\n\"harvest\",\n\"hasan\",\n\"hash\",\n\"hashab\",\n\"hasher\",\n\"hashish\",\n\"hashy\",\n\"hask\",\n\"hasky\",\n\"haslet\",\n\"haslock\",\n\"hasp\",\n\"hassar\",\n\"hassel\",\n\"hassle\",\n\"hassock\",\n\"hasta\",\n\"hastate\",\n\"hastati\",\n\"haste\",\n\"hasten\",\n\"haster\",\n\"hastily\",\n\"hastish\",\n\"hastler\",\n\"hasty\",\n\"hat\",\n\"hatable\",\n\"hatband\",\n\"hatbox\",\n\"hatbrim\",\n\"hatch\",\n\"hatchel\",\n\"hatcher\",\n\"hatchet\",\n\"hate\",\n\"hateful\",\n\"hater\",\n\"hatful\",\n\"hath\",\n\"hathi\",\n\"hatless\",\n\"hatlike\",\n\"hatpin\",\n\"hatrack\",\n\"hatrail\",\n\"hatred\",\n\"hatress\",\n\"hatt\",\n\"hatted\",\n\"hatter\",\n\"hattery\",\n\"hatting\",\n\"hattock\",\n\"hatty\",\n\"hau\",\n\"hauberk\",\n\"haugh\",\n\"haught\",\n\"haughty\",\n\"haul\",\n\"haulage\",\n\"hauld\",\n\"hauler\",\n\"haulier\",\n\"haulm\",\n\"haulmy\",\n\"haunch\",\n\"haunchy\",\n\"haunt\",\n\"haunter\",\n\"haunty\",\n\"hause\",\n\"hausen\",\n\"hausse\",\n\"hautboy\",\n\"hauteur\",\n\"havage\",\n\"have\",\n\"haveage\",\n\"havel\",\n\"haven\",\n\"havener\",\n\"havenet\",\n\"havent\",\n\"haver\",\n\"haverel\",\n\"haverer\",\n\"havers\",\n\"havier\",\n\"havoc\",\n\"haw\",\n\"hawbuck\",\n\"hawer\",\n\"hawk\",\n\"hawkbit\",\n\"hawked\",\n\"hawker\",\n\"hawkery\",\n\"hawkie\",\n\"hawking\",\n\"hawkish\",\n\"hawknut\",\n\"hawky\",\n\"hawm\",\n\"hawok\",\n\"hawse\",\n\"hawser\",\n\"hay\",\n\"haya\",\n\"hayband\",\n\"haybird\",\n\"haybote\",\n\"haycap\",\n\"haycart\",\n\"haycock\",\n\"hayey\",\n\"hayfork\",\n\"haylift\",\n\"hayloft\",\n\"haymow\",\n\"hayrack\",\n\"hayrake\",\n\"hayrick\",\n\"hayseed\",\n\"haysel\",\n\"haysuck\",\n\"haytime\",\n\"hayward\",\n\"hayweed\",\n\"haywire\",\n\"hayz\",\n\"hazard\",\n\"haze\",\n\"hazel\",\n\"hazeled\",\n\"hazelly\",\n\"hazen\",\n\"hazer\",\n\"hazily\",\n\"hazing\",\n\"hazle\",\n\"hazy\",\n\"hazzan\",\n\"he\",\n\"head\",\n\"headcap\",\n\"headed\",\n\"header\",\n\"headful\",\n\"headily\",\n\"heading\",\n\"headman\",\n\"headset\",\n\"headway\",\n\"heady\",\n\"heaf\",\n\"heal\",\n\"heald\",\n\"healder\",\n\"healer\",\n\"healful\",\n\"healing\",\n\"health\",\n\"healthy\",\n\"heap\",\n\"heaper\",\n\"heaps\",\n\"heapy\",\n\"hear\",\n\"hearer\",\n\"hearing\",\n\"hearken\",\n\"hearsay\",\n\"hearse\",\n\"hearst\",\n\"heart\",\n\"hearted\",\n\"hearten\",\n\"hearth\",\n\"heartly\",\n\"hearts\",\n\"hearty\",\n\"heat\",\n\"heater\",\n\"heatful\",\n\"heath\",\n\"heathen\",\n\"heather\",\n\"heathy\",\n\"heating\",\n\"heaume\",\n\"heaumer\",\n\"heave\",\n\"heaven\",\n\"heavens\",\n\"heaver\",\n\"heavies\",\n\"heavily\",\n\"heaving\",\n\"heavity\",\n\"heavy\",\n\"hebamic\",\n\"hebenon\",\n\"hebete\",\n\"hebetic\",\n\"hech\",\n\"heck\",\n\"heckle\",\n\"heckler\",\n\"hectare\",\n\"hecte\",\n\"hectic\",\n\"hector\",\n\"heddle\",\n\"heddler\",\n\"hedebo\",\n\"heder\",\n\"hederic\",\n\"hederin\",\n\"hedge\",\n\"hedger\",\n\"hedging\",\n\"hedgy\",\n\"hedonic\",\n\"heed\",\n\"heeder\",\n\"heedful\",\n\"heedily\",\n\"heedy\",\n\"heehaw\",\n\"heel\",\n\"heelcap\",\n\"heeled\",\n\"heeler\",\n\"heeltap\",\n\"heer\",\n\"heeze\",\n\"heezie\",\n\"heezy\",\n\"heft\",\n\"hefter\",\n\"heftily\",\n\"hefty\",\n\"hegari\",\n\"hegemon\",\n\"hegira\",\n\"hegumen\",\n\"hei\",\n\"heiau\",\n\"heifer\",\n\"heigh\",\n\"height\",\n\"heii\",\n\"heimin\",\n\"heinous\",\n\"heir\",\n\"heirdom\",\n\"heiress\",\n\"heitiki\",\n\"hekteus\",\n\"helbeh\",\n\"helcoid\",\n\"helder\",\n\"hele\",\n\"helenin\",\n\"heliast\",\n\"helical\",\n\"heliced\",\n\"helices\",\n\"helicin\",\n\"helicon\",\n\"helide\",\n\"heling\",\n\"helio\",\n\"helioid\",\n\"helium\",\n\"helix\",\n\"hell\",\n\"hellbox\",\n\"hellcat\",\n\"helldog\",\n\"heller\",\n\"helleri\",\n\"hellhag\",\n\"hellier\",\n\"hellion\",\n\"hellish\",\n\"hello\",\n\"helluo\",\n\"helly\",\n\"helm\",\n\"helmage\",\n\"helmed\",\n\"helmet\",\n\"helodes\",\n\"heloe\",\n\"heloma\",\n\"helonin\",\n\"helosis\",\n\"helotry\",\n\"help\",\n\"helper\",\n\"helpful\",\n\"helping\",\n\"helply\",\n\"helve\",\n\"helvell\",\n\"helver\",\n\"helvite\",\n\"hem\",\n\"hemad\",\n\"hemal\",\n\"hemapod\",\n\"hemase\",\n\"hematal\",\n\"hematic\",\n\"hematid\",\n\"hematin\",\n\"heme\",\n\"hemen\",\n\"hemera\",\n\"hemiamb\",\n\"hemic\",\n\"hemin\",\n\"hemina\",\n\"hemine\",\n\"heminee\",\n\"hemiope\",\n\"hemipic\",\n\"heml\",\n\"hemlock\",\n\"hemmel\",\n\"hemmer\",\n\"hemocry\",\n\"hemoid\",\n\"hemol\",\n\"hemopod\",\n\"hemp\",\n\"hempen\",\n\"hempy\",\n\"hen\",\n\"henad\",\n\"henbane\",\n\"henbill\",\n\"henbit\",\n\"hence\",\n\"hencoop\",\n\"hencote\",\n\"hend\",\n\"hendly\",\n\"henfish\",\n\"henism\",\n\"henlike\",\n\"henna\",\n\"hennery\",\n\"hennin\",\n\"hennish\",\n\"henny\",\n\"henotic\",\n\"henpeck\",\n\"henpen\",\n\"henry\",\n\"hent\",\n\"henter\",\n\"henware\",\n\"henwife\",\n\"henwise\",\n\"henyard\",\n\"hep\",\n\"hepar\",\n\"heparin\",\n\"hepatic\",\n\"hepcat\",\n\"heppen\",\n\"hepper\",\n\"heptace\",\n\"heptad\",\n\"heptal\",\n\"heptane\",\n\"heptene\",\n\"heptine\",\n\"heptite\",\n\"heptoic\",\n\"heptose\",\n\"heptyl\",\n\"heptyne\",\n\"her\",\n\"herald\",\n\"herb\",\n\"herbage\",\n\"herbal\",\n\"herbane\",\n\"herbary\",\n\"herbish\",\n\"herbist\",\n\"herblet\",\n\"herbman\",\n\"herbose\",\n\"herbous\",\n\"herby\",\n\"herd\",\n\"herdboy\",\n\"herder\",\n\"herdic\",\n\"herding\",\n\"here\",\n\"hereat\",\n\"hereby\",\n\"herein\",\n\"herem\",\n\"hereof\",\n\"hereon\",\n\"heresy\",\n\"heretic\",\n\"hereto\",\n\"herile\",\n\"heriot\",\n\"heritor\",\n\"herl\",\n\"herling\",\n\"herma\",\n\"hermaic\",\n\"hermit\",\n\"hern\",\n\"hernani\",\n\"hernant\",\n\"herne\",\n\"hernia\",\n\"hernial\",\n\"hero\",\n\"heroess\",\n\"heroic\",\n\"heroid\",\n\"heroify\",\n\"heroin\",\n\"heroine\",\n\"heroism\",\n\"heroize\",\n\"heron\",\n\"heroner\",\n\"heronry\",\n\"herpes\",\n\"herring\",\n\"hers\",\n\"herse\",\n\"hersed\",\n\"herself\",\n\"hership\",\n\"hersir\",\n\"hertz\",\n\"hessite\",\n\"hest\",\n\"hestern\",\n\"het\",\n\"hetaera\",\n\"hetaery\",\n\"heteric\",\n\"hetero\",\n\"hething\",\n\"hetman\",\n\"hetter\",\n\"heuau\",\n\"heugh\",\n\"heumite\",\n\"hevi\",\n\"hew\",\n\"hewable\",\n\"hewel\",\n\"hewer\",\n\"hewhall\",\n\"hewn\",\n\"hewt\",\n\"hex\",\n\"hexa\",\n\"hexace\",\n\"hexacid\",\n\"hexact\",\n\"hexad\",\n\"hexadic\",\n\"hexagon\",\n\"hexagyn\",\n\"hexane\",\n\"hexaped\",\n\"hexapla\",\n\"hexapod\",\n\"hexarch\",\n\"hexene\",\n\"hexer\",\n\"hexerei\",\n\"hexeris\",\n\"hexine\",\n\"hexis\",\n\"hexitol\",\n\"hexode\",\n\"hexogen\",\n\"hexoic\",\n\"hexone\",\n\"hexonic\",\n\"hexosan\",\n\"hexose\",\n\"hexyl\",\n\"hexylic\",\n\"hexyne\",\n\"hey\",\n\"heyday\",\n\"hi\",\n\"hia\",\n\"hiant\",\n\"hiatal\",\n\"hiate\",\n\"hiation\",\n\"hiatus\",\n\"hibbin\",\n\"hic\",\n\"hicatee\",\n\"hiccup\",\n\"hick\",\n\"hickey\",\n\"hickory\",\n\"hidable\",\n\"hidage\",\n\"hidalgo\",\n\"hidated\",\n\"hidden\",\n\"hide\",\n\"hided\",\n\"hideous\",\n\"hider\",\n\"hidling\",\n\"hie\",\n\"hieder\",\n\"hield\",\n\"hiemal\",\n\"hieron\",\n\"hieros\",\n\"higdon\",\n\"higgle\",\n\"higgler\",\n\"high\",\n\"highboy\",\n\"higher\",\n\"highest\",\n\"highish\",\n\"highly\",\n\"highman\",\n\"hight\",\n\"hightop\",\n\"highway\",\n\"higuero\",\n\"hijack\",\n\"hike\",\n\"hiker\",\n\"hilch\",\n\"hilding\",\n\"hill\",\n\"hiller\",\n\"hillet\",\n\"hillman\",\n\"hillock\",\n\"hilltop\",\n\"hilly\",\n\"hilsa\",\n\"hilt\",\n\"hilum\",\n\"hilus\",\n\"him\",\n\"himp\",\n\"himself\",\n\"himward\",\n\"hin\",\n\"hinau\",\n\"hinch\",\n\"hind\",\n\"hinder\",\n\"hing\",\n\"hinge\",\n\"hinger\",\n\"hingle\",\n\"hinney\",\n\"hinny\",\n\"hinoid\",\n\"hinoki\",\n\"hint\",\n\"hinter\",\n\"hiodont\",\n\"hip\",\n\"hipbone\",\n\"hipe\",\n\"hiper\",\n\"hiphalt\",\n\"hipless\",\n\"hipmold\",\n\"hipped\",\n\"hippen\",\n\"hippian\",\n\"hippic\",\n\"hipping\",\n\"hippish\",\n\"hipple\",\n\"hippo\",\n\"hippoid\",\n\"hippus\",\n\"hippy\",\n\"hipshot\",\n\"hipwort\",\n\"hirable\",\n\"hircine\",\n\"hire\",\n\"hired\",\n\"hireman\",\n\"hirer\",\n\"hirmos\",\n\"hiro\",\n\"hirple\",\n\"hirse\",\n\"hirsel\",\n\"hirsle\",\n\"hirsute\",\n\"his\",\n\"hish\",\n\"hisn\",\n\"hispid\",\n\"hiss\",\n\"hisser\",\n\"hissing\",\n\"hist\",\n\"histie\",\n\"histoid\",\n\"histon\",\n\"histone\",\n\"history\",\n\"histrio\",\n\"hit\",\n\"hitch\",\n\"hitcher\",\n\"hitchy\",\n\"hithe\",\n\"hither\",\n\"hitless\",\n\"hitter\",\n\"hive\",\n\"hiver\",\n\"hives\",\n\"hizz\",\n\"ho\",\n\"hoar\",\n\"hoard\",\n\"hoarder\",\n\"hoarily\",\n\"hoarish\",\n\"hoarse\",\n\"hoarsen\",\n\"hoary\",\n\"hoast\",\n\"hoatzin\",\n\"hoax\",\n\"hoaxee\",\n\"hoaxer\",\n\"hob\",\n\"hobber\",\n\"hobbet\",\n\"hobbil\",\n\"hobble\",\n\"hobbler\",\n\"hobbly\",\n\"hobby\",\n\"hoblike\",\n\"hobnail\",\n\"hobnob\",\n\"hobo\",\n\"hoboism\",\n\"hocco\",\n\"hock\",\n\"hocker\",\n\"hocket\",\n\"hockey\",\n\"hocky\",\n\"hocus\",\n\"hod\",\n\"hodden\",\n\"hodder\",\n\"hoddle\",\n\"hoddy\",\n\"hodful\",\n\"hodman\",\n\"hoe\",\n\"hoecake\",\n\"hoedown\",\n\"hoeful\",\n\"hoer\",\n\"hog\",\n\"hoga\",\n\"hogan\",\n\"hogback\",\n\"hogbush\",\n\"hogfish\",\n\"hogged\",\n\"hogger\",\n\"hoggery\",\n\"hogget\",\n\"hoggie\",\n\"hoggin\",\n\"hoggish\",\n\"hoggism\",\n\"hoggy\",\n\"hogherd\",\n\"hoghide\",\n\"hoghood\",\n\"hoglike\",\n\"hogling\",\n\"hogmace\",\n\"hognose\",\n\"hognut\",\n\"hogpen\",\n\"hogship\",\n\"hogskin\",\n\"hogsty\",\n\"hogward\",\n\"hogwash\",\n\"hogweed\",\n\"hogwort\",\n\"hogyard\",\n\"hoi\",\n\"hoick\",\n\"hoin\",\n\"hoise\",\n\"hoist\",\n\"hoister\",\n\"hoit\",\n\"hoju\",\n\"hokey\",\n\"hokum\",\n\"holard\",\n\"holcad\",\n\"hold\",\n\"holdall\",\n\"holden\",\n\"holder\",\n\"holding\",\n\"holdout\",\n\"holdup\",\n\"hole\",\n\"holeman\",\n\"holer\",\n\"holey\",\n\"holia\",\n\"holiday\",\n\"holily\",\n\"holing\",\n\"holism\",\n\"holl\",\n\"holla\",\n\"holler\",\n\"hollin\",\n\"hollo\",\n\"hollock\",\n\"hollong\",\n\"hollow\",\n\"holly\",\n\"holm\",\n\"holmia\",\n\"holmic\",\n\"holmium\",\n\"holmos\",\n\"holour\",\n\"holster\",\n\"holt\",\n\"holy\",\n\"holyday\",\n\"homage\",\n\"homager\",\n\"home\",\n\"homelet\",\n\"homely\",\n\"homelyn\",\n\"homeoid\",\n\"homer\",\n\"homey\",\n\"homily\",\n\"hominal\",\n\"hominid\",\n\"hominy\",\n\"homish\",\n\"homo\",\n\"homodox\",\n\"homogen\",\n\"homonym\",\n\"homrai\",\n\"homy\",\n\"honda\",\n\"hondo\",\n\"hone\",\n\"honest\",\n\"honesty\",\n\"honey\",\n\"honeyed\",\n\"hong\",\n\"honied\",\n\"honily\",\n\"honk\",\n\"honker\",\n\"honor\",\n\"honoree\",\n\"honorer\",\n\"hontish\",\n\"hontous\",\n\"hooch\",\n\"hood\",\n\"hoodcap\",\n\"hooded\",\n\"hoodful\",\n\"hoodie\",\n\"hoodlum\",\n\"hoodman\",\n\"hoodoo\",\n\"hoodshy\",\n\"hooey\",\n\"hoof\",\n\"hoofed\",\n\"hoofer\",\n\"hoofish\",\n\"hooflet\",\n\"hoofrot\",\n\"hoofs\",\n\"hoofy\",\n\"hook\",\n\"hookah\",\n\"hooked\",\n\"hooker\",\n\"hookers\",\n\"hookish\",\n\"hooklet\",\n\"hookman\",\n\"hooktip\",\n\"hookum\",\n\"hookup\",\n\"hooky\",\n\"hoolock\",\n\"hooly\",\n\"hoon\",\n\"hoop\",\n\"hooped\",\n\"hooper\",\n\"hooping\",\n\"hoopla\",\n\"hoople\",\n\"hoopman\",\n\"hoopoe\",\n\"hoose\",\n\"hoosh\",\n\"hoot\",\n\"hootay\",\n\"hooter\",\n\"hoove\",\n\"hooven\",\n\"hoovey\",\n\"hop\",\n\"hopbine\",\n\"hopbush\",\n\"hope\",\n\"hoped\",\n\"hopeful\",\n\"hopeite\",\n\"hoper\",\n\"hopi\",\n\"hoplite\",\n\"hopoff\",\n\"hopped\",\n\"hopper\",\n\"hoppers\",\n\"hoppet\",\n\"hoppity\",\n\"hopple\",\n\"hoppy\",\n\"hoptoad\",\n\"hopvine\",\n\"hopyard\",\n\"hora\",\n\"horal\",\n\"horary\",\n\"hordary\",\n\"horde\",\n\"hordein\",\n\"horizon\",\n\"horme\",\n\"hormic\",\n\"hormigo\",\n\"hormion\",\n\"hormist\",\n\"hormone\",\n\"hormos\",\n\"horn\",\n\"horned\",\n\"horner\",\n\"hornet\",\n\"hornety\",\n\"hornful\",\n\"hornify\",\n\"hornily\",\n\"horning\",\n\"hornish\",\n\"hornist\",\n\"hornito\",\n\"hornlet\",\n\"horntip\",\n\"horny\",\n\"horrent\",\n\"horreum\",\n\"horrid\",\n\"horrify\",\n\"horror\",\n\"horse\",\n\"horser\",\n\"horsify\",\n\"horsily\",\n\"horsing\",\n\"horst\",\n\"horsy\",\n\"hortite\",\n\"hory\",\n\"hosanna\",\n\"hose\",\n\"hosed\",\n\"hosel\",\n\"hoseman\",\n\"hosier\",\n\"hosiery\",\n\"hospice\",\n\"host\",\n\"hostage\",\n\"hostel\",\n\"hoster\",\n\"hostess\",\n\"hostie\",\n\"hostile\",\n\"hosting\",\n\"hostler\",\n\"hostly\",\n\"hostry\",\n\"hot\",\n\"hotbed\",\n\"hotbox\",\n\"hotch\",\n\"hotel\",\n\"hotfoot\",\n\"hothead\",\n\"hoti\",\n\"hotly\",\n\"hotness\",\n\"hotspur\",\n\"hotter\",\n\"hottery\",\n\"hottish\",\n\"houbara\",\n\"hough\",\n\"hougher\",\n\"hounce\",\n\"hound\",\n\"hounder\",\n\"houndy\",\n\"hour\",\n\"hourful\",\n\"houri\",\n\"hourly\",\n\"housage\",\n\"housal\",\n\"house\",\n\"housel\",\n\"houser\",\n\"housing\",\n\"housty\",\n\"housy\",\n\"houtou\",\n\"houvari\",\n\"hove\",\n\"hovel\",\n\"hoveler\",\n\"hoven\",\n\"hover\",\n\"hoverer\",\n\"hoverly\",\n\"how\",\n\"howadji\",\n\"howbeit\",\n\"howdah\",\n\"howder\",\n\"howdie\",\n\"howdy\",\n\"howe\",\n\"howel\",\n\"however\",\n\"howff\",\n\"howish\",\n\"howk\",\n\"howkit\",\n\"howl\",\n\"howler\",\n\"howlet\",\n\"howling\",\n\"howlite\",\n\"howso\",\n\"hox\",\n\"hoy\",\n\"hoyden\",\n\"hoyle\",\n\"hoyman\",\n\"huaca\",\n\"huaco\",\n\"huarizo\",\n\"hub\",\n\"hubb\",\n\"hubba\",\n\"hubber\",\n\"hubble\",\n\"hubbly\",\n\"hubbub\",\n\"hubby\",\n\"hubshi\",\n\"huchen\",\n\"hucho\",\n\"huck\",\n\"huckle\",\n\"hud\",\n\"huddle\",\n\"huddler\",\n\"huddock\",\n\"huddup\",\n\"hue\",\n\"hued\",\n\"hueful\",\n\"hueless\",\n\"huer\",\n\"huff\",\n\"huffier\",\n\"huffily\",\n\"huffish\",\n\"huffle\",\n\"huffler\",\n\"huffy\",\n\"hug\",\n\"huge\",\n\"hugely\",\n\"hugeous\",\n\"hugger\",\n\"hugging\",\n\"huggle\",\n\"hugsome\",\n\"huh\",\n\"huia\",\n\"huipil\",\n\"huitain\",\n\"huke\",\n\"hula\",\n\"huldee\",\n\"hulk\",\n\"hulkage\",\n\"hulking\",\n\"hulky\",\n\"hull\",\n\"huller\",\n\"hullock\",\n\"hulloo\",\n\"hulsite\",\n\"hulster\",\n\"hulu\",\n\"hulver\",\n\"hum\",\n\"human\",\n\"humane\",\n\"humanly\",\n\"humate\",\n\"humble\",\n\"humbler\",\n\"humblie\",\n\"humbly\",\n\"humbo\",\n\"humbug\",\n\"humbuzz\",\n\"humdrum\",\n\"humect\",\n\"humeral\",\n\"humeri\",\n\"humerus\",\n\"humet\",\n\"humetty\",\n\"humhum\",\n\"humic\",\n\"humid\",\n\"humidly\",\n\"humidor\",\n\"humific\",\n\"humify\",\n\"humin\",\n\"humite\",\n\"humlie\",\n\"hummel\",\n\"hummer\",\n\"hummie\",\n\"humming\",\n\"hummock\",\n\"humor\",\n\"humoral\",\n\"humous\",\n\"hump\",\n\"humped\",\n\"humph\",\n\"humpty\",\n\"humpy\",\n\"humus\",\n\"hunch\",\n\"hunchet\",\n\"hunchy\",\n\"hundi\",\n\"hundred\",\n\"hung\",\n\"hunger\",\n\"hungry\",\n\"hunh\",\n\"hunk\",\n\"hunker\",\n\"hunkers\",\n\"hunkies\",\n\"hunks\",\n\"hunky\",\n\"hunt\",\n\"hunting\",\n\"hup\",\n\"hura\",\n\"hurdies\",\n\"hurdis\",\n\"hurdle\",\n\"hurdler\",\n\"hurds\",\n\"hure\",\n\"hureek\",\n\"hurgila\",\n\"hurkle\",\n\"hurl\",\n\"hurled\",\n\"hurler\",\n\"hurley\",\n\"hurling\",\n\"hurlock\",\n\"hurly\",\n\"huron\",\n\"hurr\",\n\"hurrah\",\n\"hurried\",\n\"hurrier\",\n\"hurrock\",\n\"hurroo\",\n\"hurry\",\n\"hurst\",\n\"hurt\",\n\"hurted\",\n\"hurter\",\n\"hurtful\",\n\"hurting\",\n\"hurtle\",\n\"hurty\",\n\"husband\",\n\"huse\",\n\"hush\",\n\"hushaby\",\n\"husheen\",\n\"hushel\",\n\"husher\",\n\"hushful\",\n\"hushing\",\n\"hushion\",\n\"husho\",\n\"husk\",\n\"husked\",\n\"husker\",\n\"huskily\",\n\"husking\",\n\"husky\",\n\"huso\",\n\"huspil\",\n\"huss\",\n\"hussar\",\n\"hussy\",\n\"husting\",\n\"hustle\",\n\"hustler\",\n\"hut\",\n\"hutch\",\n\"hutcher\",\n\"hutchet\",\n\"huthold\",\n\"hutia\",\n\"hutlet\",\n\"hutment\",\n\"huvelyk\",\n\"huzoor\",\n\"huzz\",\n\"huzza\",\n\"huzzard\",\n\"hyaena\",\n\"hyaline\",\n\"hyalite\",\n\"hyaloid\",\n\"hybosis\",\n\"hybrid\",\n\"hydatid\",\n\"hydnoid\",\n\"hydrant\",\n\"hydrate\",\n\"hydrazo\",\n\"hydria\",\n\"hydric\",\n\"hydride\",\n\"hydro\",\n\"hydroa\",\n\"hydroid\",\n\"hydrol\",\n\"hydrome\",\n\"hydrone\",\n\"hydrops\",\n\"hydrous\",\n\"hydroxy\",\n\"hydrula\",\n\"hyena\",\n\"hyenic\",\n\"hyenine\",\n\"hyenoid\",\n\"hyetal\",\n\"hygeist\",\n\"hygiene\",\n\"hygric\",\n\"hygrine\",\n\"hygroma\",\n\"hying\",\n\"hyke\",\n\"hyle\",\n\"hyleg\",\n\"hylic\",\n\"hylism\",\n\"hylist\",\n\"hyloid\",\n\"hymen\",\n\"hymenal\",\n\"hymenic\",\n\"hymn\",\n\"hymnal\",\n\"hymnary\",\n\"hymner\",\n\"hymnic\",\n\"hymnist\",\n\"hymnode\",\n\"hymnody\",\n\"hynde\",\n\"hyne\",\n\"hyoid\",\n\"hyoidal\",\n\"hyoidan\",\n\"hyoides\",\n\"hyp\",\n\"hypate\",\n\"hypaton\",\n\"hyper\",\n\"hypha\",\n\"hyphal\",\n\"hyphema\",\n\"hyphen\",\n\"hypho\",\n\"hypnody\",\n\"hypnoid\",\n\"hypnone\",\n\"hypo\",\n\"hypogee\",\n\"hypoid\",\n\"hyponym\",\n\"hypopus\",\n\"hyporit\",\n\"hyppish\",\n\"hypural\",\n\"hyraces\",\n\"hyracid\",\n\"hyrax\",\n\"hyson\",\n\"hyssop\",\n\"i\",\n\"iamb\",\n\"iambi\",\n\"iambic\",\n\"iambist\",\n\"iambize\",\n\"iambus\",\n\"iao\",\n\"iatric\",\n\"iba\",\n\"iberite\",\n\"ibex\",\n\"ibices\",\n\"ibid\",\n\"ibidine\",\n\"ibis\",\n\"ibolium\",\n\"ibota\",\n\"icaco\",\n\"ice\",\n\"iceberg\",\n\"iceboat\",\n\"icebone\",\n\"icebox\",\n\"icecap\",\n\"iced\",\n\"icefall\",\n\"icefish\",\n\"iceland\",\n\"iceleaf\",\n\"iceless\",\n\"icelike\",\n\"iceman\",\n\"iceroot\",\n\"icework\",\n\"ich\",\n\"ichnite\",\n\"icho\",\n\"ichor\",\n\"ichthus\",\n\"ichu\",\n\"icica\",\n\"icicle\",\n\"icicled\",\n\"icily\",\n\"iciness\",\n\"icing\",\n\"icon\",\n\"iconic\",\n\"iconism\",\n\"icosian\",\n\"icotype\",\n\"icteric\",\n\"icterus\",\n\"ictic\",\n\"ictuate\",\n\"ictus\",\n\"icy\",\n\"id\",\n\"idalia\",\n\"idant\",\n\"iddat\",\n\"ide\",\n\"idea\",\n\"ideaed\",\n\"ideaful\",\n\"ideal\",\n\"ideally\",\n\"ideate\",\n\"ideist\",\n\"identic\",\n\"ides\",\n\"idgah\",\n\"idiasm\",\n\"idic\",\n\"idiocy\",\n\"idiom\",\n\"idiot\",\n\"idiotcy\",\n\"idiotic\",\n\"idiotry\",\n\"idite\",\n\"iditol\",\n\"idle\",\n\"idleful\",\n\"idleman\",\n\"idler\",\n\"idleset\",\n\"idlety\",\n\"idlish\",\n\"idly\",\n\"idol\",\n\"idola\",\n\"idolify\",\n\"idolism\",\n\"idolist\",\n\"idolize\",\n\"idolous\",\n\"idolum\",\n\"idoneal\",\n\"idorgan\",\n\"idose\",\n\"idryl\",\n\"idyl\",\n\"idyler\",\n\"idylism\",\n\"idylist\",\n\"idylize\",\n\"idyllic\",\n\"ie\",\n\"if\",\n\"ife\",\n\"iffy\",\n\"igloo\",\n\"ignatia\",\n\"ignavia\",\n\"igneous\",\n\"ignify\",\n\"ignite\",\n\"igniter\",\n\"ignitor\",\n\"ignoble\",\n\"ignobly\",\n\"ignore\",\n\"ignorer\",\n\"ignote\",\n\"iguana\",\n\"iguanid\",\n\"ihi\",\n\"ihleite\",\n\"ihram\",\n\"iiwi\",\n\"ijma\",\n\"ijolite\",\n\"ikat\",\n\"ikey\",\n\"ikona\",\n\"ikra\",\n\"ileac\",\n\"ileitis\",\n\"ileon\",\n\"ilesite\",\n\"ileum\",\n\"ileus\",\n\"ilex\",\n\"ilia\",\n\"iliac\",\n\"iliacus\",\n\"iliahi\",\n\"ilial\",\n\"iliau\",\n\"ilicic\",\n\"ilicin\",\n\"ilima\",\n\"ilium\",\n\"ilk\",\n\"ilka\",\n\"ilkane\",\n\"ill\",\n\"illapse\",\n\"illeck\",\n\"illegal\",\n\"illeism\",\n\"illeist\",\n\"illess\",\n\"illfare\",\n\"illicit\",\n\"illish\",\n\"illium\",\n\"illness\",\n\"illocal\",\n\"illogic\",\n\"illoyal\",\n\"illth\",\n\"illude\",\n\"illuder\",\n\"illume\",\n\"illumer\",\n\"illupi\",\n\"illure\",\n\"illusor\",\n\"illy\",\n\"ilot\",\n\"ilvaite\",\n\"image\",\n\"imager\",\n\"imagery\",\n\"imagine\",\n\"imagism\",\n\"imagist\",\n\"imago\",\n\"imam\",\n\"imamah\",\n\"imamate\",\n\"imamic\",\n\"imaret\",\n\"imban\",\n\"imband\",\n\"imbarge\",\n\"imbark\",\n\"imbarn\",\n\"imbased\",\n\"imbat\",\n\"imbauba\",\n\"imbe\",\n\"imbed\",\n\"imber\",\n\"imbibe\",\n\"imbiber\",\n\"imbondo\",\n\"imbosom\",\n\"imbower\",\n\"imbrex\",\n\"imbrue\",\n\"imbrute\",\n\"imbue\",\n\"imburse\",\n\"imi\",\n\"imide\",\n\"imidic\",\n\"imine\",\n\"imino\",\n\"imitant\",\n\"imitate\",\n\"immane\",\n\"immask\",\n\"immense\",\n\"immerd\",\n\"immerge\",\n\"immerit\",\n\"immerse\",\n\"immew\",\n\"immi\",\n\"immit\",\n\"immix\",\n\"immoral\",\n\"immound\",\n\"immund\",\n\"immune\",\n\"immure\",\n\"immute\",\n\"imonium\",\n\"imp\",\n\"impack\",\n\"impact\",\n\"impages\",\n\"impaint\",\n\"impair\",\n\"impala\",\n\"impale\",\n\"impaler\",\n\"impall\",\n\"impalm\",\n\"impalsy\",\n\"impane\",\n\"impanel\",\n\"impar\",\n\"impark\",\n\"imparl\",\n\"impart\",\n\"impasse\",\n\"impaste\",\n\"impasto\",\n\"impave\",\n\"impavid\",\n\"impawn\",\n\"impeach\",\n\"impearl\",\n\"impede\",\n\"impeder\",\n\"impel\",\n\"impen\",\n\"impend\",\n\"impent\",\n\"imperia\",\n\"imperil\",\n\"impest\",\n\"impetre\",\n\"impetus\",\n\"imphee\",\n\"impi\",\n\"impiety\",\n\"impinge\",\n\"impious\",\n\"impish\",\n\"implant\",\n\"implate\",\n\"implead\",\n\"implete\",\n\"implex\",\n\"implial\",\n\"impling\",\n\"implode\",\n\"implore\",\n\"implume\",\n\"imply\",\n\"impofo\",\n\"impone\",\n\"impoor\",\n\"import\",\n\"imposal\",\n\"impose\",\n\"imposer\",\n\"impost\",\n\"impot\",\n\"impound\",\n\"impreg\",\n\"impregn\",\n\"impresa\",\n\"imprese\",\n\"impress\",\n\"imprest\",\n\"imprime\",\n\"imprint\",\n\"improof\",\n\"improve\",\n\"impship\",\n\"impubic\",\n\"impugn\",\n\"impulse\",\n\"impure\",\n\"impute\",\n\"imputer\",\n\"impy\",\n\"imshi\",\n\"imsonic\",\n\"imu\",\n\"in\",\n\"inachid\",\n\"inadept\",\n\"inagile\",\n\"inaja\",\n\"inane\",\n\"inanely\",\n\"inanga\",\n\"inanity\",\n\"inapt\",\n\"inaptly\",\n\"inarch\",\n\"inarm\",\n\"inaugur\",\n\"inaxon\",\n\"inbe\",\n\"inbeing\",\n\"inbent\",\n\"inbirth\",\n\"inblow\",\n\"inblown\",\n\"inboard\",\n\"inbond\",\n\"inborn\",\n\"inbound\",\n\"inbread\",\n\"inbreak\",\n\"inbred\",\n\"inbreed\",\n\"inbring\",\n\"inbuilt\",\n\"inburnt\",\n\"inburst\",\n\"inby\",\n\"incarn\",\n\"incase\",\n\"incast\",\n\"incense\",\n\"incept\",\n\"incest\",\n\"inch\",\n\"inched\",\n\"inchpin\",\n\"incide\",\n\"incisal\",\n\"incise\",\n\"incisor\",\n\"incite\",\n\"inciter\",\n\"incivic\",\n\"incline\",\n\"inclip\",\n\"inclose\",\n\"include\",\n\"inclusa\",\n\"incluse\",\n\"incog\",\n\"income\",\n\"incomer\",\n\"inconnu\",\n\"incrash\",\n\"increep\",\n\"increst\",\n\"incross\",\n\"incrust\",\n\"incubi\",\n\"incubus\",\n\"incudal\",\n\"incudes\",\n\"incult\",\n\"incur\",\n\"incurse\",\n\"incurve\",\n\"incus\",\n\"incuse\",\n\"incut\",\n\"indaba\",\n\"indan\",\n\"indane\",\n\"indart\",\n\"indazin\",\n\"indazol\",\n\"inde\",\n\"indebt\",\n\"indeed\",\n\"indeedy\",\n\"indene\",\n\"indent\",\n\"index\",\n\"indexed\",\n\"indexer\",\n\"indic\",\n\"indican\",\n\"indices\",\n\"indicia\",\n\"indict\",\n\"indign\",\n\"indigo\",\n\"indite\",\n\"inditer\",\n\"indium\",\n\"indogen\",\n\"indole\",\n\"indoles\",\n\"indolyl\",\n\"indoor\",\n\"indoors\",\n\"indorse\",\n\"indoxyl\",\n\"indraft\",\n\"indrawn\",\n\"indri\",\n\"induce\",\n\"induced\",\n\"inducer\",\n\"induct\",\n\"indue\",\n\"indulge\",\n\"indult\",\n\"indulto\",\n\"induna\",\n\"indwell\",\n\"indy\",\n\"indyl\",\n\"indylic\",\n\"inearth\",\n\"inept\",\n\"ineptly\",\n\"inequal\",\n\"inerm\",\n\"inert\",\n\"inertia\",\n\"inertly\",\n\"inesite\",\n\"ineunt\",\n\"inexact\",\n\"inexist\",\n\"inface\",\n\"infall\",\n\"infame\",\n\"infamy\",\n\"infancy\",\n\"infand\",\n\"infang\",\n\"infant\",\n\"infanta\",\n\"infante\",\n\"infarct\",\n\"infare\",\n\"infaust\",\n\"infect\",\n\"infeed\",\n\"infeft\",\n\"infelt\",\n\"infer\",\n\"infern\",\n\"inferno\",\n\"infest\",\n\"infidel\",\n\"infield\",\n\"infill\",\n\"infilm\",\n\"infirm\",\n\"infit\",\n\"infix\",\n\"inflame\",\n\"inflate\",\n\"inflect\",\n\"inflex\",\n\"inflict\",\n\"inflood\",\n\"inflow\",\n\"influx\",\n\"infold\",\n\"inform\",\n\"infra\",\n\"infract\",\n\"infula\",\n\"infuse\",\n\"infuser\",\n\"ing\",\n\"ingate\",\n\"ingenit\",\n\"ingenue\",\n\"ingest\",\n\"ingesta\",\n\"ingiver\",\n\"ingle\",\n\"inglobe\",\n\"ingoing\",\n\"ingot\",\n\"ingraft\",\n\"ingrain\",\n\"ingrate\",\n\"ingress\",\n\"ingross\",\n\"ingrow\",\n\"ingrown\",\n\"inguen\",\n\"ingulf\",\n\"inhabit\",\n\"inhale\",\n\"inhaler\",\n\"inhaul\",\n\"inhaust\",\n\"inhere\",\n\"inherit\",\n\"inhiate\",\n\"inhibit\",\n\"inhuman\",\n\"inhume\",\n\"inhumer\",\n\"inial\",\n\"iniome\",\n\"inion\",\n\"initial\",\n\"initis\",\n\"initive\",\n\"inject\",\n\"injelly\",\n\"injunct\",\n\"injure\",\n\"injured\",\n\"injurer\",\n\"injury\",\n\"ink\",\n\"inkbush\",\n\"inken\",\n\"inker\",\n\"inket\",\n\"inkfish\",\n\"inkhorn\",\n\"inkish\",\n\"inkle\",\n\"inkless\",\n\"inklike\",\n\"inkling\",\n\"inknot\",\n\"inkosi\",\n\"inkpot\",\n\"inkroot\",\n\"inks\",\n\"inkshed\",\n\"inkweed\",\n\"inkwell\",\n\"inkwood\",\n\"inky\",\n\"inlaid\",\n\"inlaik\",\n\"inlake\",\n\"inland\",\n\"inlaut\",\n\"inlaw\",\n\"inlawry\",\n\"inlay\",\n\"inlayer\",\n\"inleak\",\n\"inlet\",\n\"inlier\",\n\"inlook\",\n\"inly\",\n\"inlying\",\n\"inmate\",\n\"inmeats\",\n\"inmost\",\n\"inn\",\n\"innate\",\n\"inneity\",\n\"inner\",\n\"innerly\",\n\"innerve\",\n\"inness\",\n\"innest\",\n\"innet\",\n\"inning\",\n\"innless\",\n\"innyard\",\n\"inocyte\",\n\"inogen\",\n\"inoglia\",\n\"inolith\",\n\"inoma\",\n\"inone\",\n\"inopine\",\n\"inorb\",\n\"inosic\",\n\"inosin\",\n\"inosite\",\n\"inower\",\n\"inphase\",\n\"inport\",\n\"inpour\",\n\"inpush\",\n\"input\",\n\"inquest\",\n\"inquiet\",\n\"inquire\",\n\"inquiry\",\n\"inring\",\n\"inro\",\n\"inroad\",\n\"inroll\",\n\"inrub\",\n\"inrun\",\n\"inrush\",\n\"insack\",\n\"insane\",\n\"insculp\",\n\"insea\",\n\"inseam\",\n\"insect\",\n\"insee\",\n\"inseer\",\n\"insense\",\n\"insert\",\n\"inset\",\n\"inshave\",\n\"inshell\",\n\"inship\",\n\"inshoe\",\n\"inshoot\",\n\"inshore\",\n\"inside\",\n\"insider\",\n\"insight\",\n\"insigne\",\n\"insipid\",\n\"insist\",\n\"insnare\",\n\"insofar\",\n\"insole\",\n\"insolid\",\n\"insooth\",\n\"insorb\",\n\"insoul\",\n\"inspan\",\n\"inspeak\",\n\"inspect\",\n\"inspire\",\n\"inspoke\",\n\"install\",\n\"instant\",\n\"instar\",\n\"instate\",\n\"instead\",\n\"insteam\",\n\"insteep\",\n\"instep\",\n\"instill\",\n\"insula\",\n\"insular\",\n\"insulin\",\n\"insulse\",\n\"insult\",\n\"insunk\",\n\"insure\",\n\"insured\",\n\"insurer\",\n\"insurge\",\n\"inswamp\",\n\"inswell\",\n\"inswept\",\n\"inswing\",\n\"intact\",\n\"intake\",\n\"intaker\",\n\"integer\",\n\"inteind\",\n\"intend\",\n\"intense\",\n\"intent\",\n\"inter\",\n\"interim\",\n\"intern\",\n\"intext\",\n\"inthrow\",\n\"intil\",\n\"intima\",\n\"intimal\",\n\"intine\",\n\"into\",\n\"intoed\",\n\"intone\",\n\"intoner\",\n\"intort\",\n\"intown\",\n\"intrada\",\n\"intrait\",\n\"intrant\",\n\"intreat\",\n\"intrine\",\n\"introit\",\n\"intrude\",\n\"intruse\",\n\"intrust\",\n\"intube\",\n\"intue\",\n\"intuent\",\n\"intuit\",\n\"inturn\",\n\"intwist\",\n\"inula\",\n\"inulase\",\n\"inulin\",\n\"inuloid\",\n\"inunct\",\n\"inure\",\n\"inured\",\n\"inurn\",\n\"inutile\",\n\"invade\",\n\"invader\",\n\"invalid\",\n\"inveigh\",\n\"inveil\",\n\"invein\",\n\"invent\",\n\"inverse\",\n\"invert\",\n\"invest\",\n\"invigor\",\n\"invised\",\n\"invital\",\n\"invite\",\n\"invitee\",\n\"inviter\",\n\"invivid\",\n\"invoice\",\n\"invoke\",\n\"invoker\",\n\"involve\",\n\"inwale\",\n\"inwall\",\n\"inward\",\n\"inwards\",\n\"inweave\",\n\"inweed\",\n\"inwick\",\n\"inwind\",\n\"inwit\",\n\"inwith\",\n\"inwood\",\n\"inwork\",\n\"inworn\",\n\"inwound\",\n\"inwoven\",\n\"inwrap\",\n\"inwrit\",\n\"inyoite\",\n\"inyoke\",\n\"io\",\n\"iodate\",\n\"iodic\",\n\"iodide\",\n\"iodine\",\n\"iodism\",\n\"iodite\",\n\"iodize\",\n\"iodizer\",\n\"iodo\",\n\"iodol\",\n\"iodoso\",\n\"iodous\",\n\"iodoxy\",\n\"iolite\",\n\"ion\",\n\"ionic\",\n\"ionium\",\n\"ionize\",\n\"ionizer\",\n\"ionogen\",\n\"ionone\",\n\"iota\",\n\"iotize\",\n\"ipecac\",\n\"ipid\",\n\"ipil\",\n\"ipomea\",\n\"ipseand\",\n\"ipseity\",\n\"iracund\",\n\"irade\",\n\"irate\",\n\"irately\",\n\"ire\",\n\"ireful\",\n\"ireless\",\n\"irene\",\n\"irenic\",\n\"irenics\",\n\"irian\",\n\"irid\",\n\"iridal\",\n\"iridate\",\n\"irides\",\n\"iridial\",\n\"iridian\",\n\"iridic\",\n\"iridin\",\n\"iridine\",\n\"iridite\",\n\"iridium\",\n\"iridize\",\n\"iris\",\n\"irised\",\n\"irisin\",\n\"iritic\",\n\"iritis\",\n\"irk\",\n\"irksome\",\n\"irok\",\n\"iroko\",\n\"iron\",\n\"irone\",\n\"ironer\",\n\"ironice\",\n\"ironish\",\n\"ironism\",\n\"ironist\",\n\"ironize\",\n\"ironly\",\n\"ironman\",\n\"irony\",\n\"irrisor\",\n\"irrupt\",\n\"is\",\n\"isagoge\",\n\"isagon\",\n\"isamine\",\n\"isatate\",\n\"isatic\",\n\"isatide\",\n\"isatin\",\n\"isazoxy\",\n\"isba\",\n\"ischiac\",\n\"ischial\",\n\"ischium\",\n\"ischury\",\n\"iserine\",\n\"iserite\",\n\"isidium\",\n\"isidoid\",\n\"island\",\n\"islandy\",\n\"islay\",\n\"isle\",\n\"islet\",\n\"isleted\",\n\"islot\",\n\"ism\",\n\"ismal\",\n\"ismatic\",\n\"ismdom\",\n\"ismy\",\n\"iso\",\n\"isoamyl\",\n\"isobar\",\n\"isobare\",\n\"isobase\",\n\"isobath\",\n\"isochor\",\n\"isocola\",\n\"isocrat\",\n\"isodont\",\n\"isoflor\",\n\"isogamy\",\n\"isogen\",\n\"isogeny\",\n\"isogon\",\n\"isogram\",\n\"isohel\",\n\"isohyet\",\n\"isolate\",\n\"isology\",\n\"isomer\",\n\"isomere\",\n\"isomery\",\n\"isoneph\",\n\"isonomy\",\n\"isonym\",\n\"isonymy\",\n\"isopag\",\n\"isopod\",\n\"isopoly\",\n\"isoptic\",\n\"isopyre\",\n\"isotac\",\n\"isotely\",\n\"isotome\",\n\"isotony\",\n\"isotope\",\n\"isotopy\",\n\"isotron\",\n\"isotype\",\n\"isoxime\",\n\"issei\",\n\"issite\",\n\"issuant\",\n\"issue\",\n\"issuer\",\n\"issuing\",\n\"ist\",\n\"isthmi\",\n\"isthmic\",\n\"isthmus\",\n\"istle\",\n\"istoke\",\n\"isuret\",\n\"isuroid\",\n\"it\",\n\"itacism\",\n\"itacist\",\n\"italics\",\n\"italite\",\n\"itch\",\n\"itching\",\n\"itchy\",\n\"itcze\",\n\"item\",\n\"iteming\",\n\"itemize\",\n\"itemy\",\n\"iter\",\n\"iterant\",\n\"iterate\",\n\"ither\",\n\"itmo\",\n\"itoubou\",\n\"its\",\n\"itself\",\n\"iturite\",\n\"itzebu\",\n\"iva\",\n\"ivied\",\n\"ivin\",\n\"ivoried\",\n\"ivorine\",\n\"ivorist\",\n\"ivory\",\n\"ivy\",\n\"ivylike\",\n\"ivyweed\",\n\"ivywood\",\n\"ivywort\",\n\"iwa\",\n\"iwaiwa\",\n\"iwis\",\n\"ixodian\",\n\"ixodic\",\n\"ixodid\",\n\"iyo\",\n\"izar\",\n\"izard\",\n\"izle\",\n\"izote\",\n\"iztle\",\n\"izzard\",\n\"j\",\n\"jab\",\n\"jabbed\",\n\"jabber\",\n\"jabbing\",\n\"jabble\",\n\"jabers\",\n\"jabia\",\n\"jabiru\",\n\"jabot\",\n\"jabul\",\n\"jacal\",\n\"jacamar\",\n\"jacami\",\n\"jacamin\",\n\"jacana\",\n\"jacare\",\n\"jacate\",\n\"jacchus\",\n\"jacent\",\n\"jacinth\",\n\"jack\",\n\"jackal\",\n\"jackass\",\n\"jackbox\",\n\"jackboy\",\n\"jackdaw\",\n\"jackeen\",\n\"jacker\",\n\"jacket\",\n\"jackety\",\n\"jackleg\",\n\"jackman\",\n\"jacko\",\n\"jackrod\",\n\"jacksaw\",\n\"jacktan\",\n\"jacobus\",\n\"jacoby\",\n\"jaconet\",\n\"jactant\",\n\"jacu\",\n\"jacuaru\",\n\"jadder\",\n\"jade\",\n\"jaded\",\n\"jadedly\",\n\"jadeite\",\n\"jadery\",\n\"jadish\",\n\"jady\",\n\"jaeger\",\n\"jag\",\n\"jagat\",\n\"jager\",\n\"jagged\",\n\"jagger\",\n\"jaggery\",\n\"jaggy\",\n\"jagir\",\n\"jagla\",\n\"jagless\",\n\"jagong\",\n\"jagrata\",\n\"jagua\",\n\"jaguar\",\n\"jail\",\n\"jailage\",\n\"jaildom\",\n\"jailer\",\n\"jailish\",\n\"jajman\",\n\"jake\",\n\"jakes\",\n\"jako\",\n\"jalap\",\n\"jalapa\",\n\"jalapin\",\n\"jalkar\",\n\"jalopy\",\n\"jalouse\",\n\"jam\",\n\"jama\",\n\"jaman\",\n\"jamb\",\n\"jambeau\",\n\"jambo\",\n\"jambone\",\n\"jambool\",\n\"jambosa\",\n\"jamdani\",\n\"jami\",\n\"jamlike\",\n\"jammer\",\n\"jammy\",\n\"jampan\",\n\"jampani\",\n\"jamwood\",\n\"janapa\",\n\"janapan\",\n\"jane\",\n\"jangada\",\n\"jangkar\",\n\"jangle\",\n\"jangler\",\n\"jangly\",\n\"janitor\",\n\"jank\",\n\"janker\",\n\"jann\",\n\"jannock\",\n\"jantu\",\n\"janua\",\n\"jaob\",\n\"jap\",\n\"japan\",\n\"jape\",\n\"japer\",\n\"japery\",\n\"japing\",\n\"japish\",\n\"jaquima\",\n\"jar\",\n\"jara\",\n\"jaragua\",\n\"jarbird\",\n\"jarble\",\n\"jarbot\",\n\"jarfly\",\n\"jarful\",\n\"jarg\",\n\"jargon\",\n\"jarkman\",\n\"jarl\",\n\"jarldom\",\n\"jarless\",\n\"jarnut\",\n\"jarool\",\n\"jarra\",\n\"jarrah\",\n\"jarring\",\n\"jarry\",\n\"jarvey\",\n\"jasey\",\n\"jaseyed\",\n\"jasmine\",\n\"jasmone\",\n\"jasper\",\n\"jaspery\",\n\"jaspis\",\n\"jaspoid\",\n\"jass\",\n\"jassid\",\n\"jassoid\",\n\"jatha\",\n\"jati\",\n\"jato\",\n\"jaudie\",\n\"jauk\",\n\"jaun\",\n\"jaunce\",\n\"jaunder\",\n\"jaunt\",\n\"jauntie\",\n\"jaunty\",\n\"jaup\",\n\"javali\",\n\"javelin\",\n\"javer\",\n\"jaw\",\n\"jawab\",\n\"jawbone\",\n\"jawed\",\n\"jawfall\",\n\"jawfish\",\n\"jawfoot\",\n\"jawless\",\n\"jawy\",\n\"jay\",\n\"jayhawk\",\n\"jaypie\",\n\"jaywalk\",\n\"jazz\",\n\"jazzer\",\n\"jazzily\",\n\"jazzy\",\n\"jealous\",\n\"jean\",\n\"jeans\",\n\"jecoral\",\n\"jecorin\",\n\"jed\",\n\"jedcock\",\n\"jedding\",\n\"jeddock\",\n\"jeel\",\n\"jeep\",\n\"jeer\",\n\"jeerer\",\n\"jeering\",\n\"jeery\",\n\"jeff\",\n\"jehu\",\n\"jehup\",\n\"jejunal\",\n\"jejune\",\n\"jejunum\",\n\"jelab\",\n\"jelick\",\n\"jell\",\n\"jellica\",\n\"jellico\",\n\"jellied\",\n\"jellify\",\n\"jellily\",\n\"jelloid\",\n\"jelly\",\n\"jemadar\",\n\"jemmily\",\n\"jemmy\",\n\"jenkin\",\n\"jenna\",\n\"jennet\",\n\"jennier\",\n\"jenny\",\n\"jeofail\",\n\"jeopard\",\n\"jerboa\",\n\"jereed\",\n\"jerez\",\n\"jerib\",\n\"jerk\",\n\"jerker\",\n\"jerkily\",\n\"jerkin\",\n\"jerkish\",\n\"jerky\",\n\"jerl\",\n\"jerm\",\n\"jerque\",\n\"jerquer\",\n\"jerry\",\n\"jersey\",\n\"jert\",\n\"jervia\",\n\"jervina\",\n\"jervine\",\n\"jess\",\n\"jessamy\",\n\"jessant\",\n\"jessed\",\n\"jessur\",\n\"jest\",\n\"jestee\",\n\"jester\",\n\"jestful\",\n\"jesting\",\n\"jet\",\n\"jetbead\",\n\"jete\",\n\"jetsam\",\n\"jettage\",\n\"jetted\",\n\"jetter\",\n\"jettied\",\n\"jetton\",\n\"jetty\",\n\"jetware\",\n\"jewbird\",\n\"jewbush\",\n\"jewel\",\n\"jeweler\",\n\"jewelry\",\n\"jewely\",\n\"jewfish\",\n\"jezail\",\n\"jeziah\",\n\"jharal\",\n\"jheel\",\n\"jhool\",\n\"jhow\",\n\"jib\",\n\"jibbah\",\n\"jibber\",\n\"jibby\",\n\"jibe\",\n\"jibhead\",\n\"jibi\",\n\"jibman\",\n\"jiboa\",\n\"jibstay\",\n\"jicama\",\n\"jicara\",\n\"jiff\",\n\"jiffle\",\n\"jiffy\",\n\"jig\",\n\"jigger\",\n\"jiggers\",\n\"jigget\",\n\"jiggety\",\n\"jiggish\",\n\"jiggle\",\n\"jiggly\",\n\"jiggy\",\n\"jiglike\",\n\"jigman\",\n\"jihad\",\n\"jikungu\",\n\"jillet\",\n\"jilt\",\n\"jiltee\",\n\"jilter\",\n\"jiltish\",\n\"jimbang\",\n\"jimjam\",\n\"jimmy\",\n\"jimp\",\n\"jimply\",\n\"jina\",\n\"jing\",\n\"jingal\",\n\"jingle\",\n\"jingled\",\n\"jingler\",\n\"jinglet\",\n\"jingly\",\n\"jingo\",\n\"jinja\",\n\"jinjili\",\n\"jink\",\n\"jinker\",\n\"jinket\",\n\"jinkle\",\n\"jinks\",\n\"jinn\",\n\"jinni\",\n\"jinny\",\n\"jinriki\",\n\"jinx\",\n\"jipper\",\n\"jiqui\",\n\"jirble\",\n\"jirga\",\n\"jiti\",\n\"jitneur\",\n\"jitney\",\n\"jitro\",\n\"jitter\",\n\"jitters\",\n\"jittery\",\n\"jiva\",\n\"jive\",\n\"jixie\",\n\"jo\",\n\"job\",\n\"jobade\",\n\"jobarbe\",\n\"jobber\",\n\"jobbery\",\n\"jobbet\",\n\"jobbing\",\n\"jobbish\",\n\"jobble\",\n\"jobless\",\n\"jobman\",\n\"jobo\",\n\"joch\",\n\"jock\",\n\"jocker\",\n\"jockey\",\n\"jocko\",\n\"jocoque\",\n\"jocose\",\n\"jocote\",\n\"jocu\",\n\"jocular\",\n\"jocum\",\n\"jocuma\",\n\"jocund\",\n\"jodel\",\n\"jodelr\",\n\"joe\",\n\"joebush\",\n\"joewood\",\n\"joey\",\n\"jog\",\n\"jogger\",\n\"joggle\",\n\"joggler\",\n\"joggly\",\n\"johnin\",\n\"join\",\n\"joinant\",\n\"joinder\",\n\"joiner\",\n\"joinery\",\n\"joining\",\n\"joint\",\n\"jointed\",\n\"jointer\",\n\"jointly\",\n\"jointy\",\n\"joist\",\n\"jojoba\",\n\"joke\",\n\"jokelet\",\n\"joker\",\n\"jokish\",\n\"jokist\",\n\"jokul\",\n\"joky\",\n\"joll\",\n\"jollier\",\n\"jollify\",\n\"jollily\",\n\"jollity\",\n\"jollop\",\n\"jolly\",\n\"jolt\",\n\"jolter\",\n\"jolting\",\n\"jolty\",\n\"jonque\",\n\"jonquil\",\n\"joola\",\n\"joom\",\n\"jordan\",\n\"joree\",\n\"jorum\",\n\"joseite\",\n\"josh\",\n\"josher\",\n\"joshi\",\n\"josie\",\n\"joskin\",\n\"joss\",\n\"josser\",\n\"jostle\",\n\"jostler\",\n\"jot\",\n\"jota\",\n\"jotisi\",\n\"jotter\",\n\"jotting\",\n\"jotty\",\n\"joubarb\",\n\"joug\",\n\"jough\",\n\"jouk\",\n\"joule\",\n\"joulean\",\n\"jounce\",\n\"journal\",\n\"journey\",\n\"jours\",\n\"joust\",\n\"jouster\",\n\"jovial\",\n\"jow\",\n\"jowar\",\n\"jowari\",\n\"jowel\",\n\"jower\",\n\"jowery\",\n\"jowl\",\n\"jowler\",\n\"jowlish\",\n\"jowlop\",\n\"jowly\",\n\"jowpy\",\n\"jowser\",\n\"jowter\",\n\"joy\",\n\"joyance\",\n\"joyancy\",\n\"joyant\",\n\"joyful\",\n\"joyhop\",\n\"joyleaf\",\n\"joyless\",\n\"joylet\",\n\"joyous\",\n\"joysome\",\n\"joyweed\",\n\"juba\",\n\"jubate\",\n\"jubbah\",\n\"jubbe\",\n\"jube\",\n\"jubilee\",\n\"jubilus\",\n\"juck\",\n\"juckies\",\n\"jud\",\n\"judcock\",\n\"judex\",\n\"judge\",\n\"judger\",\n\"judices\",\n\"judo\",\n\"jufti\",\n\"jug\",\n\"jugal\",\n\"jugale\",\n\"jugate\",\n\"jugated\",\n\"juger\",\n\"jugerum\",\n\"jugful\",\n\"jugger\",\n\"juggins\",\n\"juggle\",\n\"juggler\",\n\"juglone\",\n\"jugular\",\n\"jugulum\",\n\"jugum\",\n\"juice\",\n\"juicily\",\n\"juicy\",\n\"jujitsu\",\n\"juju\",\n\"jujube\",\n\"jujuism\",\n\"jujuist\",\n\"juke\",\n\"jukebox\",\n\"julep\",\n\"julid\",\n\"julidan\",\n\"julio\",\n\"juloid\",\n\"julole\",\n\"julolin\",\n\"jumart\",\n\"jumba\",\n\"jumble\",\n\"jumbler\",\n\"jumbly\",\n\"jumbo\",\n\"jumbuck\",\n\"jumby\",\n\"jumelle\",\n\"jument\",\n\"jumfru\",\n\"jumma\",\n\"jump\",\n\"jumper\",\n\"jumpy\",\n\"juncite\",\n\"juncous\",\n\"june\",\n\"jungle\",\n\"jungled\",\n\"jungli\",\n\"jungly\",\n\"juniata\",\n\"junior\",\n\"juniper\",\n\"junk\",\n\"junker\",\n\"junket\",\n\"junking\",\n\"junkman\",\n\"junt\",\n\"junta\",\n\"junto\",\n\"jupati\",\n\"jupe\",\n\"jupon\",\n\"jural\",\n\"jurally\",\n\"jurant\",\n\"jurara\",\n\"jurat\",\n\"jurator\",\n\"jure\",\n\"jurel\",\n\"juridic\",\n\"juring\",\n\"jurist\",\n\"juror\",\n\"jury\",\n\"juryman\",\n\"jussel\",\n\"jussion\",\n\"jussive\",\n\"jussory\",\n\"just\",\n\"justen\",\n\"justice\",\n\"justify\",\n\"justly\",\n\"justo\",\n\"jut\",\n\"jute\",\n\"jutka\",\n\"jutting\",\n\"jutty\",\n\"juvenal\",\n\"juvia\",\n\"juvite\",\n\"jyngine\",\n\"jynx\",\n\"k\",\n\"ka\",\n\"kabaya\",\n\"kabel\",\n\"kaberu\",\n\"kabiet\",\n\"kabuki\",\n\"kachin\",\n\"kadaya\",\n\"kadein\",\n\"kados\",\n\"kaffir\",\n\"kafir\",\n\"kafirin\",\n\"kafiz\",\n\"kafta\",\n\"kago\",\n\"kagu\",\n\"kaha\",\n\"kahar\",\n\"kahau\",\n\"kahili\",\n\"kahu\",\n\"kahuna\",\n\"kai\",\n\"kaid\",\n\"kaik\",\n\"kaikara\",\n\"kail\",\n\"kainga\",\n\"kainite\",\n\"kainsi\",\n\"kainyn\",\n\"kairine\",\n\"kaiser\",\n\"kaitaka\",\n\"kaiwi\",\n\"kajawah\",\n\"kaka\",\n\"kakapo\",\n\"kakar\",\n\"kaki\",\n\"kakkak\",\n\"kakke\",\n\"kala\",\n\"kalasie\",\n\"kale\",\n\"kalema\",\n\"kalends\",\n\"kali\",\n\"kalian\",\n\"kalium\",\n\"kallah\",\n\"kallege\",\n\"kalo\",\n\"kalon\",\n\"kalong\",\n\"kalpis\",\n\"kamahi\",\n\"kamala\",\n\"kamansi\",\n\"kamao\",\n\"kamas\",\n\"kamassi\",\n\"kambal\",\n\"kamboh\",\n\"kame\",\n\"kamerad\",\n\"kamias\",\n\"kamichi\",\n\"kamik\",\n\"kampong\",\n\"kan\",\n\"kana\",\n\"kanae\",\n\"kanagi\",\n\"kanap\",\n\"kanara\",\n\"kanari\",\n\"kanat\",\n\"kanchil\",\n\"kande\",\n\"kandol\",\n\"kaneh\",\n\"kang\",\n\"kanga\",\n\"kangani\",\n\"kankie\",\n\"kannume\",\n\"kanoon\",\n\"kans\",\n\"kantele\",\n\"kanten\",\n\"kaolin\",\n\"kapa\",\n\"kapai\",\n\"kapeika\",\n\"kapok\",\n\"kapp\",\n\"kappa\",\n\"kappe\",\n\"kapur\",\n\"kaput\",\n\"karagan\",\n\"karaka\",\n\"karakul\",\n\"karamu\",\n\"karaoke\",\n\"karate\",\n\"karaya\",\n\"karbi\",\n\"karch\",\n\"kareao\",\n\"kareeta\",\n\"karela\",\n\"karite\",\n\"karma\",\n\"karmic\",\n\"karo\",\n\"kaross\",\n\"karou\",\n\"karree\",\n\"karri\",\n\"karroo\",\n\"karsha\",\n\"karst\",\n\"karstic\",\n\"kartel\",\n\"kartos\",\n\"karwar\",\n\"karyon\",\n\"kasa\",\n\"kasbah\",\n\"kasbeke\",\n\"kasher\",\n\"kashga\",\n\"kashi\",\n\"kashima\",\n\"kasida\",\n\"kasm\",\n\"kassu\",\n\"kastura\",\n\"kat\",\n\"katar\",\n\"katcina\",\n\"kath\",\n\"katha\",\n\"kathal\",\n\"katipo\",\n\"katmon\",\n\"katogle\",\n\"katsup\",\n\"katuka\",\n\"katun\",\n\"katurai\",\n\"katydid\",\n\"kauri\",\n\"kava\",\n\"kavaic\",\n\"kavass\",\n\"kawaka\",\n\"kawika\",\n\"kay\",\n\"kayak\",\n\"kayaker\",\n\"kayles\",\n\"kayo\",\n\"kazi\",\n\"kazoo\",\n\"kea\",\n\"keach\",\n\"keacorn\",\n\"keawe\",\n\"keb\",\n\"kebab\",\n\"kebbie\",\n\"kebbuck\",\n\"kechel\",\n\"keck\",\n\"keckle\",\n\"kecksy\",\n\"kecky\",\n\"ked\",\n\"keddah\",\n\"kedge\",\n\"kedger\",\n\"kedlock\",\n\"keech\",\n\"keek\",\n\"keeker\",\n\"keel\",\n\"keelage\",\n\"keeled\",\n\"keeler\",\n\"keelfat\",\n\"keelie\",\n\"keeling\",\n\"keelman\",\n\"keelson\",\n\"keen\",\n\"keena\",\n\"keened\",\n\"keener\",\n\"keenly\",\n\"keep\",\n\"keeper\",\n\"keeping\",\n\"keest\",\n\"keet\",\n\"keeve\",\n\"kef\",\n\"keffel\",\n\"kefir\",\n\"kefiric\",\n\"keg\",\n\"kegler\",\n\"kehaya\",\n\"keita\",\n\"keitloa\",\n\"kekuna\",\n\"kelchin\",\n\"keld\",\n\"kele\",\n\"kelebe\",\n\"keleh\",\n\"kelek\",\n\"kelep\",\n\"kelk\",\n\"kell\",\n\"kella\",\n\"kellion\",\n\"kelly\",\n\"keloid\",\n\"kelp\",\n\"kelper\",\n\"kelpie\",\n\"kelpy\",\n\"kelt\",\n\"kelter\",\n\"kelty\",\n\"kelvin\",\n\"kemb\",\n\"kemp\",\n\"kempite\",\n\"kemple\",\n\"kempt\",\n\"kempy\",\n\"ken\",\n\"kenaf\",\n\"kenareh\",\n\"kench\",\n\"kend\",\n\"kendir\",\n\"kendyr\",\n\"kenlore\",\n\"kenmark\",\n\"kennel\",\n\"kenner\",\n\"kenning\",\n\"kenno\",\n\"keno\",\n\"kenosis\",\n\"kenotic\",\n\"kenspac\",\n\"kent\",\n\"kenyte\",\n\"kep\",\n\"kepi\",\n\"kept\",\n\"kerana\",\n\"kerasin\",\n\"kerat\",\n\"keratin\",\n\"keratto\",\n\"kerchoo\",\n\"kerchug\",\n\"kerel\",\n\"kerf\",\n\"kerflap\",\n\"kerflop\",\n\"kermes\",\n\"kermis\",\n\"kern\",\n\"kernel\",\n\"kerner\",\n\"kernish\",\n\"kernite\",\n\"kernos\",\n\"kerogen\",\n\"kerrie\",\n\"kerril\",\n\"kerrite\",\n\"kerry\",\n\"kersey\",\n\"kerslam\",\n\"kerugma\",\n\"kerwham\",\n\"kerygma\",\n\"kestrel\",\n\"ket\",\n\"keta\",\n\"ketal\",\n\"ketch\",\n\"ketchup\",\n\"keten\",\n\"ketene\",\n\"ketipic\",\n\"keto\",\n\"ketogen\",\n\"ketol\",\n\"ketole\",\n\"ketone\",\n\"ketonic\",\n\"ketose\",\n\"ketosis\",\n\"kette\",\n\"ketting\",\n\"kettle\",\n\"kettler\",\n\"ketty\",\n\"ketuba\",\n\"ketupa\",\n\"ketyl\",\n\"keup\",\n\"kevalin\",\n\"kevel\",\n\"kewpie\",\n\"kex\",\n\"kexy\",\n\"key\",\n\"keyage\",\n\"keyed\",\n\"keyhole\",\n\"keyless\",\n\"keylet\",\n\"keylock\",\n\"keynote\",\n\"keyway\",\n\"khaddar\",\n\"khadi\",\n\"khahoon\",\n\"khaiki\",\n\"khair\",\n\"khaja\",\n\"khajur\",\n\"khaki\",\n\"khakied\",\n\"khalifa\",\n\"khalsa\",\n\"khamsin\",\n\"khan\",\n\"khanate\",\n\"khanda\",\n\"khanjar\",\n\"khanjee\",\n\"khankah\",\n\"khanum\",\n\"khar\",\n\"kharaj\",\n\"kharua\",\n\"khass\",\n\"khat\",\n\"khatib\",\n\"khatri\",\n\"khediva\",\n\"khedive\",\n\"khepesh\",\n\"khet\",\n\"khilat\",\n\"khir\",\n\"khirka\",\n\"khoja\",\n\"khoka\",\n\"khot\",\n\"khu\",\n\"khubber\",\n\"khula\",\n\"khutbah\",\n\"khvat\",\n\"kiack\",\n\"kiaki\",\n\"kialee\",\n\"kiang\",\n\"kiaugh\",\n\"kibber\",\n\"kibble\",\n\"kibbler\",\n\"kibe\",\n\"kibei\",\n\"kibitka\",\n\"kibitz\",\n\"kiblah\",\n\"kibosh\",\n\"kiby\",\n\"kick\",\n\"kickee\",\n\"kicker\",\n\"kicking\",\n\"kickish\",\n\"kickoff\",\n\"kickout\",\n\"kickup\",\n\"kidder\",\n\"kiddier\",\n\"kiddish\",\n\"kiddush\",\n\"kiddy\",\n\"kidhood\",\n\"kidlet\",\n\"kidling\",\n\"kidnap\",\n\"kidney\",\n\"kidskin\",\n\"kidsman\",\n\"kiekie\",\n\"kiel\",\n\"kier\",\n\"kieye\",\n\"kikar\",\n\"kike\",\n\"kiki\",\n\"kiku\",\n\"kikuel\",\n\"kikumon\",\n\"kil\",\n\"kiladja\",\n\"kilah\",\n\"kilan\",\n\"kildee\",\n\"kileh\",\n\"kilerg\",\n\"kiley\",\n\"kilhig\",\n\"kiliare\",\n\"kilim\",\n\"kill\",\n\"killas\",\n\"killcu\",\n\"killeen\",\n\"killer\",\n\"killick\",\n\"killing\",\n\"killy\",\n\"kiln\",\n\"kilneye\",\n\"kilnman\",\n\"kilnrib\",\n\"kilo\",\n\"kilobar\",\n\"kiloton\",\n\"kilovar\",\n\"kilp\",\n\"kilt\",\n\"kilter\",\n\"kiltie\",\n\"kilting\",\n\"kim\",\n\"kimbang\",\n\"kimnel\",\n\"kimono\",\n\"kin\",\n\"kina\",\n\"kinah\",\n\"kinase\",\n\"kinbote\",\n\"kinch\",\n\"kinchin\",\n\"kincob\",\n\"kind\",\n\"kindle\",\n\"kindler\",\n\"kindly\",\n\"kindred\",\n\"kinepox\",\n\"kinesic\",\n\"kinesis\",\n\"kinetic\",\n\"king\",\n\"kingcob\",\n\"kingcup\",\n\"kingdom\",\n\"kinglet\",\n\"kingly\",\n\"kingpin\",\n\"kingrow\",\n\"kink\",\n\"kinkhab\",\n\"kinkily\",\n\"kinkle\",\n\"kinkled\",\n\"kinkly\",\n\"kinky\",\n\"kinless\",\n\"kino\",\n\"kinship\",\n\"kinsman\",\n\"kintar\",\n\"kioea\",\n\"kiosk\",\n\"kiotome\",\n\"kip\",\n\"kipage\",\n\"kipe\",\n\"kippeen\",\n\"kipper\",\n\"kippy\",\n\"kipsey\",\n\"kipskin\",\n\"kiri\",\n\"kirimon\",\n\"kirk\",\n\"kirker\",\n\"kirkify\",\n\"kirking\",\n\"kirkman\",\n\"kirmew\",\n\"kirn\",\n\"kirombo\",\n\"kirsch\",\n\"kirtle\",\n\"kirtled\",\n\"kirve\",\n\"kirver\",\n\"kischen\",\n\"kish\",\n\"kishen\",\n\"kishon\",\n\"kishy\",\n\"kismet\",\n\"kisra\",\n\"kiss\",\n\"kissage\",\n\"kissar\",\n\"kisser\",\n\"kissing\",\n\"kissy\",\n\"kist\",\n\"kistful\",\n\"kiswa\",\n\"kit\",\n\"kitab\",\n\"kitabis\",\n\"kitar\",\n\"kitcat\",\n\"kitchen\",\n\"kite\",\n\"kith\",\n\"kithe\",\n\"kitish\",\n\"kitling\",\n\"kittel\",\n\"kitten\",\n\"kitter\",\n\"kittle\",\n\"kittles\",\n\"kittly\",\n\"kittock\",\n\"kittul\",\n\"kitty\",\n\"kiva\",\n\"kiver\",\n\"kivu\",\n\"kiwi\",\n\"kiyas\",\n\"kiyi\",\n\"klafter\",\n\"klam\",\n\"klavern\",\n\"klaxon\",\n\"klepht\",\n\"kleptic\",\n\"klicket\",\n\"klip\",\n\"klipbok\",\n\"klipdas\",\n\"klippe\",\n\"klippen\",\n\"klister\",\n\"klom\",\n\"klop\",\n\"klops\",\n\"klosh\",\n\"kmet\",\n\"knab\",\n\"knabble\",\n\"knack\",\n\"knacker\",\n\"knacky\",\n\"knag\",\n\"knagged\",\n\"knaggy\",\n\"knap\",\n\"knape\",\n\"knappan\",\n\"knapper\",\n\"knar\",\n\"knark\",\n\"knarred\",\n\"knarry\",\n\"knave\",\n\"knavery\",\n\"knavess\",\n\"knavish\",\n\"knawel\",\n\"knead\",\n\"kneader\",\n\"knee\",\n\"kneecap\",\n\"kneed\",\n\"kneel\",\n\"kneeler\",\n\"kneelet\",\n\"kneepad\",\n\"kneepan\",\n\"knell\",\n\"knelt\",\n\"knet\",\n\"knew\",\n\"knez\",\n\"knezi\",\n\"kniaz\",\n\"kniazi\",\n\"knick\",\n\"knicker\",\n\"knife\",\n\"knifer\",\n\"knight\",\n\"knit\",\n\"knitch\",\n\"knitted\",\n\"knitter\",\n\"knittle\",\n\"knived\",\n\"knivey\",\n\"knob\",\n\"knobbed\",\n\"knobber\",\n\"knobble\",\n\"knobbly\",\n\"knobby\",\n\"knock\",\n\"knocker\",\n\"knockup\",\n\"knoll\",\n\"knoller\",\n\"knolly\",\n\"knop\",\n\"knopite\",\n\"knopped\",\n\"knopper\",\n\"knoppy\",\n\"knosp\",\n\"knosped\",\n\"knot\",\n\"knotted\",\n\"knotter\",\n\"knotty\",\n\"knout\",\n\"know\",\n\"knowe\",\n\"knower\",\n\"knowing\",\n\"known\",\n\"knub\",\n\"knubbly\",\n\"knubby\",\n\"knublet\",\n\"knuckle\",\n\"knuckly\",\n\"knur\",\n\"knurl\",\n\"knurled\",\n\"knurly\",\n\"knut\",\n\"knutty\",\n\"knyaz\",\n\"knyazi\",\n\"ko\",\n\"koa\",\n\"koae\",\n\"koala\",\n\"koali\",\n\"kob\",\n\"koban\",\n\"kobi\",\n\"kobird\",\n\"kobold\",\n\"kobong\",\n\"kobu\",\n\"koda\",\n\"kodak\",\n\"kodaker\",\n\"kodakry\",\n\"kodro\",\n\"koel\",\n\"koff\",\n\"koft\",\n\"koftgar\",\n\"kohemp\",\n\"kohl\",\n\"kohua\",\n\"koi\",\n\"koil\",\n\"koila\",\n\"koilon\",\n\"koine\",\n\"koinon\",\n\"kojang\",\n\"kokako\",\n\"kokam\",\n\"kokan\",\n\"kokil\",\n\"kokio\",\n\"koklas\",\n\"koklass\",\n\"koko\",\n\"kokoon\",\n\"kokowai\",\n\"kokra\",\n\"koku\",\n\"kokum\",\n\"kokumin\",\n\"kola\",\n\"kolach\",\n\"kolea\",\n\"kolhoz\",\n\"kolkhos\",\n\"kolkhoz\",\n\"kollast\",\n\"koller\",\n\"kolo\",\n\"kolobus\",\n\"kolsun\",\n\"komatik\",\n\"kombu\",\n\"kommos\",\n\"kompeni\",\n\"kon\",\n\"kona\",\n\"konak\",\n\"kongoni\",\n\"kongu\",\n\"konini\",\n\"konjak\",\n\"kooka\",\n\"kookery\",\n\"kookri\",\n\"koolah\",\n\"koombar\",\n\"koomkie\",\n\"kootcha\",\n\"kop\",\n\"kopeck\",\n\"koph\",\n\"kopi\",\n\"koppa\",\n\"koppen\",\n\"koppite\",\n\"kor\",\n\"kora\",\n\"koradji\",\n\"korait\",\n\"korakan\",\n\"korari\",\n\"kore\",\n\"korec\",\n\"koreci\",\n\"korero\",\n\"kori\",\n\"korin\",\n\"korona\",\n\"korova\",\n\"korrel\",\n\"koruna\",\n\"korzec\",\n\"kos\",\n\"kosher\",\n\"kosin\",\n\"kosong\",\n\"koswite\",\n\"kotal\",\n\"koto\",\n\"kotuku\",\n\"kotwal\",\n\"kotyle\",\n\"kotylos\",\n\"kou\",\n\"koulan\",\n\"kouza\",\n\"kovil\",\n\"kowhai\",\n\"kowtow\",\n\"koyan\",\n\"kozo\",\n\"kra\",\n\"kraal\",\n\"kraft\",\n\"krait\",\n\"kraken\",\n\"kral\",\n\"krama\",\n\"kran\",\n\"kras\",\n\"krasis\",\n\"krausen\",\n\"kraut\",\n\"kreis\",\n\"krelos\",\n\"kremlin\",\n\"krems\",\n\"kreng\",\n\"krieker\",\n\"krimmer\",\n\"krina\",\n\"krocket\",\n\"krome\",\n\"krona\",\n\"krone\",\n\"kronen\",\n\"kroner\",\n\"kronor\",\n\"kronur\",\n\"kroon\",\n\"krosa\",\n\"krypsis\",\n\"kryptic\",\n\"kryptol\",\n\"krypton\",\n\"kuan\",\n\"kuba\",\n\"kubba\",\n\"kuchen\",\n\"kudize\",\n\"kudos\",\n\"kudu\",\n\"kudzu\",\n\"kuei\",\n\"kuge\",\n\"kugel\",\n\"kuichua\",\n\"kukri\",\n\"kuku\",\n\"kukui\",\n\"kukupa\",\n\"kula\",\n\"kulack\",\n\"kulah\",\n\"kulaite\",\n\"kulak\",\n\"kulang\",\n\"kulimit\",\n\"kulm\",\n\"kulmet\",\n\"kumbi\",\n\"kumhar\",\n\"kumiss\",\n\"kummel\",\n\"kumquat\",\n\"kumrah\",\n\"kunai\",\n\"kung\",\n\"kunk\",\n\"kunkur\",\n\"kunzite\",\n\"kuphar\",\n\"kupper\",\n\"kurbash\",\n\"kurgan\",\n\"kuruma\",\n\"kurung\",\n\"kurus\",\n\"kurvey\",\n\"kusa\",\n\"kusam\",\n\"kusha\",\n\"kuskite\",\n\"kuskos\",\n\"kuskus\",\n\"kusti\",\n\"kusum\",\n\"kutcha\",\n\"kuttab\",\n\"kuttar\",\n\"kuttaur\",\n\"kuvasz\",\n\"kvass\",\n\"kvint\",\n\"kvinter\",\n\"kwamme\",\n\"kwan\",\n\"kwarta\",\n\"kwazoku\",\n\"kyack\",\n\"kyah\",\n\"kyar\",\n\"kyat\",\n\"kyaung\",\n\"kyl\",\n\"kyle\",\n\"kylite\",\n\"kylix\",\n\"kyrine\",\n\"kyte\",\n\"l\",\n\"la\",\n\"laager\",\n\"laang\",\n\"lab\",\n\"labara\",\n\"labarum\",\n\"labba\",\n\"labber\",\n\"labefy\",\n\"label\",\n\"labeler\",\n\"labella\",\n\"labia\",\n\"labial\",\n\"labiate\",\n\"labile\",\n\"labiose\",\n\"labis\",\n\"labium\",\n\"lablab\",\n\"labor\",\n\"labored\",\n\"laborer\",\n\"labour\",\n\"labra\",\n\"labral\",\n\"labret\",\n\"labroid\",\n\"labrose\",\n\"labrum\",\n\"labrys\",\n\"lac\",\n\"lacca\",\n\"laccaic\",\n\"laccase\",\n\"laccol\",\n\"lace\",\n\"laced\",\n\"laceman\",\n\"lacepod\",\n\"lacer\",\n\"lacery\",\n\"lacet\",\n\"lache\",\n\"laches\",\n\"lachsa\",\n\"lacily\",\n\"lacing\",\n\"lacinia\",\n\"lacis\",\n\"lack\",\n\"lacker\",\n\"lackey\",\n\"lackwit\",\n\"lacmoid\",\n\"lacmus\",\n\"laconic\",\n\"lacquer\",\n\"lacrym\",\n\"lactam\",\n\"lactant\",\n\"lactary\",\n\"lactase\",\n\"lactate\",\n\"lacteal\",\n\"lactean\",\n\"lactic\",\n\"lactid\",\n\"lactide\",\n\"lactify\",\n\"lactim\",\n\"lacto\",\n\"lactoid\",\n\"lactol\",\n\"lactone\",\n\"lactose\",\n\"lactyl\",\n\"lacuna\",\n\"lacunae\",\n\"lacunal\",\n\"lacunar\",\n\"lacune\",\n\"lacwork\",\n\"lacy\",\n\"lad\",\n\"ladakin\",\n\"ladanum\",\n\"ladder\",\n\"laddery\",\n\"laddess\",\n\"laddie\",\n\"laddish\",\n\"laddock\",\n\"lade\",\n\"lademan\",\n\"laden\",\n\"lader\",\n\"ladhood\",\n\"ladies\",\n\"ladify\",\n\"lading\",\n\"ladkin\",\n\"ladle\",\n\"ladler\",\n\"ladrone\",\n\"lady\",\n\"ladybug\",\n\"ladydom\",\n\"ladyfly\",\n\"ladyfy\",\n\"ladyish\",\n\"ladyism\",\n\"ladykin\",\n\"ladyly\",\n\"laet\",\n\"laeti\",\n\"laetic\",\n\"lag\",\n\"lagan\",\n\"lagarto\",\n\"lagen\",\n\"lagena\",\n\"lagend\",\n\"lager\",\n\"lagetto\",\n\"laggar\",\n\"laggard\",\n\"lagged\",\n\"laggen\",\n\"lagger\",\n\"laggin\",\n\"lagging\",\n\"laglast\",\n\"lagna\",\n\"lagoon\",\n\"lagwort\",\n\"lai\",\n\"laic\",\n\"laical\",\n\"laich\",\n\"laicism\",\n\"laicity\",\n\"laicize\",\n\"laid\",\n\"laigh\",\n\"lain\",\n\"laine\",\n\"laiose\",\n\"lair\",\n\"lairage\",\n\"laird\",\n\"lairdie\",\n\"lairdly\",\n\"lairman\",\n\"lairy\",\n\"laity\",\n\"lak\",\n\"lakatoi\",\n\"lake\",\n\"lakelet\",\n\"laker\",\n\"lakie\",\n\"laking\",\n\"lakish\",\n\"lakism\",\n\"lakist\",\n\"laky\",\n\"lalang\",\n\"lall\",\n\"lalling\",\n\"lalo\",\n\"lam\",\n\"lama\",\n\"lamaic\",\n\"lamany\",\n\"lamb\",\n\"lamba\",\n\"lambale\",\n\"lambda\",\n\"lambeau\",\n\"lambent\",\n\"lamber\",\n\"lambert\",\n\"lambie\",\n\"lambish\",\n\"lambkin\",\n\"lambly\",\n\"lamboys\",\n\"lamby\",\n\"lame\",\n\"lamedh\",\n\"lamel\",\n\"lamella\",\n\"lamely\",\n\"lament\",\n\"lameter\",\n\"lametta\",\n\"lamia\",\n\"lamiger\",\n\"lamiid\",\n\"lamin\",\n\"lamina\",\n\"laminae\",\n\"laminar\",\n\"lamish\",\n\"lamiter\",\n\"lammas\",\n\"lammer\",\n\"lammock\",\n\"lammy\",\n\"lamnid\",\n\"lamnoid\",\n\"lamp\",\n\"lampad\",\n\"lampas\",\n\"lamper\",\n\"lampern\",\n\"lampers\",\n\"lampfly\",\n\"lampful\",\n\"lamping\",\n\"lampion\",\n\"lampist\",\n\"lamplet\",\n\"lamplit\",\n\"lampman\",\n\"lampoon\",\n\"lamprey\",\n\"lan\",\n\"lanas\",\n\"lanate\",\n\"lanated\",\n\"lanaz\",\n\"lance\",\n\"lanced\",\n\"lancely\",\n\"lancer\",\n\"lances\",\n\"lancet\",\n\"lancha\",\n\"land\",\n\"landau\",\n\"landed\",\n\"lander\",\n\"landing\",\n\"landman\",\n\"landmil\",\n\"lane\",\n\"lanete\",\n\"laneway\",\n\"laney\",\n\"langaha\",\n\"langca\",\n\"langi\",\n\"langite\",\n\"langle\",\n\"langoon\",\n\"langsat\",\n\"langued\",\n\"languet\",\n\"languid\",\n\"languor\",\n\"langur\",\n\"laniary\",\n\"laniate\",\n\"lanific\",\n\"lanioid\",\n\"lanista\",\n\"lank\",\n\"lanket\",\n\"lankily\",\n\"lankish\",\n\"lankly\",\n\"lanky\",\n\"lanner\",\n\"lanolin\",\n\"lanose\",\n\"lansat\",\n\"lanseh\",\n\"lanson\",\n\"lant\",\n\"lantaca\",\n\"lantern\",\n\"lantum\",\n\"lanugo\",\n\"lanum\",\n\"lanx\",\n\"lanyard\",\n\"lap\",\n\"lapacho\",\n\"lapcock\",\n\"lapel\",\n\"lapeler\",\n\"lapful\",\n\"lapillo\",\n\"lapon\",\n\"lappage\",\n\"lapped\",\n\"lapper\",\n\"lappet\",\n\"lapping\",\n\"lapse\",\n\"lapsed\",\n\"lapser\",\n\"lapsi\",\n\"lapsing\",\n\"lapwing\",\n\"lapwork\",\n\"laquear\",\n\"laqueus\",\n\"lar\",\n\"larceny\",\n\"larch\",\n\"larchen\",\n\"lard\",\n\"larder\",\n\"lardite\",\n\"lardon\",\n\"lardy\",\n\"large\",\n\"largely\",\n\"largen\",\n\"largess\",\n\"largish\",\n\"largo\",\n\"lari\",\n\"lariat\",\n\"larick\",\n\"larid\",\n\"larigo\",\n\"larigot\",\n\"lariid\",\n\"larin\",\n\"larine\",\n\"larixin\",\n\"lark\",\n\"larker\",\n\"larking\",\n\"larkish\",\n\"larky\",\n\"larmier\",\n\"larnax\",\n\"laroid\",\n\"larrup\",\n\"larry\",\n\"larva\",\n\"larvae\",\n\"larval\",\n\"larvate\",\n\"larve\",\n\"larvule\",\n\"larynx\",\n\"las\",\n\"lasa\",\n\"lascar\",\n\"laser\",\n\"lash\",\n\"lasher\",\n\"lask\",\n\"lasket\",\n\"lasque\",\n\"lass\",\n\"lasset\",\n\"lassie\",\n\"lasso\",\n\"lassock\",\n\"lassoer\",\n\"last\",\n\"lastage\",\n\"laster\",\n\"lasting\",\n\"lastly\",\n\"lastre\",\n\"lasty\",\n\"lat\",\n\"lata\",\n\"latah\",\n\"latch\",\n\"latcher\",\n\"latchet\",\n\"late\",\n\"latebra\",\n\"lated\",\n\"lateen\",\n\"lately\",\n\"laten\",\n\"latence\",\n\"latency\",\n\"latent\",\n\"later\",\n\"latera\",\n\"laterad\",\n\"lateral\",\n\"latest\",\n\"latex\",\n\"lath\",\n\"lathe\",\n\"lathee\",\n\"lathen\",\n\"lather\",\n\"lathery\",\n\"lathing\",\n\"lathy\",\n\"latices\",\n\"latigo\",\n\"lation\",\n\"latish\",\n\"latitat\",\n\"latite\",\n\"latomy\",\n\"latrant\",\n\"latria\",\n\"latrine\",\n\"latro\",\n\"latrobe\",\n\"latron\",\n\"latten\",\n\"latter\",\n\"lattice\",\n\"latus\",\n\"lauan\",\n\"laud\",\n\"lauder\",\n\"laudist\",\n\"laugh\",\n\"laughee\",\n\"laugher\",\n\"laughy\",\n\"lauia\",\n\"laun\",\n\"launce\",\n\"launch\",\n\"laund\",\n\"launder\",\n\"laundry\",\n\"laur\",\n\"laura\",\n\"laurate\",\n\"laurel\",\n\"lauric\",\n\"laurin\",\n\"laurite\",\n\"laurone\",\n\"lauryl\",\n\"lava\",\n\"lavable\",\n\"lavabo\",\n\"lavacre\",\n\"lavage\",\n\"lavanga\",\n\"lavant\",\n\"lavaret\",\n\"lavatic\",\n\"lave\",\n\"laveer\",\n\"laver\",\n\"lavic\",\n\"lavish\",\n\"lavolta\",\n\"law\",\n\"lawbook\",\n\"lawful\",\n\"lawing\",\n\"lawish\",\n\"lawk\",\n\"lawless\",\n\"lawlike\",\n\"lawman\",\n\"lawn\",\n\"lawned\",\n\"lawner\",\n\"lawnlet\",\n\"lawny\",\n\"lawsuit\",\n\"lawter\",\n\"lawyer\",\n\"lawyery\",\n\"lawzy\",\n\"lax\",\n\"laxate\",\n\"laxism\",\n\"laxist\",\n\"laxity\",\n\"laxly\",\n\"laxness\",\n\"lay\",\n\"layaway\",\n\"layback\",\n\"layboy\",\n\"layer\",\n\"layered\",\n\"layery\",\n\"layette\",\n\"laying\",\n\"layland\",\n\"layman\",\n\"layne\",\n\"layoff\",\n\"layout\",\n\"layover\",\n\"layship\",\n\"laystow\",\n\"lazar\",\n\"lazaret\",\n\"lazarly\",\n\"laze\",\n\"lazily\",\n\"lazule\",\n\"lazuli\",\n\"lazy\",\n\"lazyish\",\n\"lea\",\n\"leach\",\n\"leacher\",\n\"leachy\",\n\"lead\",\n\"leadage\",\n\"leaded\",\n\"leaden\",\n\"leader\",\n\"leadin\",\n\"leading\",\n\"leadman\",\n\"leadoff\",\n\"leadout\",\n\"leadway\",\n\"leady\",\n\"leaf\",\n\"leafage\",\n\"leafboy\",\n\"leafcup\",\n\"leafdom\",\n\"leafed\",\n\"leafen\",\n\"leafer\",\n\"leafery\",\n\"leafit\",\n\"leaflet\",\n\"leafy\",\n\"league\",\n\"leaguer\",\n\"leak\",\n\"leakage\",\n\"leaker\",\n\"leaky\",\n\"leal\",\n\"lealand\",\n\"leally\",\n\"lealty\",\n\"leam\",\n\"leamer\",\n\"lean\",\n\"leaner\",\n\"leaning\",\n\"leanish\",\n\"leanly\",\n\"leant\",\n\"leap\",\n\"leaper\",\n\"leaping\",\n\"leapt\",\n\"lear\",\n\"learn\",\n\"learned\",\n\"learner\",\n\"learnt\",\n\"lease\",\n\"leaser\",\n\"leash\",\n\"leasing\",\n\"leasow\",\n\"least\",\n\"leat\",\n\"leath\",\n\"leather\",\n\"leatman\",\n\"leave\",\n\"leaved\",\n\"leaven\",\n\"leaver\",\n\"leaves\",\n\"leaving\",\n\"leavy\",\n\"leawill\",\n\"leban\",\n\"lebbek\",\n\"lecama\",\n\"lech\",\n\"lecher\",\n\"lechery\",\n\"lechwe\",\n\"leck\",\n\"lecker\",\n\"lectern\",\n\"lection\",\n\"lector\",\n\"lectual\",\n\"lecture\",\n\"lecyth\",\n\"led\",\n\"lede\",\n\"leden\",\n\"ledge\",\n\"ledged\",\n\"ledger\",\n\"ledging\",\n\"ledgy\",\n\"ledol\",\n\"lee\",\n\"leech\",\n\"leecher\",\n\"leeches\",\n\"leed\",\n\"leefang\",\n\"leek\",\n\"leekish\",\n\"leeky\",\n\"leep\",\n\"leepit\",\n\"leer\",\n\"leerily\",\n\"leerish\",\n\"leery\",\n\"lees\",\n\"leet\",\n\"leetman\",\n\"leewan\",\n\"leeward\",\n\"leeway\",\n\"leewill\",\n\"left\",\n\"leftish\",\n\"leftism\",\n\"leftist\",\n\"leg\",\n\"legacy\",\n\"legal\",\n\"legally\",\n\"legate\",\n\"legatee\",\n\"legato\",\n\"legator\",\n\"legend\",\n\"legenda\",\n\"leger\",\n\"leges\",\n\"legged\",\n\"legger\",\n\"legging\",\n\"leggy\",\n\"leghorn\",\n\"legible\",\n\"legibly\",\n\"legific\",\n\"legion\",\n\"legist\",\n\"legit\",\n\"legitim\",\n\"leglen\",\n\"legless\",\n\"leglet\",\n\"leglike\",\n\"legman\",\n\"legoa\",\n\"legpull\",\n\"legrope\",\n\"legua\",\n\"leguan\",\n\"legume\",\n\"legumen\",\n\"legumin\",\n\"lehr\",\n\"lehrman\",\n\"lehua\",\n\"lei\",\n\"leister\",\n\"leisure\",\n\"lek\",\n\"lekach\",\n\"lekane\",\n\"lekha\",\n\"leman\",\n\"lemel\",\n\"lemma\",\n\"lemmata\",\n\"lemming\",\n\"lemnad\",\n\"lemon\",\n\"lemony\",\n\"lempira\",\n\"lemur\",\n\"lemures\",\n\"lemurid\",\n\"lenad\",\n\"lenard\",\n\"lench\",\n\"lend\",\n\"lendee\",\n\"lender\",\n\"lene\",\n\"length\",\n\"lengthy\",\n\"lenient\",\n\"lenify\",\n\"lenis\",\n\"lenitic\",\n\"lenity\",\n\"lennow\",\n\"leno\",\n\"lens\",\n\"lensed\",\n\"lent\",\n\"lenth\",\n\"lentigo\",\n\"lentil\",\n\"lentisc\",\n\"lentisk\",\n\"lento\",\n\"lentoid\",\n\"lentor\",\n\"lentous\",\n\"lenvoi\",\n\"lenvoy\",\n\"leonine\",\n\"leonite\",\n\"leopard\",\n\"leotard\",\n\"lepa\",\n\"leper\",\n\"lepered\",\n\"leporid\",\n\"lepra\",\n\"lepric\",\n\"leproid\",\n\"leproma\",\n\"leprose\",\n\"leprosy\",\n\"leprous\",\n\"leptid\",\n\"leptite\",\n\"leptome\",\n\"lepton\",\n\"leptus\",\n\"lerot\",\n\"lerp\",\n\"lerret\",\n\"lesche\",\n\"lesion\",\n\"lesiy\",\n\"less\",\n\"lessee\",\n\"lessen\",\n\"lesser\",\n\"lessive\",\n\"lessn\",\n\"lesson\",\n\"lessor\",\n\"lest\",\n\"lestrad\",\n\"let\",\n\"letch\",\n\"letchy\",\n\"letdown\",\n\"lete\",\n\"lethal\",\n\"letoff\",\n\"letten\",\n\"letter\",\n\"lettrin\",\n\"lettuce\",\n\"letup\",\n\"leu\",\n\"leuch\",\n\"leucine\",\n\"leucism\",\n\"leucite\",\n\"leuco\",\n\"leucoid\",\n\"leucoma\",\n\"leucon\",\n\"leucous\",\n\"leucyl\",\n\"leud\",\n\"leuk\",\n\"leuma\",\n\"lev\",\n\"levance\",\n\"levant\",\n\"levator\",\n\"levee\",\n\"level\",\n\"leveler\",\n\"levelly\",\n\"lever\",\n\"leverer\",\n\"leveret\",\n\"levers\",\n\"levier\",\n\"levin\",\n\"levir\",\n\"levity\",\n\"levo\",\n\"levulic\",\n\"levulin\",\n\"levy\",\n\"levyist\",\n\"lew\",\n\"lewd\",\n\"lewdly\",\n\"lewis\",\n\"lewth\",\n\"lexia\",\n\"lexical\",\n\"lexicon\",\n\"ley\",\n\"leyland\",\n\"leysing\",\n\"li\",\n\"liable\",\n\"liaison\",\n\"liana\",\n\"liang\",\n\"liar\",\n\"liard\",\n\"libant\",\n\"libate\",\n\"libber\",\n\"libbet\",\n\"libbra\",\n\"libel\",\n\"libelee\",\n\"libeler\",\n\"liber\",\n\"liberal\",\n\"liberty\",\n\"libido\",\n\"libken\",\n\"libra\",\n\"libral\",\n\"library\",\n\"librate\",\n\"licca\",\n\"license\",\n\"lich\",\n\"licham\",\n\"lichen\",\n\"licheny\",\n\"lichi\",\n\"licit\",\n\"licitly\",\n\"lick\",\n\"licker\",\n\"licking\",\n\"licorn\",\n\"licorne\",\n\"lictor\",\n\"lid\",\n\"lidded\",\n\"lidder\",\n\"lidgate\",\n\"lidless\",\n\"lie\",\n\"lied\",\n\"lief\",\n\"liege\",\n\"liegely\",\n\"lieger\",\n\"lien\",\n\"lienal\",\n\"lienee\",\n\"lienic\",\n\"lienor\",\n\"lier\",\n\"lierne\",\n\"lierre\",\n\"liesh\",\n\"lieu\",\n\"lieue\",\n\"lieve\",\n\"life\",\n\"lifeday\",\n\"lifeful\",\n\"lifelet\",\n\"lifer\",\n\"lifey\",\n\"lifo\",\n\"lift\",\n\"lifter\",\n\"lifting\",\n\"liftman\",\n\"ligable\",\n\"ligas\",\n\"ligate\",\n\"ligator\",\n\"ligger\",\n\"light\",\n\"lighten\",\n\"lighter\",\n\"lightly\",\n\"ligne\",\n\"lignify\",\n\"lignin\",\n\"lignite\",\n\"lignone\",\n\"lignose\",\n\"lignum\",\n\"ligula\",\n\"ligular\",\n\"ligule\",\n\"ligulin\",\n\"ligure\",\n\"liin\",\n\"lija\",\n\"likable\",\n\"like\",\n\"likely\",\n\"liken\",\n\"liker\",\n\"likin\",\n\"liking\",\n\"liknon\",\n\"lilac\",\n\"lilacin\",\n\"lilacky\",\n\"lile\",\n\"lilied\",\n\"lill\",\n\"lilt\",\n\"lily\",\n\"lilyfy\",\n\"lim\",\n\"limacel\",\n\"limacon\",\n\"liman\",\n\"limb\",\n\"limbal\",\n\"limbat\",\n\"limbate\",\n\"limbeck\",\n\"limbed\",\n\"limber\",\n\"limbers\",\n\"limbic\",\n\"limbie\",\n\"limbo\",\n\"limbous\",\n\"limbus\",\n\"limby\",\n\"lime\",\n\"limeade\",\n\"limeman\",\n\"limen\",\n\"limer\",\n\"limes\",\n\"limetta\",\n\"limey\",\n\"liminal\",\n\"liming\",\n\"limit\",\n\"limital\",\n\"limited\",\n\"limiter\",\n\"limma\",\n\"limmer\",\n\"limmock\",\n\"limmu\",\n\"limn\",\n\"limner\",\n\"limnery\",\n\"limniad\",\n\"limnite\",\n\"limoid\",\n\"limonin\",\n\"limose\",\n\"limous\",\n\"limp\",\n\"limper\",\n\"limpet\",\n\"limpid\",\n\"limpily\",\n\"limpin\",\n\"limping\",\n\"limpish\",\n\"limpkin\",\n\"limply\",\n\"limpsy\",\n\"limpy\",\n\"limsy\",\n\"limu\",\n\"limulid\",\n\"limy\",\n\"lin\",\n\"lina\",\n\"linable\",\n\"linaga\",\n\"linage\",\n\"linaloa\",\n\"linalol\",\n\"linch\",\n\"linchet\",\n\"linctus\",\n\"lindane\",\n\"linden\",\n\"linder\",\n\"lindo\",\n\"line\",\n\"linea\",\n\"lineage\",\n\"lineal\",\n\"linear\",\n\"lineate\",\n\"linecut\",\n\"lined\",\n\"linelet\",\n\"lineman\",\n\"linen\",\n\"liner\",\n\"ling\",\n\"linga\",\n\"linge\",\n\"lingel\",\n\"linger\",\n\"lingo\",\n\"lingtow\",\n\"lingua\",\n\"lingual\",\n\"linguet\",\n\"lingula\",\n\"lingy\",\n\"linha\",\n\"linhay\",\n\"linie\",\n\"linin\",\n\"lining\",\n\"linitis\",\n\"liniya\",\n\"linja\",\n\"linje\",\n\"link\",\n\"linkage\",\n\"linkboy\",\n\"linked\",\n\"linker\",\n\"linking\",\n\"linkman\",\n\"links\",\n\"linky\",\n\"linn\",\n\"linnet\",\n\"lino\",\n\"linolic\",\n\"linolin\",\n\"linon\",\n\"linous\",\n\"linoxin\",\n\"linoxyn\",\n\"linpin\",\n\"linseed\",\n\"linsey\",\n\"lint\",\n\"lintel\",\n\"linten\",\n\"linter\",\n\"lintern\",\n\"lintie\",\n\"linty\",\n\"linwood\",\n\"liny\",\n\"lion\",\n\"lioncel\",\n\"lionel\",\n\"lioness\",\n\"lionet\",\n\"lionism\",\n\"lionize\",\n\"lionly\",\n\"lip\",\n\"lipa\",\n\"liparid\",\n\"lipase\",\n\"lipemia\",\n\"lipide\",\n\"lipin\",\n\"lipless\",\n\"liplet\",\n\"liplike\",\n\"lipoid\",\n\"lipoma\",\n\"lipopod\",\n\"liposis\",\n\"lipped\",\n\"lippen\",\n\"lipper\",\n\"lipping\",\n\"lippy\",\n\"lipuria\",\n\"lipwork\",\n\"liquate\",\n\"liquefy\",\n\"liqueur\",\n\"liquid\",\n\"liquidy\",\n\"liquor\",\n\"lira\",\n\"lirate\",\n\"lire\",\n\"lirella\",\n\"lis\",\n\"lisere\",\n\"lish\",\n\"lisk\",\n\"lisle\",\n\"lisp\",\n\"lisper\",\n\"lispund\",\n\"liss\",\n\"lissom\",\n\"lissome\",\n\"list\",\n\"listed\",\n\"listel\",\n\"listen\",\n\"lister\",\n\"listing\",\n\"listred\",\n\"lit\",\n\"litany\",\n\"litas\",\n\"litch\",\n\"litchi\",\n\"lite\",\n\"liter\",\n\"literal\",\n\"lith\",\n\"lithe\",\n\"lithely\",\n\"lithi\",\n\"lithia\",\n\"lithic\",\n\"lithify\",\n\"lithite\",\n\"lithium\",\n\"litho\",\n\"lithoid\",\n\"lithous\",\n\"lithy\",\n\"litmus\",\n\"litotes\",\n\"litra\",\n\"litster\",\n\"litten\",\n\"litter\",\n\"littery\",\n\"little\",\n\"lituite\",\n\"liturgy\",\n\"litus\",\n\"lituus\",\n\"litz\",\n\"livable\",\n\"live\",\n\"lived\",\n\"livedo\",\n\"lively\",\n\"liven\",\n\"liver\",\n\"livered\",\n\"livery\",\n\"livid\",\n\"lividly\",\n\"livier\",\n\"living\",\n\"livor\",\n\"livre\",\n\"liwan\",\n\"lixive\",\n\"lizard\",\n\"llama\",\n\"llano\",\n\"llautu\",\n\"llyn\",\n\"lo\",\n\"loa\",\n\"loach\",\n\"load\",\n\"loadage\",\n\"loaded\",\n\"loaden\",\n\"loader\",\n\"loading\",\n\"loaf\",\n\"loafer\",\n\"loafing\",\n\"loaflet\",\n\"loam\",\n\"loamily\",\n\"loaming\",\n\"loamy\",\n\"loan\",\n\"loaner\",\n\"loanin\",\n\"loath\",\n\"loathe\",\n\"loather\",\n\"loathly\",\n\"loave\",\n\"lob\",\n\"lobal\",\n\"lobar\",\n\"lobate\",\n\"lobated\",\n\"lobber\",\n\"lobbish\",\n\"lobby\",\n\"lobbyer\",\n\"lobcock\",\n\"lobe\",\n\"lobed\",\n\"lobelet\",\n\"lobelin\",\n\"lobfig\",\n\"lobing\",\n\"lobiped\",\n\"lobo\",\n\"lobola\",\n\"lobose\",\n\"lobster\",\n\"lobtail\",\n\"lobular\",\n\"lobule\",\n\"lobworm\",\n\"loca\",\n\"locable\",\n\"local\",\n\"locale\",\n\"locally\",\n\"locanda\",\n\"locate\",\n\"locator\",\n\"loch\",\n\"lochage\",\n\"lochan\",\n\"lochia\",\n\"lochial\",\n\"lochus\",\n\"lochy\",\n\"loci\",\n\"lock\",\n\"lockage\",\n\"lockbox\",\n\"locked\",\n\"locker\",\n\"locket\",\n\"lockful\",\n\"locking\",\n\"lockjaw\",\n\"locklet\",\n\"lockman\",\n\"lockout\",\n\"lockpin\",\n\"lockram\",\n\"lockup\",\n\"locky\",\n\"loco\",\n\"locoism\",\n\"locular\",\n\"locule\",\n\"loculus\",\n\"locum\",\n\"locus\",\n\"locust\",\n\"locusta\",\n\"locutor\",\n\"lod\",\n\"lode\",\n\"lodge\",\n\"lodged\",\n\"lodger\",\n\"lodging\",\n\"loess\",\n\"loessal\",\n\"loessic\",\n\"lof\",\n\"loft\",\n\"lofter\",\n\"loftily\",\n\"lofting\",\n\"loftman\",\n\"lofty\",\n\"log\",\n\"loganin\",\n\"logbook\",\n\"logcock\",\n\"loge\",\n\"logeion\",\n\"logeum\",\n\"loggat\",\n\"logged\",\n\"logger\",\n\"loggia\",\n\"loggin\",\n\"logging\",\n\"loggish\",\n\"loghead\",\n\"logia\",\n\"logic\",\n\"logical\",\n\"logie\",\n\"login\",\n\"logion\",\n\"logium\",\n\"loglet\",\n\"loglike\",\n\"logman\",\n\"logoi\",\n\"logos\",\n\"logroll\",\n\"logway\",\n\"logwise\",\n\"logwood\",\n\"logwork\",\n\"logy\",\n\"lohan\",\n\"lohoch\",\n\"loimic\",\n\"loin\",\n\"loined\",\n\"loir\",\n\"loiter\",\n\"loka\",\n\"lokao\",\n\"lokaose\",\n\"loke\",\n\"loket\",\n\"lokiec\",\n\"loll\",\n\"loller\",\n\"lollop\",\n\"lollopy\",\n\"lolly\",\n\"loma\",\n\"lombard\",\n\"lomboy\",\n\"loment\",\n\"lomita\",\n\"lommock\",\n\"lone\",\n\"lonely\",\n\"long\",\n\"longa\",\n\"longan\",\n\"longbow\",\n\"longe\",\n\"longear\",\n\"longer\",\n\"longfin\",\n\"longful\",\n\"longing\",\n\"longish\",\n\"longjaw\",\n\"longly\",\n\"longs\",\n\"longue\",\n\"longway\",\n\"lontar\",\n\"loo\",\n\"looby\",\n\"lood\",\n\"loof\",\n\"loofah\",\n\"loofie\",\n\"look\",\n\"looker\",\n\"looking\",\n\"lookout\",\n\"lookum\",\n\"loom\",\n\"loomer\",\n\"loomery\",\n\"looming\",\n\"loon\",\n\"loonery\",\n\"looney\",\n\"loony\",\n\"loop\",\n\"looper\",\n\"loopful\",\n\"looping\",\n\"loopist\",\n\"looplet\",\n\"loopy\",\n\"loose\",\n\"loosely\",\n\"loosen\",\n\"looser\",\n\"loosing\",\n\"loosish\",\n\"loot\",\n\"looten\",\n\"looter\",\n\"lootie\",\n\"lop\",\n\"lope\",\n\"loper\",\n\"lophiid\",\n\"lophine\",\n\"loppard\",\n\"lopper\",\n\"loppet\",\n\"lopping\",\n\"loppy\",\n\"lopseed\",\n\"loquat\",\n\"loquent\",\n\"lora\",\n\"loral\",\n\"loran\",\n\"lorate\",\n\"lorcha\",\n\"lord\",\n\"lording\",\n\"lordkin\",\n\"lordlet\",\n\"lordly\",\n\"lordy\",\n\"lore\",\n\"loreal\",\n\"lored\",\n\"lori\",\n\"loric\",\n\"lorica\",\n\"lorilet\",\n\"lorimer\",\n\"loriot\",\n\"loris\",\n\"lormery\",\n\"lorn\",\n\"loro\",\n\"lorry\",\n\"lors\",\n\"lorum\",\n\"lory\",\n\"losable\",\n\"lose\",\n\"losel\",\n\"loser\",\n\"losh\",\n\"losing\",\n\"loss\",\n\"lost\",\n\"lot\",\n\"lota\",\n\"lotase\",\n\"lote\",\n\"lotic\",\n\"lotion\",\n\"lotment\",\n\"lotrite\",\n\"lots\",\n\"lotter\",\n\"lottery\",\n\"lotto\",\n\"lotus\",\n\"lotusin\",\n\"louch\",\n\"loud\",\n\"louden\",\n\"loudish\",\n\"loudly\",\n\"louey\",\n\"lough\",\n\"louk\",\n\"loukoum\",\n\"loulu\",\n\"lounder\",\n\"lounge\",\n\"lounger\",\n\"loungy\",\n\"loup\",\n\"loupe\",\n\"lour\",\n\"lourdy\",\n\"louse\",\n\"lousily\",\n\"louster\",\n\"lousy\",\n\"lout\",\n\"louter\",\n\"louther\",\n\"loutish\",\n\"louty\",\n\"louvar\",\n\"louver\",\n\"lovable\",\n\"lovably\",\n\"lovage\",\n\"love\",\n\"loveful\",\n\"lovely\",\n\"loveman\",\n\"lover\",\n\"lovered\",\n\"loverly\",\n\"loving\",\n\"low\",\n\"lowa\",\n\"lowan\",\n\"lowbell\",\n\"lowborn\",\n\"lowboy\",\n\"lowbred\",\n\"lowdah\",\n\"lowder\",\n\"loweite\",\n\"lower\",\n\"lowerer\",\n\"lowery\",\n\"lowish\",\n\"lowland\",\n\"lowlily\",\n\"lowly\",\n\"lowmen\",\n\"lowmost\",\n\"lown\",\n\"lowness\",\n\"lownly\",\n\"lowth\",\n\"lowwood\",\n\"lowy\",\n\"lox\",\n\"loxia\",\n\"loxic\",\n\"loxotic\",\n\"loy\",\n\"loyal\",\n\"loyally\",\n\"loyalty\",\n\"lozenge\",\n\"lozengy\",\n\"lubber\",\n\"lube\",\n\"lubra\",\n\"lubric\",\n\"lubrify\",\n\"lucanid\",\n\"lucarne\",\n\"lucban\",\n\"luce\",\n\"lucence\",\n\"lucency\",\n\"lucent\",\n\"lucern\",\n\"lucerne\",\n\"lucet\",\n\"lucible\",\n\"lucid\",\n\"lucida\",\n\"lucidly\",\n\"lucifee\",\n\"lucific\",\n\"lucigen\",\n\"lucivee\",\n\"luck\",\n\"lucken\",\n\"luckful\",\n\"luckie\",\n\"luckily\",\n\"lucky\",\n\"lucre\",\n\"lucrify\",\n\"lucule\",\n\"lucumia\",\n\"lucy\",\n\"ludden\",\n\"ludibry\",\n\"ludo\",\n\"lue\",\n\"lues\",\n\"luetic\",\n\"lufbery\",\n\"luff\",\n\"lug\",\n\"luge\",\n\"luger\",\n\"luggage\",\n\"luggar\",\n\"lugged\",\n\"lugger\",\n\"luggie\",\n\"lugmark\",\n\"lugsail\",\n\"lugsome\",\n\"lugworm\",\n\"luhinga\",\n\"luigino\",\n\"luke\",\n\"lukely\",\n\"lulab\",\n\"lull\",\n\"lullaby\",\n\"luller\",\n\"lulu\",\n\"lum\",\n\"lumbago\",\n\"lumbang\",\n\"lumbar\",\n\"lumber\",\n\"lumen\",\n\"luminal\",\n\"lumine\",\n\"lummox\",\n\"lummy\",\n\"lump\",\n\"lumper\",\n\"lumpet\",\n\"lumpily\",\n\"lumping\",\n\"lumpish\",\n\"lumpkin\",\n\"lumpman\",\n\"lumpy\",\n\"luna\",\n\"lunacy\",\n\"lunar\",\n\"lunare\",\n\"lunary\",\n\"lunate\",\n\"lunatic\",\n\"lunatum\",\n\"lunch\",\n\"luncher\",\n\"lune\",\n\"lunes\",\n\"lunette\",\n\"lung\",\n\"lunge\",\n\"lunged\",\n\"lunger\",\n\"lungful\",\n\"lungi\",\n\"lungie\",\n\"lungis\",\n\"lungy\",\n\"lunn\",\n\"lunoid\",\n\"lunt\",\n\"lunula\",\n\"lunular\",\n\"lunule\",\n\"lunulet\",\n\"lupe\",\n\"lupeol\",\n\"lupeose\",\n\"lupine\",\n\"lupinin\",\n\"lupis\",\n\"lupoid\",\n\"lupous\",\n\"lupulic\",\n\"lupulin\",\n\"lupulus\",\n\"lupus\",\n\"lura\",\n\"lural\",\n\"lurch\",\n\"lurcher\",\n\"lurdan\",\n\"lure\",\n\"lureful\",\n\"lurer\",\n\"lurg\",\n\"lurid\",\n\"luridly\",\n\"lurk\",\n\"lurker\",\n\"lurky\",\n\"lurrier\",\n\"lurry\",\n\"lush\",\n\"lusher\",\n\"lushly\",\n\"lushy\",\n\"lusk\",\n\"lusky\",\n\"lusory\",\n\"lust\",\n\"luster\",\n\"lustful\",\n\"lustily\",\n\"lustra\",\n\"lustral\",\n\"lustrum\",\n\"lusty\",\n\"lut\",\n\"lutany\",\n\"lute\",\n\"luteal\",\n\"lutecia\",\n\"lutein\",\n\"lutelet\",\n\"luteo\",\n\"luteoma\",\n\"luteous\",\n\"luter\",\n\"luteway\",\n\"lutfisk\",\n\"luthern\",\n\"luthier\",\n\"luting\",\n\"lutist\",\n\"lutose\",\n\"lutrin\",\n\"lutrine\",\n\"lux\",\n\"luxate\",\n\"luxe\",\n\"luxury\",\n\"luxus\",\n\"ly\",\n\"lyam\",\n\"lyard\",\n\"lyceal\",\n\"lyceum\",\n\"lycid\",\n\"lycopin\",\n\"lycopod\",\n\"lycosid\",\n\"lyctid\",\n\"lyddite\",\n\"lydite\",\n\"lye\",\n\"lyery\",\n\"lygaeid\",\n\"lying\",\n\"lyingly\",\n\"lymph\",\n\"lymphad\",\n\"lymphy\",\n\"lyncean\",\n\"lynch\",\n\"lyncher\",\n\"lyncine\",\n\"lynx\",\n\"lyra\",\n\"lyrate\",\n\"lyrated\",\n\"lyraway\",\n\"lyre\",\n\"lyreman\",\n\"lyric\",\n\"lyrical\",\n\"lyrism\",\n\"lyrist\",\n\"lys\",\n\"lysate\",\n\"lyse\",\n\"lysin\",\n\"lysine\",\n\"lysis\",\n\"lysogen\",\n\"lyssa\",\n\"lyssic\",\n\"lytic\",\n\"lytta\",\n\"lyxose\",\n\"m\",\n\"ma\",\n\"maam\",\n\"mabi\",\n\"mabolo\",\n\"mac\",\n\"macabre\",\n\"macaco\",\n\"macadam\",\n\"macan\",\n\"macana\",\n\"macao\",\n\"macaque\",\n\"macaw\",\n\"macco\",\n\"mace\",\n\"maceman\",\n\"macer\",\n\"machan\",\n\"machar\",\n\"machete\",\n\"machi\",\n\"machila\",\n\"machin\",\n\"machine\",\n\"machree\",\n\"macies\",\n\"mack\",\n\"mackins\",\n\"mackle\",\n\"macle\",\n\"macled\",\n\"maco\",\n\"macrame\",\n\"macro\",\n\"macron\",\n\"macuca\",\n\"macula\",\n\"macular\",\n\"macule\",\n\"macuta\",\n\"mad\",\n\"madam\",\n\"madame\",\n\"madcap\",\n\"madden\",\n\"madder\",\n\"madding\",\n\"maddish\",\n\"maddle\",\n\"made\",\n\"madefy\",\n\"madhuca\",\n\"madid\",\n\"madling\",\n\"madly\",\n\"madman\",\n\"madnep\",\n\"madness\",\n\"mado\",\n\"madoqua\",\n\"madrier\",\n\"madrona\",\n\"madship\",\n\"maduro\",\n\"madweed\",\n\"madwort\",\n\"mae\",\n\"maenad\",\n\"maestri\",\n\"maestro\",\n\"maffia\",\n\"maffick\",\n\"maffle\",\n\"mafflin\",\n\"mafic\",\n\"mafoo\",\n\"mafura\",\n\"mag\",\n\"magadis\",\n\"magani\",\n\"magas\",\n\"mage\",\n\"magenta\",\n\"magged\",\n\"maggle\",\n\"maggot\",\n\"maggoty\",\n\"magi\",\n\"magic\",\n\"magical\",\n\"magiric\",\n\"magma\",\n\"magnate\",\n\"magnes\",\n\"magnet\",\n\"magneta\",\n\"magneto\",\n\"magnify\",\n\"magnum\",\n\"magot\",\n\"magpie\",\n\"magpied\",\n\"magsman\",\n\"maguari\",\n\"maguey\",\n\"maha\",\n\"mahaleb\",\n\"mahalla\",\n\"mahant\",\n\"mahar\",\n\"maharao\",\n\"mahatma\",\n\"mahmal\",\n\"mahmudi\",\n\"mahoe\",\n\"maholi\",\n\"mahone\",\n\"mahout\",\n\"mahseer\",\n\"mahua\",\n\"mahuang\",\n\"maid\",\n\"maidan\",\n\"maiden\",\n\"maidish\",\n\"maidism\",\n\"maidkin\",\n\"maidy\",\n\"maiefic\",\n\"maigre\",\n\"maiid\",\n\"mail\",\n\"mailbag\",\n\"mailbox\",\n\"mailed\",\n\"mailer\",\n\"mailie\",\n\"mailman\",\n\"maim\",\n\"maimed\",\n\"maimer\",\n\"maimon\",\n\"main\",\n\"mainly\",\n\"mainour\",\n\"mainpin\",\n\"mains\",\n\"maint\",\n\"maintop\",\n\"maioid\",\n\"maire\",\n\"maize\",\n\"maizer\",\n\"majagua\",\n\"majesty\",\n\"majo\",\n\"majoon\",\n\"major\",\n\"makable\",\n\"make\",\n\"makedom\",\n\"maker\",\n\"makhzan\",\n\"maki\",\n\"making\",\n\"makluk\",\n\"mako\",\n\"makuk\",\n\"mal\",\n\"mala\",\n\"malacia\",\n\"malacon\",\n\"malady\",\n\"malagma\",\n\"malaise\",\n\"malakin\",\n\"malambo\",\n\"malanga\",\n\"malapi\",\n\"malar\",\n\"malaria\",\n\"malarin\",\n\"malate\",\n\"malati\",\n\"malax\",\n\"malduck\",\n\"male\",\n\"malease\",\n\"maleate\",\n\"maleic\",\n\"malella\",\n\"maleo\",\n\"malfed\",\n\"mali\",\n\"malic\",\n\"malice\",\n\"malicho\",\n\"malign\",\n\"malik\",\n\"maline\",\n\"malines\",\n\"malism\",\n\"malison\",\n\"malist\",\n\"malkin\",\n\"mall\",\n\"mallard\",\n\"malleal\",\n\"mallear\",\n\"mallee\",\n\"mallein\",\n\"mallet\",\n\"malleus\",\n\"mallow\",\n\"mallum\",\n\"mallus\",\n\"malm\",\n\"malmsey\",\n\"malmy\",\n\"malo\",\n\"malodor\",\n\"malonic\",\n\"malonyl\",\n\"malouah\",\n\"malpais\",\n\"malt\",\n\"maltase\",\n\"malter\",\n\"maltha\",\n\"malting\",\n\"maltman\",\n\"maltose\",\n\"malty\",\n\"mamba\",\n\"mambo\",\n\"mamma\",\n\"mammal\",\n\"mammary\",\n\"mammate\",\n\"mammee\",\n\"mammer\",\n\"mammock\",\n\"mammon\",\n\"mammoth\",\n\"mammula\",\n\"mammy\",\n\"mamo\",\n\"man\",\n\"mana\",\n\"manacle\",\n\"manage\",\n\"managee\",\n\"manager\",\n\"manaism\",\n\"manakin\",\n\"manal\",\n\"manas\",\n\"manatee\",\n\"manavel\",\n\"manbird\",\n\"manbot\",\n\"manche\",\n\"manchet\",\n\"mancono\",\n\"mancus\",\n\"mand\",\n\"mandala\",\n\"mandant\",\n\"mandate\",\n\"mandil\",\n\"mandola\",\n\"mandom\",\n\"mandora\",\n\"mandore\",\n\"mandra\",\n\"mandrel\",\n\"mandrin\",\n\"mandua\",\n\"mandyas\",\n\"mane\",\n\"maned\",\n\"manege\",\n\"manei\",\n\"manent\",\n\"manes\",\n\"maness\",\n\"maney\",\n\"manful\",\n\"mang\",\n\"manga\",\n\"mangal\",\n\"mange\",\n\"mangeao\",\n\"mangel\",\n\"manger\",\n\"mangi\",\n\"mangily\",\n\"mangle\",\n\"mangler\",\n\"mango\",\n\"mangona\",\n\"mangue\",\n\"mangy\",\n\"manhead\",\n\"manhole\",\n\"manhood\",\n\"mani\",\n\"mania\",\n\"maniac\",\n\"manic\",\n\"manid\",\n\"manify\",\n\"manikin\",\n\"manila\",\n\"manilla\",\n\"manille\",\n\"manioc\",\n\"maniple\",\n\"manism\",\n\"manist\",\n\"manito\",\n\"maniu\",\n\"manjak\",\n\"mank\",\n\"mankin\",\n\"mankind\",\n\"manless\",\n\"manlet\",\n\"manlike\",\n\"manlily\",\n\"manling\",\n\"manly\",\n\"manna\",\n\"mannan\",\n\"manner\",\n\"manners\",\n\"manness\",\n\"mannide\",\n\"mannie\",\n\"mannify\",\n\"manning\",\n\"mannish\",\n\"mannite\",\n\"mannose\",\n\"manny\",\n\"mano\",\n\"manoc\",\n\"manomin\",\n\"manor\",\n\"manque\",\n\"manred\",\n\"manrent\",\n\"manroot\",\n\"manrope\",\n\"mansard\",\n\"manse\",\n\"manship\",\n\"mansion\",\n\"manso\",\n\"mant\",\n\"manta\",\n\"mantal\",\n\"manteau\",\n\"mantel\",\n\"manter\",\n\"mantes\",\n\"mantic\",\n\"mantid\",\n\"mantis\",\n\"mantle\",\n\"mantled\",\n\"mantlet\",\n\"manto\",\n\"mantoid\",\n\"mantra\",\n\"mantrap\",\n\"mantua\",\n\"manual\",\n\"manuao\",\n\"manuka\",\n\"manul\",\n\"manuma\",\n\"manumea\",\n\"manumit\",\n\"manure\",\n\"manurer\",\n\"manus\",\n\"manward\",\n\"manway\",\n\"manweed\",\n\"manwise\",\n\"many\",\n\"manzana\",\n\"manzil\",\n\"mao\",\n\"maomao\",\n\"map\",\n\"mapach\",\n\"mapau\",\n\"mapland\",\n\"maple\",\n\"mapo\",\n\"mapper\",\n\"mappist\",\n\"mappy\",\n\"mapwise\",\n\"maqui\",\n\"maquis\",\n\"mar\",\n\"marabou\",\n\"maraca\",\n\"maracan\",\n\"marae\",\n\"maral\",\n\"marang\",\n\"marara\",\n\"mararie\",\n\"marasca\",\n\"maraud\",\n\"marble\",\n\"marbled\",\n\"marbler\",\n\"marbles\",\n\"marbly\",\n\"marc\",\n\"marcel\",\n\"march\",\n\"marcher\",\n\"marcid\",\n\"marco\",\n\"marconi\",\n\"marcor\",\n\"mardy\",\n\"mare\",\n\"maremma\",\n\"marengo\",\n\"marfire\",\n\"margay\",\n\"marge\",\n\"margent\",\n\"margin\",\n\"margosa\",\n\"marhala\",\n\"maria\",\n\"marid\",\n\"marimba\",\n\"marina\",\n\"marine\",\n\"mariner\",\n\"mariola\",\n\"maris\",\n\"marish\",\n\"marital\",\n\"mark\",\n\"marka\",\n\"marked\",\n\"marker\",\n\"market\",\n\"markhor\",\n\"marking\",\n\"markka\",\n\"markman\",\n\"markup\",\n\"marl\",\n\"marled\",\n\"marler\",\n\"marli\",\n\"marlin\",\n\"marline\",\n\"marlite\",\n\"marlock\",\n\"marlpit\",\n\"marly\",\n\"marm\",\n\"marmit\",\n\"marmite\",\n\"marmose\",\n\"marmot\",\n\"maro\",\n\"marok\",\n\"maroon\",\n\"marplot\",\n\"marque\",\n\"marquee\",\n\"marquis\",\n\"marrano\",\n\"marree\",\n\"marrer\",\n\"married\",\n\"marrier\",\n\"marron\",\n\"marrot\",\n\"marrow\",\n\"marrowy\",\n\"marry\",\n\"marryer\",\n\"marsh\",\n\"marshal\",\n\"marshy\",\n\"marsoon\",\n\"mart\",\n\"martel\",\n\"marten\",\n\"martext\",\n\"martial\",\n\"martin\",\n\"martite\",\n\"martlet\",\n\"martyr\",\n\"martyry\",\n\"maru\",\n\"marvel\",\n\"marver\",\n\"mary\",\n\"marybud\",\n\"mas\",\n\"masa\",\n\"mascara\",\n\"mascled\",\n\"mascot\",\n\"masculy\",\n\"masdeu\",\n\"mash\",\n\"masha\",\n\"mashal\",\n\"masher\",\n\"mashie\",\n\"mashing\",\n\"mashman\",\n\"mashru\",\n\"mashy\",\n\"masjid\",\n\"mask\",\n\"masked\",\n\"masker\",\n\"maskoid\",\n\"maslin\",\n\"mason\",\n\"masoned\",\n\"masoner\",\n\"masonic\",\n\"masonry\",\n\"masooka\",\n\"masoola\",\n\"masque\",\n\"masquer\",\n\"mass\",\n\"massa\",\n\"massage\",\n\"masse\",\n\"massel\",\n\"masser\",\n\"masseur\",\n\"massier\",\n\"massif\",\n\"massily\",\n\"massive\",\n\"massoy\",\n\"massula\",\n\"massy\",\n\"mast\",\n\"mastaba\",\n\"mastage\",\n\"mastax\",\n\"masted\",\n\"master\",\n\"mastery\",\n\"mastful\",\n\"mastic\",\n\"mastiff\",\n\"masting\",\n\"mastman\",\n\"mastoid\",\n\"masty\",\n\"masu\",\n\"mat\",\n\"mataco\",\n\"matador\",\n\"matai\",\n\"matalan\",\n\"matanza\",\n\"matapan\",\n\"matapi\",\n\"matara\",\n\"matax\",\n\"match\",\n\"matcher\",\n\"matchy\",\n\"mate\",\n\"mately\",\n\"mater\",\n\"matey\",\n\"math\",\n\"mathes\",\n\"matico\",\n\"matin\",\n\"matinal\",\n\"matinee\",\n\"mating\",\n\"matins\",\n\"matipo\",\n\"matka\",\n\"matless\",\n\"matlow\",\n\"matra\",\n\"matral\",\n\"matrass\",\n\"matreed\",\n\"matric\",\n\"matris\",\n\"matrix\",\n\"matron\",\n\"matross\",\n\"matsu\",\n\"matsuri\",\n\"matta\",\n\"mattaro\",\n\"matte\",\n\"matted\",\n\"matter\",\n\"mattery\",\n\"matti\",\n\"matting\",\n\"mattock\",\n\"mattoid\",\n\"mattoir\",\n\"mature\",\n\"maturer\",\n\"matweed\",\n\"maty\",\n\"matzo\",\n\"matzoon\",\n\"matzos\",\n\"matzoth\",\n\"mau\",\n\"maud\",\n\"maudle\",\n\"maudlin\",\n\"mauger\",\n\"maugh\",\n\"maul\",\n\"mauler\",\n\"mauley\",\n\"mauling\",\n\"maumet\",\n\"maun\",\n\"maund\",\n\"maunder\",\n\"maundy\",\n\"maunge\",\n\"mauther\",\n\"mauve\",\n\"mauvine\",\n\"maux\",\n\"mavis\",\n\"maw\",\n\"mawk\",\n\"mawkish\",\n\"mawky\",\n\"mawp\",\n\"maxilla\",\n\"maxim\",\n\"maxima\",\n\"maximal\",\n\"maximed\",\n\"maximum\",\n\"maximus\",\n\"maxixe\",\n\"maxwell\",\n\"may\",\n\"maya\",\n\"maybe\",\n\"maybush\",\n\"maycock\",\n\"mayday\",\n\"mayfish\",\n\"mayhap\",\n\"mayhem\",\n\"maynt\",\n\"mayor\",\n\"mayoral\",\n\"maypop\",\n\"maysin\",\n\"mayten\",\n\"mayweed\",\n\"maza\",\n\"mazame\",\n\"mazard\",\n\"maze\",\n\"mazed\",\n\"mazedly\",\n\"mazeful\",\n\"mazer\",\n\"mazic\",\n\"mazily\",\n\"mazuca\",\n\"mazuma\",\n\"mazurka\",\n\"mazut\",\n\"mazy\",\n\"mazzard\",\n\"mbalolo\",\n\"mbori\",\n\"me\",\n\"meable\",\n\"mead\",\n\"meader\",\n\"meadow\",\n\"meadowy\",\n\"meager\",\n\"meagre\",\n\"meak\",\n\"meal\",\n\"mealer\",\n\"mealies\",\n\"mealily\",\n\"mealman\",\n\"mealy\",\n\"mean\",\n\"meander\",\n\"meaned\",\n\"meaner\",\n\"meaning\",\n\"meanish\",\n\"meanly\",\n\"meant\",\n\"mease\",\n\"measle\",\n\"measled\",\n\"measles\",\n\"measly\",\n\"measure\",\n\"meat\",\n\"meatal\",\n\"meated\",\n\"meatily\",\n\"meatman\",\n\"meatus\",\n\"meaty\",\n\"mecate\",\n\"mecon\",\n\"meconic\",\n\"meconin\",\n\"medal\",\n\"medaled\",\n\"medalet\",\n\"meddle\",\n\"meddler\",\n\"media\",\n\"mediacy\",\n\"mediad\",\n\"medial\",\n\"median\",\n\"mediant\",\n\"mediate\",\n\"medic\",\n\"medical\",\n\"medico\",\n\"mediety\",\n\"medimn\",\n\"medimno\",\n\"medino\",\n\"medio\",\n\"medium\",\n\"medius\",\n\"medlar\",\n\"medley\",\n\"medrick\",\n\"medulla\",\n\"medusal\",\n\"medusan\",\n\"meebos\",\n\"meece\",\n\"meed\",\n\"meek\",\n\"meeken\",\n\"meekly\",\n\"meered\",\n\"meerkat\",\n\"meese\",\n\"meet\",\n\"meeten\",\n\"meeter\",\n\"meeting\",\n\"meetly\",\n\"megabar\",\n\"megaerg\",\n\"megafog\",\n\"megapod\",\n\"megaron\",\n\"megaton\",\n\"megerg\",\n\"megilp\",\n\"megmho\",\n\"megohm\",\n\"megrim\",\n\"mehalla\",\n\"mehari\",\n\"mehtar\",\n\"meile\",\n\"mein\",\n\"meinie\",\n\"meio\",\n\"meiobar\",\n\"meiosis\",\n\"meiotic\",\n\"meith\",\n\"mel\",\n\"mela\",\n\"melada\",\n\"melagra\",\n\"melam\",\n\"melamed\",\n\"melange\",\n\"melanic\",\n\"melanin\",\n\"melano\",\n\"melasma\",\n\"melch\",\n\"meld\",\n\"melder\",\n\"meldrop\",\n\"mele\",\n\"melee\",\n\"melena\",\n\"melene\",\n\"melenic\",\n\"melic\",\n\"melilot\",\n\"meline\",\n\"melisma\",\n\"melitis\",\n\"mell\",\n\"mellate\",\n\"mellay\",\n\"meller\",\n\"mellit\",\n\"mellite\",\n\"mellon\",\n\"mellow\",\n\"mellowy\",\n\"melodia\",\n\"melodic\",\n\"melody\",\n\"meloe\",\n\"meloid\",\n\"melon\",\n\"melonry\",\n\"melos\",\n\"melosa\",\n\"melt\",\n\"meltage\",\n\"melted\",\n\"melter\",\n\"melters\",\n\"melting\",\n\"melton\",\n\"mem\",\n\"member\",\n\"membral\",\n\"memento\",\n\"meminna\",\n\"memo\",\n\"memoir\",\n\"memoria\",\n\"memory\",\n\"men\",\n\"menace\",\n\"menacer\",\n\"menacme\",\n\"menage\",\n\"menald\",\n\"mend\",\n\"mendee\",\n\"mender\",\n\"mending\",\n\"mendole\",\n\"mends\",\n\"menfolk\",\n\"meng\",\n\"menhir\",\n\"menial\",\n\"meninx\",\n\"menkind\",\n\"mennom\",\n\"mensa\",\n\"mensal\",\n\"mense\",\n\"menses\",\n\"mensk\",\n\"mensual\",\n\"mental\",\n\"mentary\",\n\"menthol\",\n\"menthyl\",\n\"mention\",\n\"mentor\",\n\"mentum\",\n\"menu\",\n\"meny\",\n\"menyie\",\n\"menzie\",\n\"merbaby\",\n\"mercal\",\n\"mercer\",\n\"mercery\",\n\"merch\",\n\"merchet\",\n\"mercy\",\n\"mere\",\n\"merel\",\n\"merely\",\n\"merfold\",\n\"merfolk\",\n\"merge\",\n\"merger\",\n\"mergh\",\n\"meriah\",\n\"merice\",\n\"meril\",\n\"merism\",\n\"merist\",\n\"merit\",\n\"merited\",\n\"meriter\",\n\"merk\",\n\"merkhet\",\n\"merkin\",\n\"merl\",\n\"merle\",\n\"merlin\",\n\"merlon\",\n\"mermaid\",\n\"merman\",\n\"mero\",\n\"merop\",\n\"meropia\",\n\"meros\",\n\"merrily\",\n\"merrow\",\n\"merry\",\n\"merse\",\n\"mesa\",\n\"mesad\",\n\"mesail\",\n\"mesal\",\n\"mesally\",\n\"mesange\",\n\"mesarch\",\n\"mescal\",\n\"mese\",\n\"mesem\",\n\"mesenna\",\n\"mesh\",\n\"meshed\",\n\"meshy\",\n\"mesiad\",\n\"mesial\",\n\"mesian\",\n\"mesic\",\n\"mesilla\",\n\"mesion\",\n\"mesityl\",\n\"mesne\",\n\"meso\",\n\"mesobar\",\n\"mesode\",\n\"mesodic\",\n\"mesole\",\n\"meson\",\n\"mesonic\",\n\"mesopic\",\n\"mespil\",\n\"mess\",\n\"message\",\n\"messan\",\n\"messe\",\n\"messer\",\n\"messet\",\n\"messily\",\n\"messin\",\n\"messing\",\n\"messman\",\n\"messor\",\n\"messrs\",\n\"messtin\",\n\"messy\",\n\"mestee\",\n\"mester\",\n\"mestiza\",\n\"mestizo\",\n\"mestome\",\n\"met\",\n\"meta\",\n\"metad\",\n\"metage\",\n\"metal\",\n\"metaler\",\n\"metamer\",\n\"metanym\",\n\"metate\",\n\"metayer\",\n\"mete\",\n\"metel\",\n\"meteor\",\n\"meter\",\n\"methane\",\n\"methene\",\n\"mether\",\n\"methid\",\n\"methide\",\n\"methine\",\n\"method\",\n\"methyl\",\n\"metic\",\n\"metier\",\n\"metis\",\n\"metochy\",\n\"metonym\",\n\"metope\",\n\"metopic\",\n\"metopon\",\n\"metra\",\n\"metreta\",\n\"metrete\",\n\"metria\",\n\"metric\",\n\"metrics\",\n\"metrify\",\n\"metrist\",\n\"mettar\",\n\"mettle\",\n\"mettled\",\n\"metusia\",\n\"metze\",\n\"meuse\",\n\"meute\",\n\"mew\",\n\"meward\",\n\"mewer\",\n\"mewl\",\n\"mewler\",\n\"mezcal\",\n\"mezuzah\",\n\"mezzo\",\n\"mho\",\n\"mi\",\n\"miamia\",\n\"mian\",\n\"miaow\",\n\"miaower\",\n\"mias\",\n\"miasm\",\n\"miasma\",\n\"miasmal\",\n\"miasmic\",\n\"miaul\",\n\"miauler\",\n\"mib\",\n\"mica\",\n\"micate\",\n\"mice\",\n\"micelle\",\n\"miche\",\n\"micher\",\n\"miching\",\n\"micht\",\n\"mick\",\n\"mickle\",\n\"mico\",\n\"micrify\",\n\"micro\",\n\"microbe\",\n\"microhm\",\n\"micron\",\n\"miction\",\n\"mid\",\n\"midday\",\n\"midden\",\n\"middle\",\n\"middler\",\n\"middy\",\n\"mide\",\n\"midge\",\n\"midget\",\n\"midgety\",\n\"midgy\",\n\"midiron\",\n\"midland\",\n\"midleg\",\n\"midmain\",\n\"midmorn\",\n\"midmost\",\n\"midnoon\",\n\"midpit\",\n\"midrash\",\n\"midrib\",\n\"midriff\",\n\"mids\",\n\"midship\",\n\"midst\",\n\"midtap\",\n\"midvein\",\n\"midward\",\n\"midway\",\n\"midweek\",\n\"midwife\",\n\"midwise\",\n\"midyear\",\n\"mien\",\n\"miff\",\n\"miffy\",\n\"mig\",\n\"might\",\n\"mightnt\",\n\"mighty\",\n\"miglio\",\n\"mignon\",\n\"migrant\",\n\"migrate\",\n\"mihrab\",\n\"mijl\",\n\"mikado\",\n\"mike\",\n\"mikie\",\n\"mil\",\n\"mila\",\n\"milady\",\n\"milch\",\n\"milcher\",\n\"milchy\",\n\"mild\",\n\"milden\",\n\"milder\",\n\"mildew\",\n\"mildewy\",\n\"mildish\",\n\"mildly\",\n\"mile\",\n\"mileage\",\n\"miler\",\n\"mileway\",\n\"milfoil\",\n\"milha\",\n\"miliary\",\n\"milieu\",\n\"militia\",\n\"milium\",\n\"milk\",\n\"milken\",\n\"milker\",\n\"milkily\",\n\"milking\",\n\"milkman\",\n\"milksop\",\n\"milky\",\n\"mill\",\n\"milla\",\n\"millage\",\n\"milldam\",\n\"mille\",\n\"milled\",\n\"miller\",\n\"millet\",\n\"millful\",\n\"milliad\",\n\"millile\",\n\"milline\",\n\"milling\",\n\"million\",\n\"millman\",\n\"milner\",\n\"milo\",\n\"milord\",\n\"milpa\",\n\"milreis\",\n\"milsey\",\n\"milsie\",\n\"milt\",\n\"milter\",\n\"milty\",\n\"milvine\",\n\"mim\",\n\"mima\",\n\"mimbar\",\n\"mimble\",\n\"mime\",\n\"mimeo\",\n\"mimer\",\n\"mimesis\",\n\"mimetic\",\n\"mimic\",\n\"mimical\",\n\"mimicry\",\n\"mimine\",\n\"mimly\",\n\"mimmest\",\n\"mimmock\",\n\"mimmood\",\n\"mimmoud\",\n\"mimosis\",\n\"mimp\",\n\"mimsey\",\n\"min\",\n\"mina\",\n\"minable\",\n\"minar\",\n\"minaret\",\n\"minaway\",\n\"mince\",\n\"mincer\",\n\"mincing\",\n\"mind\",\n\"minded\",\n\"minder\",\n\"mindful\",\n\"minding\",\n\"mine\",\n\"miner\",\n\"mineral\",\n\"minery\",\n\"mines\",\n\"minette\",\n\"ming\",\n\"minge\",\n\"mingle\",\n\"mingler\",\n\"mingy\",\n\"minhag\",\n\"minhah\",\n\"miniate\",\n\"minibus\",\n\"minicam\",\n\"minify\",\n\"minikin\",\n\"minim\",\n\"minima\",\n\"minimal\",\n\"minimum\",\n\"minimus\",\n\"mining\",\n\"minion\",\n\"minish\",\n\"minium\",\n\"miniver\",\n\"minivet\",\n\"mink\",\n\"minkery\",\n\"minkish\",\n\"minnie\",\n\"minning\",\n\"minnow\",\n\"minny\",\n\"mino\",\n\"minoize\",\n\"minor\",\n\"minot\",\n\"minster\",\n\"mint\",\n\"mintage\",\n\"minter\",\n\"mintman\",\n\"minty\",\n\"minuend\",\n\"minuet\",\n\"minus\",\n\"minute\",\n\"minuter\",\n\"minutia\",\n\"minx\",\n\"minxish\",\n\"miny\",\n\"minyan\",\n\"miqra\",\n\"mir\",\n\"mirach\",\n\"miracle\",\n\"mirador\",\n\"mirage\",\n\"miragy\",\n\"mirate\",\n\"mirbane\",\n\"mird\",\n\"mirdaha\",\n\"mire\",\n\"mirid\",\n\"mirific\",\n\"mirish\",\n\"mirk\",\n\"miro\",\n\"mirror\",\n\"mirrory\",\n\"mirth\",\n\"miry\",\n\"mirza\",\n\"misact\",\n\"misadd\",\n\"misaim\",\n\"misally\",\n\"misbias\",\n\"misbill\",\n\"misbind\",\n\"misbode\",\n\"misborn\",\n\"misbusy\",\n\"miscall\",\n\"miscast\",\n\"mischio\",\n\"miscoin\",\n\"miscook\",\n\"miscrop\",\n\"miscue\",\n\"miscut\",\n\"misdate\",\n\"misdaub\",\n\"misdeal\",\n\"misdeed\",\n\"misdeem\",\n\"misdiet\",\n\"misdo\",\n\"misdoer\",\n\"misdraw\",\n\"mise\",\n\"misease\",\n\"misedit\",\n\"miser\",\n\"miserly\",\n\"misery\",\n\"misfare\",\n\"misfile\",\n\"misfire\",\n\"misfit\",\n\"misfond\",\n\"misform\",\n\"misgive\",\n\"misgo\",\n\"misgrow\",\n\"mishap\",\n\"mishmee\",\n\"misjoin\",\n\"miskeep\",\n\"misken\",\n\"miskill\",\n\"misknow\",\n\"misky\",\n\"mislay\",\n\"mislead\",\n\"mislear\",\n\"misled\",\n\"mislest\",\n\"mislike\",\n\"mislive\",\n\"mismade\",\n\"mismake\",\n\"mismate\",\n\"mismove\",\n\"misname\",\n\"misobey\",\n\"mispage\",\n\"mispart\",\n\"mispay\",\n\"mispick\",\n\"misplay\",\n\"misput\",\n\"misrate\",\n\"misread\",\n\"misrule\",\n\"miss\",\n\"missal\",\n\"missay\",\n\"misseem\",\n\"missel\",\n\"misset\",\n\"missile\",\n\"missing\",\n\"mission\",\n\"missis\",\n\"missish\",\n\"missive\",\n\"misstay\",\n\"misstep\",\n\"missy\",\n\"mist\",\n\"mistake\",\n\"mistbow\",\n\"misted\",\n\"mistell\",\n\"mistend\",\n\"mister\",\n\"misterm\",\n\"mistful\",\n\"mistic\",\n\"mistide\",\n\"mistify\",\n\"mistily\",\n\"mistime\",\n\"mistle\",\n\"mistone\",\n\"mistook\",\n\"mistral\",\n\"mistry\",\n\"misturn\",\n\"misty\",\n\"misura\",\n\"misuse\",\n\"misuser\",\n\"miswed\",\n\"miswish\",\n\"misword\",\n\"misyoke\",\n\"mite\",\n\"miter\",\n\"mitered\",\n\"miterer\",\n\"mitis\",\n\"mitome\",\n\"mitosis\",\n\"mitotic\",\n\"mitra\",\n\"mitral\",\n\"mitrate\",\n\"mitre\",\n\"mitrer\",\n\"mitt\",\n\"mitten\",\n\"mitty\",\n\"mity\",\n\"miurus\",\n\"mix\",\n\"mixable\",\n\"mixed\",\n\"mixedly\",\n\"mixen\",\n\"mixer\",\n\"mixhill\",\n\"mixible\",\n\"mixite\",\n\"mixtion\",\n\"mixture\",\n\"mixy\",\n\"mizmaze\",\n\"mizzen\",\n\"mizzle\",\n\"mizzler\",\n\"mizzly\",\n\"mizzy\",\n\"mneme\",\n\"mnemic\",\n\"mnesic\",\n\"mnestic\",\n\"mnioid\",\n\"mo\",\n\"moan\",\n\"moanful\",\n\"moaning\",\n\"moat\",\n\"mob\",\n\"mobable\",\n\"mobber\",\n\"mobbish\",\n\"mobbism\",\n\"mobbist\",\n\"mobby\",\n\"mobcap\",\n\"mobed\",\n\"mobile\",\n\"moble\",\n\"moblike\",\n\"mobship\",\n\"mobsman\",\n\"mobster\",\n\"mocha\",\n\"mochras\",\n\"mock\",\n\"mockado\",\n\"mocker\",\n\"mockery\",\n\"mockful\",\n\"mocmain\",\n\"mocuck\",\n\"modal\",\n\"modally\",\n\"mode\",\n\"model\",\n\"modeler\",\n\"modena\",\n\"modern\",\n\"modest\",\n\"modesty\",\n\"modicum\",\n\"modify\",\n\"modish\",\n\"modist\",\n\"modiste\",\n\"modius\",\n\"modular\",\n\"module\",\n\"modulo\",\n\"modulus\",\n\"moellon\",\n\"mofette\",\n\"moff\",\n\"mog\",\n\"mogador\",\n\"mogdad\",\n\"moggan\",\n\"moggy\",\n\"mogo\",\n\"moguey\",\n\"moha\",\n\"mohabat\",\n\"mohair\",\n\"mohar\",\n\"mohel\",\n\"moho\",\n\"mohr\",\n\"mohur\",\n\"moider\",\n\"moidore\",\n\"moieter\",\n\"moiety\",\n\"moil\",\n\"moiler\",\n\"moiles\",\n\"moiley\",\n\"moiling\",\n\"moineau\",\n\"moio\",\n\"moire\",\n\"moise\",\n\"moist\",\n\"moisten\",\n\"moistly\",\n\"moisty\",\n\"moit\",\n\"moity\",\n\"mojarra\",\n\"mojo\",\n\"moke\",\n\"moki\",\n\"moko\",\n\"moksha\",\n\"mokum\",\n\"moky\",\n\"mola\",\n\"molal\",\n\"molar\",\n\"molary\",\n\"molassy\",\n\"molave\",\n\"mold\",\n\"molder\",\n\"moldery\",\n\"molding\",\n\"moldy\",\n\"mole\",\n\"moleism\",\n\"moler\",\n\"molest\",\n\"molimen\",\n\"moline\",\n\"molka\",\n\"molland\",\n\"molle\",\n\"mollie\",\n\"mollify\",\n\"mollusk\",\n\"molly\",\n\"molman\",\n\"moloid\",\n\"moloker\",\n\"molompi\",\n\"molosse\",\n\"molpe\",\n\"molt\",\n\"molten\",\n\"molter\",\n\"moly\",\n\"mombin\",\n\"momble\",\n\"mome\",\n\"moment\",\n\"momenta\",\n\"momism\",\n\"momme\",\n\"mommet\",\n\"mommy\",\n\"momo\",\n\"mon\",\n\"mona\",\n\"monad\",\n\"monadic\",\n\"monaene\",\n\"monal\",\n\"monarch\",\n\"monas\",\n\"monase\",\n\"monaxon\",\n\"mone\",\n\"monel\",\n\"monepic\",\n\"moner\",\n\"moneral\",\n\"moneran\",\n\"moneric\",\n\"moneron\",\n\"monesia\",\n\"money\",\n\"moneyed\",\n\"moneyer\",\n\"mong\",\n\"monger\",\n\"mongery\",\n\"mongler\",\n\"mongrel\",\n\"mongst\",\n\"monial\",\n\"moniker\",\n\"monism\",\n\"monist\",\n\"monitor\",\n\"monk\",\n\"monkdom\",\n\"monkery\",\n\"monkess\",\n\"monkey\",\n\"monkish\",\n\"monkism\",\n\"monkly\",\n\"monny\",\n\"mono\",\n\"monoazo\",\n\"monocle\",\n\"monocot\",\n\"monodic\",\n\"monody\",\n\"monoid\",\n\"monomer\",\n\"mononch\",\n\"monont\",\n\"mononym\",\n\"monose\",\n\"monotic\",\n\"monsoon\",\n\"monster\",\n\"montage\",\n\"montana\",\n\"montane\",\n\"montant\",\n\"monte\",\n\"montem\",\n\"month\",\n\"monthly\",\n\"monthon\",\n\"montjoy\",\n\"monton\",\n\"monture\",\n\"moo\",\n\"mooch\",\n\"moocha\",\n\"moocher\",\n\"mood\",\n\"mooder\",\n\"moodily\",\n\"moodish\",\n\"moodle\",\n\"moody\",\n\"mooing\",\n\"mool\",\n\"moolet\",\n\"mools\",\n\"moolum\",\n\"moon\",\n\"moonack\",\n\"mooned\",\n\"mooner\",\n\"moonery\",\n\"mooneye\",\n\"moonily\",\n\"mooning\",\n\"moonish\",\n\"moonite\",\n\"moonja\",\n\"moonjah\",\n\"moonlet\",\n\"moonlit\",\n\"moonman\",\n\"moonset\",\n\"moonway\",\n\"moony\",\n\"moop\",\n\"moor\",\n\"moorage\",\n\"mooring\",\n\"moorish\",\n\"moorman\",\n\"moorn\",\n\"moorpan\",\n\"moors\",\n\"moorup\",\n\"moory\",\n\"moosa\",\n\"moose\",\n\"moosey\",\n\"moost\",\n\"moot\",\n\"mooter\",\n\"mooth\",\n\"mooting\",\n\"mootman\",\n\"mop\",\n\"mopane\",\n\"mope\",\n\"moper\",\n\"moph\",\n\"mophead\",\n\"moping\",\n\"mopish\",\n\"mopla\",\n\"mopper\",\n\"moppet\",\n\"moppy\",\n\"mopsy\",\n\"mopus\",\n\"mor\",\n\"mora\",\n\"moraine\",\n\"moral\",\n\"morale\",\n\"morally\",\n\"morals\",\n\"morass\",\n\"morassy\",\n\"morat\",\n\"morate\",\n\"moray\",\n\"morbid\",\n\"morbify\",\n\"mordant\",\n\"mordent\",\n\"mordore\",\n\"more\",\n\"moreen\",\n\"moreish\",\n\"morel\",\n\"morella\",\n\"morello\",\n\"mores\",\n\"morfrey\",\n\"morg\",\n\"morga\",\n\"morgan\",\n\"morgay\",\n\"morgen\",\n\"morglay\",\n\"morgue\",\n\"moric\",\n\"moriche\",\n\"morin\",\n\"morinel\",\n\"morion\",\n\"morkin\",\n\"morlop\",\n\"mormaor\",\n\"mormo\",\n\"mormon\",\n\"mormyr\",\n\"mormyre\",\n\"morn\",\n\"morne\",\n\"morned\",\n\"morning\",\n\"moro\",\n\"moroc\",\n\"morocco\",\n\"moron\",\n\"moroncy\",\n\"morong\",\n\"moronic\",\n\"moronry\",\n\"morose\",\n\"morosis\",\n\"morph\",\n\"morphea\",\n\"morphew\",\n\"morphia\",\n\"morphic\",\n\"morphon\",\n\"morris\",\n\"morrow\",\n\"morsal\",\n\"morse\",\n\"morsel\",\n\"morsing\",\n\"morsure\",\n\"mort\",\n\"mortal\",\n\"mortar\",\n\"mortary\",\n\"morth\",\n\"mortier\",\n\"mortify\",\n\"mortise\",\n\"morula\",\n\"morular\",\n\"morule\",\n\"morvin\",\n\"morwong\",\n\"mosaic\",\n\"mosaist\",\n\"mosette\",\n\"mosey\",\n\"mosker\",\n\"mosque\",\n\"moss\",\n\"mossed\",\n\"mosser\",\n\"mossery\",\n\"mossful\",\n\"mossy\",\n\"most\",\n\"moste\",\n\"mostly\",\n\"mot\",\n\"mote\",\n\"moted\",\n\"motel\",\n\"moter\",\n\"motet\",\n\"motey\",\n\"moth\",\n\"mothed\",\n\"mother\",\n\"mothery\",\n\"mothy\",\n\"motif\",\n\"motific\",\n\"motile\",\n\"motion\",\n\"motive\",\n\"motley\",\n\"motmot\",\n\"motor\",\n\"motored\",\n\"motoric\",\n\"motory\",\n\"mott\",\n\"motte\",\n\"mottle\",\n\"mottled\",\n\"mottler\",\n\"motto\",\n\"mottoed\",\n\"motyka\",\n\"mou\",\n\"mouche\",\n\"moud\",\n\"moudie\",\n\"moudy\",\n\"mouflon\",\n\"mouille\",\n\"moujik\",\n\"moul\",\n\"mould\",\n\"moulded\",\n\"moule\",\n\"moulin\",\n\"mouls\",\n\"moulter\",\n\"mouly\",\n\"mound\",\n\"moundy\",\n\"mount\",\n\"mounted\",\n\"mounter\",\n\"moup\",\n\"mourn\",\n\"mourner\",\n\"mouse\",\n\"mouser\",\n\"mousery\",\n\"mousey\",\n\"mousily\",\n\"mousing\",\n\"mousle\",\n\"mousmee\",\n\"mousse\",\n\"moustoc\",\n\"mousy\",\n\"mout\",\n\"moutan\",\n\"mouth\",\n\"mouthed\",\n\"mouther\",\n\"mouthy\",\n\"mouton\",\n\"mouzah\",\n\"movable\",\n\"movably\",\n\"movant\",\n\"move\",\n\"mover\",\n\"movie\",\n\"moving\",\n\"mow\",\n\"mowable\",\n\"mowana\",\n\"mowburn\",\n\"mowch\",\n\"mowcht\",\n\"mower\",\n\"mowha\",\n\"mowie\",\n\"mowing\",\n\"mowland\",\n\"mown\",\n\"mowra\",\n\"mowrah\",\n\"mowse\",\n\"mowt\",\n\"mowth\",\n\"moxa\",\n\"moy\",\n\"moyen\",\n\"moyenne\",\n\"moyite\",\n\"moyle\",\n\"moyo\",\n\"mozing\",\n\"mpret\",\n\"mu\",\n\"muang\",\n\"mubarat\",\n\"mucago\",\n\"mucaro\",\n\"mucedin\",\n\"much\",\n\"muchly\",\n\"mucic\",\n\"mucid\",\n\"mucific\",\n\"mucigen\",\n\"mucin\",\n\"muck\",\n\"mucker\",\n\"mucket\",\n\"muckite\",\n\"muckle\",\n\"muckman\",\n\"muckna\",\n\"mucksy\",\n\"mucky\",\n\"mucluc\",\n\"mucoid\",\n\"muconic\",\n\"mucopus\",\n\"mucor\",\n\"mucosa\",\n\"mucosal\",\n\"mucose\",\n\"mucous\",\n\"mucro\",\n\"mucus\",\n\"mucusin\",\n\"mud\",\n\"mudar\",\n\"mudbank\",\n\"mudcap\",\n\"mudd\",\n\"mudde\",\n\"mudden\",\n\"muddify\",\n\"muddily\",\n\"mudding\",\n\"muddish\",\n\"muddle\",\n\"muddler\",\n\"muddy\",\n\"mudee\",\n\"mudfish\",\n\"mudflow\",\n\"mudhead\",\n\"mudhole\",\n\"mudir\",\n\"mudiria\",\n\"mudland\",\n\"mudlark\",\n\"mudless\",\n\"mudra\",\n\"mudsill\",\n\"mudweed\",\n\"mudwort\",\n\"muermo\",\n\"muezzin\",\n\"muff\",\n\"muffed\",\n\"muffet\",\n\"muffin\",\n\"muffish\",\n\"muffle\",\n\"muffled\",\n\"muffler\",\n\"mufflin\",\n\"muffy\",\n\"mufti\",\n\"mufty\",\n\"mug\",\n\"muga\",\n\"mugful\",\n\"mugg\",\n\"mugger\",\n\"mugget\",\n\"muggily\",\n\"muggins\",\n\"muggish\",\n\"muggles\",\n\"muggy\",\n\"mugient\",\n\"mugweed\",\n\"mugwort\",\n\"mugwump\",\n\"muid\",\n\"muir\",\n\"muist\",\n\"mukluk\",\n\"muktar\",\n\"mukti\",\n\"mulatta\",\n\"mulatto\",\n\"mulch\",\n\"mulcher\",\n\"mulct\",\n\"mulder\",\n\"mule\",\n\"muleman\",\n\"muleta\",\n\"muletta\",\n\"muley\",\n\"mulga\",\n\"mulier\",\n\"mulish\",\n\"mulism\",\n\"mulita\",\n\"mulk\",\n\"mull\",\n\"mulla\",\n\"mullah\",\n\"mullar\",\n\"mullein\",\n\"muller\",\n\"mullet\",\n\"mullets\",\n\"mulley\",\n\"mullid\",\n\"mullion\",\n\"mullite\",\n\"mullock\",\n\"mulloid\",\n\"mulmul\",\n\"mulse\",\n\"mulsify\",\n\"mult\",\n\"multum\",\n\"multure\",\n\"mum\",\n\"mumble\",\n\"mumbler\",\n\"mummer\",\n\"mummery\",\n\"mummick\",\n\"mummied\",\n\"mummify\",\n\"mumming\",\n\"mummy\",\n\"mumness\",\n\"mump\",\n\"mumper\",\n\"mumpish\",\n\"mumps\",\n\"mun\",\n\"munch\",\n\"muncher\",\n\"munchet\",\n\"mund\",\n\"mundane\",\n\"mundic\",\n\"mundify\",\n\"mundil\",\n\"mundle\",\n\"mung\",\n\"munga\",\n\"munge\",\n\"mungey\",\n\"mungo\",\n\"mungofa\",\n\"munguba\",\n\"mungy\",\n\"munific\",\n\"munity\",\n\"munj\",\n\"munjeet\",\n\"munnion\",\n\"munshi\",\n\"munt\",\n\"muntin\",\n\"muntjac\",\n\"mura\",\n\"murage\",\n\"mural\",\n\"muraled\",\n\"murally\",\n\"murchy\",\n\"murder\",\n\"murdrum\",\n\"mure\",\n\"murex\",\n\"murexan\",\n\"murga\",\n\"murgavi\",\n\"murgeon\",\n\"muriate\",\n\"muricid\",\n\"murid\",\n\"murine\",\n\"murinus\",\n\"muriti\",\n\"murium\",\n\"murk\",\n\"murkily\",\n\"murkish\",\n\"murkly\",\n\"murky\",\n\"murlin\",\n\"murly\",\n\"murmur\",\n\"murphy\",\n\"murra\",\n\"murrain\",\n\"murre\",\n\"murrey\",\n\"murrina\",\n\"murshid\",\n\"muruxi\",\n\"murva\",\n\"murza\",\n\"musal\",\n\"musang\",\n\"musar\",\n\"muscade\",\n\"muscat\",\n\"muscid\",\n\"muscle\",\n\"muscled\",\n\"muscly\",\n\"muscoid\",\n\"muscone\",\n\"muscose\",\n\"muscot\",\n\"muscovy\",\n\"muscule\",\n\"muse\",\n\"mused\",\n\"museful\",\n\"museist\",\n\"muser\",\n\"musery\",\n\"musette\",\n\"museum\",\n\"mush\",\n\"musha\",\n\"mushaa\",\n\"mushed\",\n\"musher\",\n\"mushily\",\n\"mushla\",\n\"mushru\",\n\"mushy\",\n\"music\",\n\"musical\",\n\"musico\",\n\"musie\",\n\"musily\",\n\"musimon\",\n\"musing\",\n\"musk\",\n\"muskat\",\n\"muskeg\",\n\"musket\",\n\"muskie\",\n\"muskish\",\n\"muskrat\",\n\"musky\",\n\"muslin\",\n\"musnud\",\n\"musquaw\",\n\"musrol\",\n\"muss\",\n\"mussal\",\n\"mussel\",\n\"mussily\",\n\"mussuk\",\n\"mussy\",\n\"must\",\n\"mustang\",\n\"mustard\",\n\"mustee\",\n\"muster\",\n\"mustify\",\n\"mustily\",\n\"mustnt\",\n\"musty\",\n\"muta\",\n\"mutable\",\n\"mutably\",\n\"mutage\",\n\"mutant\",\n\"mutase\",\n\"mutate\",\n\"mutch\",\n\"mute\",\n\"mutedly\",\n\"mutely\",\n\"muth\",\n\"mutic\",\n\"mutiny\",\n\"mutism\",\n\"mutist\",\n\"mutive\",\n\"mutsje\",\n\"mutt\",\n\"mutter\",\n\"mutton\",\n\"muttony\",\n\"mutual\",\n\"mutuary\",\n\"mutule\",\n\"mutuum\",\n\"mux\",\n\"muyusa\",\n\"muzhik\",\n\"muzz\",\n\"muzzily\",\n\"muzzle\",\n\"muzzler\",\n\"muzzy\",\n\"my\",\n\"myal\",\n\"myalgia\",\n\"myalgic\",\n\"myalism\",\n\"myall\",\n\"myarian\",\n\"myatony\",\n\"mycele\",\n\"mycelia\",\n\"mycoid\",\n\"mycose\",\n\"mycosin\",\n\"mycosis\",\n\"mycotic\",\n\"mydine\",\n\"myelic\",\n\"myelin\",\n\"myeloic\",\n\"myeloid\",\n\"myeloma\",\n\"myelon\",\n\"mygale\",\n\"mygalid\",\n\"myiasis\",\n\"myiosis\",\n\"myitis\",\n\"mykiss\",\n\"mymarid\",\n\"myna\",\n\"myocele\",\n\"myocyte\",\n\"myogen\",\n\"myogram\",\n\"myoid\",\n\"myology\",\n\"myoma\",\n\"myomere\",\n\"myoneme\",\n\"myope\",\n\"myophan\",\n\"myopia\",\n\"myopic\",\n\"myops\",\n\"myopy\",\n\"myosin\",\n\"myosis\",\n\"myosote\",\n\"myotic\",\n\"myotome\",\n\"myotomy\",\n\"myotony\",\n\"myowun\",\n\"myoxine\",\n\"myrcene\",\n\"myrcia\",\n\"myriad\",\n\"myriare\",\n\"myrica\",\n\"myricin\",\n\"myricyl\",\n\"myringa\",\n\"myron\",\n\"myronic\",\n\"myrosin\",\n\"myrrh\",\n\"myrrhed\",\n\"myrrhic\",\n\"myrrhol\",\n\"myrrhy\",\n\"myrtal\",\n\"myrtle\",\n\"myrtol\",\n\"mysel\",\n\"myself\",\n\"mysell\",\n\"mysid\",\n\"mysoid\",\n\"mysost\",\n\"myst\",\n\"mystax\",\n\"mystery\",\n\"mystes\",\n\"mystic\",\n\"mystify\",\n\"myth\",\n\"mythify\",\n\"mythism\",\n\"mythist\",\n\"mythize\",\n\"mythos\",\n\"mythus\",\n\"mytilid\",\n\"myxa\",\n\"myxemia\",\n\"myxo\",\n\"myxoid\",\n\"myxoma\",\n\"myxopod\",\n\"myzont\",\n\"n\",\n\"na\",\n\"naa\",\n\"naam\",\n\"nab\",\n\"nabak\",\n\"nabber\",\n\"nabk\",\n\"nabla\",\n\"nable\",\n\"nabob\",\n\"nabobry\",\n\"nabs\",\n\"nacarat\",\n\"nace\",\n\"nacelle\",\n\"nach\",\n\"nachani\",\n\"nacket\",\n\"nacre\",\n\"nacred\",\n\"nacrine\",\n\"nacrite\",\n\"nacrous\",\n\"nacry\",\n\"nadder\",\n\"nadir\",\n\"nadiral\",\n\"nae\",\n\"naebody\",\n\"naegate\",\n\"nael\",\n\"naether\",\n\"nag\",\n\"naga\",\n\"nagaika\",\n\"nagana\",\n\"nagara\",\n\"nagger\",\n\"naggin\",\n\"nagging\",\n\"naggish\",\n\"naggle\",\n\"naggly\",\n\"naggy\",\n\"naght\",\n\"nagmaal\",\n\"nagman\",\n\"nagnag\",\n\"nagnail\",\n\"nagor\",\n\"nagsman\",\n\"nagster\",\n\"nagual\",\n\"naiad\",\n\"naiant\",\n\"naid\",\n\"naif\",\n\"naifly\",\n\"naig\",\n\"naigie\",\n\"naik\",\n\"nail\",\n\"nailbin\",\n\"nailer\",\n\"nailery\",\n\"nailing\",\n\"nailrod\",\n\"naily\",\n\"nain\",\n\"nainsel\",\n\"naio\",\n\"naipkin\",\n\"nairy\",\n\"nais\",\n\"naish\",\n\"naither\",\n\"naive\",\n\"naively\",\n\"naivete\",\n\"naivety\",\n\"nak\",\n\"nake\",\n\"naked\",\n\"nakedly\",\n\"naker\",\n\"nakhod\",\n\"nakhoda\",\n\"nako\",\n\"nakong\",\n\"nakoo\",\n\"nallah\",\n\"nam\",\n\"namable\",\n\"namaqua\",\n\"namaz\",\n\"namda\",\n\"name\",\n\"namely\",\n\"namer\",\n\"naming\",\n\"nammad\",\n\"nan\",\n\"nana\",\n\"nancy\",\n\"nandi\",\n\"nandine\",\n\"nandow\",\n\"nandu\",\n\"nane\",\n\"nanes\",\n\"nanga\",\n\"nanism\",\n\"nankeen\",\n\"nankin\",\n\"nanny\",\n\"nanoid\",\n\"nanpie\",\n\"nant\",\n\"nantle\",\n\"naology\",\n\"naos\",\n\"nap\",\n\"napa\",\n\"napal\",\n\"napalm\",\n\"nape\",\n\"napead\",\n\"naperer\",\n\"napery\",\n\"naphtha\",\n\"naphtho\",\n\"naphtol\",\n\"napkin\",\n\"napless\",\n\"napoo\",\n\"nappe\",\n\"napped\",\n\"napper\",\n\"napping\",\n\"nappy\",\n\"napron\",\n\"napu\",\n\"nar\",\n\"narcism\",\n\"narcist\",\n\"narcoma\",\n\"narcose\",\n\"narcous\",\n\"nard\",\n\"nardine\",\n\"nardoo\",\n\"nares\",\n\"nargil\",\n\"narial\",\n\"naric\",\n\"narica\",\n\"narine\",\n\"nark\",\n\"narky\",\n\"narr\",\n\"narra\",\n\"narras\",\n\"narrate\",\n\"narrow\",\n\"narrowy\",\n\"narthex\",\n\"narwhal\",\n\"nary\",\n\"nasab\",\n\"nasal\",\n\"nasalis\",\n\"nasally\",\n\"nasard\",\n\"nascent\",\n\"nasch\",\n\"nash\",\n\"nashgab\",\n\"nashgob\",\n\"nasi\",\n\"nasial\",\n\"nasion\",\n\"nasitis\",\n\"nasrol\",\n\"nast\",\n\"nastic\",\n\"nastika\",\n\"nastily\",\n\"nasty\",\n\"nasus\",\n\"nasute\",\n\"nasutus\",\n\"nat\",\n\"nataka\",\n\"natal\",\n\"natals\",\n\"natant\",\n\"natator\",\n\"natch\",\n\"nates\",\n\"nathe\",\n\"nather\",\n\"nation\",\n\"native\",\n\"natr\",\n\"natrium\",\n\"natron\",\n\"natter\",\n\"nattily\",\n\"nattle\",\n\"natty\",\n\"natuary\",\n\"natural\",\n\"nature\",\n\"naucrar\",\n\"nauger\",\n\"naught\",\n\"naughty\",\n\"naumk\",\n\"naunt\",\n\"nauntle\",\n\"nausea\",\n\"naut\",\n\"nautch\",\n\"nauther\",\n\"nautic\",\n\"nautics\",\n\"naval\",\n\"navally\",\n\"navar\",\n\"navarch\",\n\"nave\",\n\"navel\",\n\"naveled\",\n\"navet\",\n\"navette\",\n\"navew\",\n\"navite\",\n\"navvy\",\n\"navy\",\n\"naw\",\n\"nawab\",\n\"nawt\",\n\"nay\",\n\"nayaur\",\n\"naysay\",\n\"nayward\",\n\"nayword\",\n\"naze\",\n\"nazim\",\n\"nazir\",\n\"ne\",\n\"nea\",\n\"neal\",\n\"neanic\",\n\"neap\",\n\"neaped\",\n\"nearby\",\n\"nearest\",\n\"nearish\",\n\"nearly\",\n\"neat\",\n\"neaten\",\n\"neath\",\n\"neatify\",\n\"neatly\",\n\"neb\",\n\"neback\",\n\"nebbed\",\n\"nebbuck\",\n\"nebbuk\",\n\"nebby\",\n\"nebel\",\n\"nebris\",\n\"nebula\",\n\"nebulae\",\n\"nebular\",\n\"nebule\",\n\"neck\",\n\"neckar\",\n\"necked\",\n\"necker\",\n\"neckful\",\n\"necking\",\n\"necklet\",\n\"necktie\",\n\"necrose\",\n\"nectar\",\n\"nectary\",\n\"nedder\",\n\"neddy\",\n\"nee\",\n\"neebor\",\n\"neebour\",\n\"need\",\n\"needer\",\n\"needful\",\n\"needham\",\n\"needily\",\n\"needing\",\n\"needle\",\n\"needled\",\n\"needler\",\n\"needles\",\n\"needly\",\n\"needs\",\n\"needy\",\n\"neeger\",\n\"neeld\",\n\"neele\",\n\"neem\",\n\"neep\",\n\"neepour\",\n\"neer\",\n\"neese\",\n\"neet\",\n\"neetup\",\n\"neeze\",\n\"nef\",\n\"nefast\",\n\"neffy\",\n\"neftgil\",\n\"negate\",\n\"negator\",\n\"neger\",\n\"neglect\",\n\"negrine\",\n\"negro\",\n\"negus\",\n\"nei\",\n\"neif\",\n\"neigh\",\n\"neigher\",\n\"neiper\",\n\"neist\",\n\"neither\",\n\"nekton\",\n\"nelson\",\n\"nema\",\n\"nematic\",\n\"nemeses\",\n\"nemesic\",\n\"nemoral\",\n\"nenta\",\n\"neo\",\n\"neocyte\",\n\"neogamy\",\n\"neolith\",\n\"neology\",\n\"neon\",\n\"neonate\",\n\"neorama\",\n\"neossin\",\n\"neoteny\",\n\"neotype\",\n\"neoza\",\n\"nep\",\n\"neper\",\n\"nephele\",\n\"nephesh\",\n\"nephew\",\n\"nephria\",\n\"nephric\",\n\"nephron\",\n\"nephros\",\n\"nepman\",\n\"nepotal\",\n\"nepote\",\n\"nepotic\",\n\"nereite\",\n\"nerine\",\n\"neritic\",\n\"nerval\",\n\"nervate\",\n\"nerve\",\n\"nerver\",\n\"nervid\",\n\"nervily\",\n\"nervine\",\n\"nerving\",\n\"nervish\",\n\"nervism\",\n\"nervose\",\n\"nervous\",\n\"nervule\",\n\"nervure\",\n\"nervy\",\n\"nese\",\n\"nesh\",\n\"neshly\",\n\"nesiote\",\n\"ness\",\n\"nest\",\n\"nestage\",\n\"nester\",\n\"nestful\",\n\"nestle\",\n\"nestler\",\n\"nesty\",\n\"net\",\n\"netball\",\n\"netbush\",\n\"netcha\",\n\"nete\",\n\"neter\",\n\"netful\",\n\"neth\",\n\"nether\",\n\"neti\",\n\"netleaf\",\n\"netlike\",\n\"netman\",\n\"netop\",\n\"netsman\",\n\"netsuke\",\n\"netted\",\n\"netter\",\n\"netting\",\n\"nettle\",\n\"nettler\",\n\"nettly\",\n\"netty\",\n\"netwise\",\n\"network\",\n\"neuma\",\n\"neume\",\n\"neumic\",\n\"neurad\",\n\"neural\",\n\"neurale\",\n\"neuric\",\n\"neurin\",\n\"neurine\",\n\"neurism\",\n\"neurite\",\n\"neuroid\",\n\"neuroma\",\n\"neuron\",\n\"neurone\",\n\"neurula\",\n\"neuter\",\n\"neutral\",\n\"neutron\",\n\"neve\",\n\"nevel\",\n\"never\",\n\"nevo\",\n\"nevoid\",\n\"nevoy\",\n\"nevus\",\n\"new\",\n\"newcal\",\n\"newcome\",\n\"newel\",\n\"newelty\",\n\"newing\",\n\"newings\",\n\"newish\",\n\"newly\",\n\"newness\",\n\"news\",\n\"newsboy\",\n\"newsful\",\n\"newsman\",\n\"newsy\",\n\"newt\",\n\"newtake\",\n\"newton\",\n\"nexal\",\n\"next\",\n\"nextly\",\n\"nexum\",\n\"nexus\",\n\"neyanda\",\n\"ngai\",\n\"ngaio\",\n\"ngapi\",\n\"ni\",\n\"niacin\",\n\"niata\",\n\"nib\",\n\"nibbana\",\n\"nibbed\",\n\"nibber\",\n\"nibble\",\n\"nibbler\",\n\"nibby\",\n\"niblick\",\n\"niblike\",\n\"nibong\",\n\"nibs\",\n\"nibsome\",\n\"nice\",\n\"niceish\",\n\"nicely\",\n\"nicety\",\n\"niche\",\n\"nicher\",\n\"nick\",\n\"nickel\",\n\"nicker\",\n\"nickey\",\n\"nicking\",\n\"nickle\",\n\"nicky\",\n\"nicolo\",\n\"nicotia\",\n\"nicotic\",\n\"nictate\",\n\"nid\",\n\"nidal\",\n\"nidana\",\n\"niddick\",\n\"niddle\",\n\"nide\",\n\"nidge\",\n\"nidget\",\n\"nidgety\",\n\"nidi\",\n\"nidify\",\n\"niding\",\n\"nidor\",\n\"nidulus\",\n\"nidus\",\n\"niece\",\n\"nielled\",\n\"niello\",\n\"niepa\",\n\"nieve\",\n\"nieveta\",\n\"nife\",\n\"niffer\",\n\"nific\",\n\"nifle\",\n\"nifling\",\n\"nifty\",\n\"nig\",\n\"niggard\",\n\"nigger\",\n\"niggery\",\n\"niggle\",\n\"niggler\",\n\"niggly\",\n\"nigh\",\n\"nighly\",\n\"night\",\n\"nighted\",\n\"nightie\",\n\"nightly\",\n\"nights\",\n\"nignay\",\n\"nignye\",\n\"nigori\",\n\"nigre\",\n\"nigrify\",\n\"nigrine\",\n\"nigrous\",\n\"nigua\",\n\"nikau\",\n\"nil\",\n\"nilgai\",\n\"nim\",\n\"nimb\",\n\"nimbed\",\n\"nimbi\",\n\"nimble\",\n\"nimbly\",\n\"nimbose\",\n\"nimbus\",\n\"nimiety\",\n\"niminy\",\n\"nimious\",\n\"nimmer\",\n\"nimshi\",\n\"nincom\",\n\"nine\",\n\"ninepin\",\n\"nineted\",\n\"ninety\",\n\"ninny\",\n\"ninon\",\n\"ninth\",\n\"ninthly\",\n\"nintu\",\n\"ninut\",\n\"niobate\",\n\"niobic\",\n\"niobite\",\n\"niobium\",\n\"niobous\",\n\"niog\",\n\"niota\",\n\"nip\",\n\"nipa\",\n\"nipper\",\n\"nippers\",\n\"nippily\",\n\"nipping\",\n\"nipple\",\n\"nippy\",\n\"nipter\",\n\"nirles\",\n\"nirvana\",\n\"nisei\",\n\"nishiki\",\n\"nisnas\",\n\"nispero\",\n\"nisse\",\n\"nisus\",\n\"nit\",\n\"nitch\",\n\"nitency\",\n\"niter\",\n\"nitered\",\n\"nither\",\n\"nithing\",\n\"nitid\",\n\"nito\",\n\"niton\",\n\"nitrate\",\n\"nitric\",\n\"nitride\",\n\"nitrify\",\n\"nitrile\",\n\"nitrite\",\n\"nitro\",\n\"nitrous\",\n\"nitryl\",\n\"nitter\",\n\"nitty\",\n\"nitwit\",\n\"nival\",\n\"niveous\",\n\"nix\",\n\"nixie\",\n\"niyoga\",\n\"nizam\",\n\"nizamut\",\n\"nizy\",\n\"njave\",\n\"no\",\n\"noa\",\n\"nob\",\n\"nobber\",\n\"nobbily\",\n\"nobble\",\n\"nobbler\",\n\"nobbut\",\n\"nobby\",\n\"noble\",\n\"nobley\",\n\"nobly\",\n\"nobody\",\n\"nobs\",\n\"nocake\",\n\"nocent\",\n\"nock\",\n\"nocket\",\n\"nocktat\",\n\"noctuid\",\n\"noctule\",\n\"nocturn\",\n\"nocuity\",\n\"nocuous\",\n\"nod\",\n\"nodal\",\n\"nodated\",\n\"nodder\",\n\"nodding\",\n\"noddle\",\n\"noddy\",\n\"node\",\n\"noded\",\n\"nodi\",\n\"nodiak\",\n\"nodical\",\n\"nodose\",\n\"nodous\",\n\"nodular\",\n\"nodule\",\n\"noduled\",\n\"nodulus\",\n\"nodus\",\n\"noel\",\n\"noetic\",\n\"noetics\",\n\"nog\",\n\"nogada\",\n\"nogal\",\n\"noggen\",\n\"noggin\",\n\"nogging\",\n\"noghead\",\n\"nohow\",\n\"noil\",\n\"noilage\",\n\"noiler\",\n\"noily\",\n\"noint\",\n\"noir\",\n\"noise\",\n\"noisily\",\n\"noisome\",\n\"noisy\",\n\"nokta\",\n\"noll\",\n\"nolle\",\n\"nolo\",\n\"noma\",\n\"nomad\",\n\"nomadic\",\n\"nomancy\",\n\"nomarch\",\n\"nombril\",\n\"nome\",\n\"nomial\",\n\"nomic\",\n\"nomina\",\n\"nominal\",\n\"nominee\",\n\"nominy\",\n\"nomism\",\n\"nomisma\",\n\"nomos\",\n\"non\",\n\"nonacid\",\n\"nonact\",\n\"nonage\",\n\"nonagon\",\n\"nonaid\",\n\"nonair\",\n\"nonane\",\n\"nonary\",\n\"nonbase\",\n\"nonce\",\n\"noncock\",\n\"noncom\",\n\"noncome\",\n\"noncon\",\n\"nonda\",\n\"nondo\",\n\"none\",\n\"nonego\",\n\"nonene\",\n\"nonent\",\n\"nonepic\",\n\"nones\",\n\"nonet\",\n\"nonevil\",\n\"nonfact\",\n\"nonfarm\",\n\"nonfat\",\n\"nonfood\",\n\"nonform\",\n\"nonfrat\",\n\"nongas\",\n\"nongod\",\n\"nongold\",\n\"nongray\",\n\"nongrey\",\n\"nonhero\",\n\"nonic\",\n\"nonion\",\n\"nonius\",\n\"nonjury\",\n\"nonlife\",\n\"nonly\",\n\"nonnant\",\n\"nonnat\",\n\"nonoic\",\n\"nonoily\",\n\"nonomad\",\n\"nonpaid\",\n\"nonpar\",\n\"nonpeak\",\n\"nonplus\",\n\"nonpoet\",\n\"nonport\",\n\"nonrun\",\n\"nonsale\",\n\"nonsane\",\n\"nonself\",\n\"nonsine\",\n\"nonskid\",\n\"nonslip\",\n\"nonstop\",\n\"nonsuit\",\n\"nontan\",\n\"nontax\",\n\"nonterm\",\n\"nonuple\",\n\"nonuse\",\n\"nonuser\",\n\"nonwar\",\n\"nonya\",\n\"nonyl\",\n\"nonylic\",\n\"nonzero\",\n\"noodle\",\n\"nook\",\n\"nooked\",\n\"nookery\",\n\"nooking\",\n\"nooklet\",\n\"nooky\",\n\"noology\",\n\"noon\",\n\"noonday\",\n\"nooning\",\n\"noonlit\",\n\"noop\",\n\"noose\",\n\"nooser\",\n\"nopal\",\n\"nopalry\",\n\"nope\",\n\"nor\",\n\"norard\",\n\"norate\",\n\"noreast\",\n\"norelin\",\n\"norgine\",\n\"nori\",\n\"noria\",\n\"norie\",\n\"norimon\",\n\"norite\",\n\"norland\",\n\"norm\",\n\"norma\",\n\"normal\",\n\"norsel\",\n\"north\",\n\"norther\",\n\"norward\",\n\"norwest\",\n\"nose\",\n\"nosean\",\n\"nosed\",\n\"nosegay\",\n\"noser\",\n\"nosey\",\n\"nosine\",\n\"nosing\",\n\"nosism\",\n\"nostic\",\n\"nostril\",\n\"nostrum\",\n\"nosy\",\n\"not\",\n\"notable\",\n\"notably\",\n\"notaeal\",\n\"notaeum\",\n\"notal\",\n\"notan\",\n\"notary\",\n\"notate\",\n\"notator\",\n\"notch\",\n\"notched\",\n\"notchel\",\n\"notcher\",\n\"notchy\",\n\"note\",\n\"noted\",\n\"notedly\",\n\"notekin\",\n\"notelet\",\n\"noter\",\n\"nother\",\n\"nothing\",\n\"nothous\",\n\"notice\",\n\"noticer\",\n\"notify\",\n\"notion\",\n\"notitia\",\n\"notour\",\n\"notself\",\n\"notum\",\n\"nougat\",\n\"nought\",\n\"noun\",\n\"nounal\",\n\"nounize\",\n\"noup\",\n\"nourice\",\n\"nourish\",\n\"nous\",\n\"nouther\",\n\"nova\",\n\"novalia\",\n\"novate\",\n\"novator\",\n\"novcic\",\n\"novel\",\n\"novelet\",\n\"novella\",\n\"novelly\",\n\"novelry\",\n\"novelty\",\n\"novem\",\n\"novena\",\n\"novene\",\n\"novice\",\n\"novity\",\n\"now\",\n\"nowaday\",\n\"noway\",\n\"noways\",\n\"nowed\",\n\"nowel\",\n\"nowhat\",\n\"nowhen\",\n\"nowhere\",\n\"nowhit\",\n\"nowise\",\n\"nowness\",\n\"nowt\",\n\"nowy\",\n\"noxa\",\n\"noxal\",\n\"noxally\",\n\"noxious\",\n\"noy\",\n\"noyade\",\n\"noyau\",\n\"nozzle\",\n\"nozzler\",\n\"nth\",\n\"nu\",\n\"nuance\",\n\"nub\",\n\"nubbin\",\n\"nubble\",\n\"nubbly\",\n\"nubby\",\n\"nubia\",\n\"nubile\",\n\"nucal\",\n\"nucha\",\n\"nuchal\",\n\"nucin\",\n\"nucleal\",\n\"nuclear\",\n\"nuclei\",\n\"nuclein\",\n\"nucleon\",\n\"nucleus\",\n\"nuclide\",\n\"nucule\",\n\"nuculid\",\n\"nudate\",\n\"nuddle\",\n\"nude\",\n\"nudely\",\n\"nudge\",\n\"nudger\",\n\"nudiped\",\n\"nudish\",\n\"nudism\",\n\"nudist\",\n\"nudity\",\n\"nugator\",\n\"nuggar\",\n\"nugget\",\n\"nuggety\",\n\"nugify\",\n\"nuke\",\n\"nul\",\n\"null\",\n\"nullah\",\n\"nullify\",\n\"nullism\",\n\"nullity\",\n\"nullo\",\n\"numb\",\n\"number\",\n\"numbing\",\n\"numble\",\n\"numbles\",\n\"numbly\",\n\"numda\",\n\"numdah\",\n\"numen\",\n\"numeral\",\n\"numero\",\n\"nummary\",\n\"nummi\",\n\"nummus\",\n\"numud\",\n\"nun\",\n\"nunatak\",\n\"nunbird\",\n\"nunch\",\n\"nuncio\",\n\"nuncle\",\n\"nundine\",\n\"nunhood\",\n\"nunky\",\n\"nunlet\",\n\"nunlike\",\n\"nunnari\",\n\"nunnery\",\n\"nunni\",\n\"nunnify\",\n\"nunnish\",\n\"nunship\",\n\"nuptial\",\n\"nuque\",\n\"nuraghe\",\n\"nurhag\",\n\"nurly\",\n\"nurse\",\n\"nurser\",\n\"nursery\",\n\"nursing\",\n\"nursle\",\n\"nursy\",\n\"nurture\",\n\"nusfiah\",\n\"nut\",\n\"nutant\",\n\"nutate\",\n\"nutcake\",\n\"nutgall\",\n\"nuthook\",\n\"nutlet\",\n\"nutlike\",\n\"nutmeg\",\n\"nutpick\",\n\"nutria\",\n\"nutrice\",\n\"nutrify\",\n\"nutseed\",\n\"nutted\",\n\"nutter\",\n\"nuttery\",\n\"nuttily\",\n\"nutting\",\n\"nuttish\",\n\"nutty\",\n\"nuzzer\",\n\"nuzzle\",\n\"nyanza\",\n\"nye\",\n\"nylast\",\n\"nylon\",\n\"nymil\",\n\"nymph\",\n\"nympha\",\n\"nymphae\",\n\"nymphal\",\n\"nymphet\",\n\"nymphic\",\n\"nymphid\",\n\"nymphly\",\n\"nyxis\",\n\"o\",\n\"oadal\",\n\"oaf\",\n\"oafdom\",\n\"oafish\",\n\"oak\",\n\"oaken\",\n\"oaklet\",\n\"oaklike\",\n\"oakling\",\n\"oakum\",\n\"oakweb\",\n\"oakwood\",\n\"oaky\",\n\"oam\",\n\"oar\",\n\"oarage\",\n\"oarcock\",\n\"oared\",\n\"oarfish\",\n\"oarhole\",\n\"oarial\",\n\"oaric\",\n\"oaritic\",\n\"oaritis\",\n\"oarium\",\n\"oarless\",\n\"oarlike\",\n\"oarlock\",\n\"oarlop\",\n\"oarman\",\n\"oarsman\",\n\"oarweed\",\n\"oary\",\n\"oasal\",\n\"oasean\",\n\"oases\",\n\"oasis\",\n\"oasitic\",\n\"oast\",\n\"oat\",\n\"oatbin\",\n\"oatcake\",\n\"oatear\",\n\"oaten\",\n\"oatfowl\",\n\"oath\",\n\"oathay\",\n\"oathed\",\n\"oathful\",\n\"oathlet\",\n\"oatland\",\n\"oatlike\",\n\"oatmeal\",\n\"oatseed\",\n\"oaty\",\n\"oban\",\n\"obclude\",\n\"obe\",\n\"obeah\",\n\"obeche\",\n\"obeism\",\n\"obelia\",\n\"obeliac\",\n\"obelial\",\n\"obelion\",\n\"obelisk\",\n\"obelism\",\n\"obelize\",\n\"obelus\",\n\"obese\",\n\"obesely\",\n\"obesity\",\n\"obex\",\n\"obey\",\n\"obeyer\",\n\"obi\",\n\"obispo\",\n\"obit\",\n\"obitual\",\n\"object\",\n\"objure\",\n\"oblate\",\n\"obley\",\n\"oblige\",\n\"obliged\",\n\"obligee\",\n\"obliger\",\n\"obligor\",\n\"oblique\",\n\"oblong\",\n\"obloquy\",\n\"oboe\",\n\"oboist\",\n\"obol\",\n\"obolary\",\n\"obole\",\n\"obolet\",\n\"obolus\",\n\"oboval\",\n\"obovate\",\n\"obovoid\",\n\"obscene\",\n\"obscure\",\n\"obsede\",\n\"obsequy\",\n\"observe\",\n\"obsess\",\n\"obtain\",\n\"obtect\",\n\"obtest\",\n\"obtrude\",\n\"obtund\",\n\"obtuse\",\n\"obverse\",\n\"obvert\",\n\"obviate\",\n\"obvious\",\n\"obvolve\",\n\"ocarina\",\n\"occamy\",\n\"occiput\",\n\"occlude\",\n\"occluse\",\n\"occult\",\n\"occupy\",\n\"occur\",\n\"ocean\",\n\"oceaned\",\n\"oceanet\",\n\"oceanic\",\n\"ocellar\",\n\"ocelli\",\n\"ocellus\",\n\"oceloid\",\n\"ocelot\",\n\"och\",\n\"ochava\",\n\"ochavo\",\n\"ocher\",\n\"ochery\",\n\"ochone\",\n\"ochrea\",\n\"ochro\",\n\"ochroid\",\n\"ochrous\",\n\"ocht\",\n\"ock\",\n\"oclock\",\n\"ocote\",\n\"ocque\",\n\"ocracy\",\n\"ocrea\",\n\"ocreate\",\n\"octad\",\n\"octadic\",\n\"octagon\",\n\"octan\",\n\"octane\",\n\"octant\",\n\"octapla\",\n\"octarch\",\n\"octary\",\n\"octaval\",\n\"octave\",\n\"octavic\",\n\"octavo\",\n\"octene\",\n\"octet\",\n\"octic\",\n\"octine\",\n\"octoad\",\n\"octoate\",\n\"octofid\",\n\"octoic\",\n\"octoid\",\n\"octonal\",\n\"octoon\",\n\"octoped\",\n\"octopi\",\n\"octopod\",\n\"octopus\",\n\"octose\",\n\"octoyl\",\n\"octroi\",\n\"octroy\",\n\"octuor\",\n\"octuple\",\n\"octuply\",\n\"octyl\",\n\"octyne\",\n\"ocuby\",\n\"ocular\",\n\"oculary\",\n\"oculate\",\n\"oculist\",\n\"oculus\",\n\"od\",\n\"oda\",\n\"odacoid\",\n\"odal\",\n\"odalisk\",\n\"odaller\",\n\"odalman\",\n\"odd\",\n\"oddish\",\n\"oddity\",\n\"oddlegs\",\n\"oddly\",\n\"oddman\",\n\"oddment\",\n\"oddness\",\n\"odds\",\n\"oddsman\",\n\"ode\",\n\"odel\",\n\"odelet\",\n\"odeon\",\n\"odeum\",\n\"odic\",\n\"odinite\",\n\"odious\",\n\"odist\",\n\"odium\",\n\"odology\",\n\"odontic\",\n\"odoom\",\n\"odor\",\n\"odorant\",\n\"odorate\",\n\"odored\",\n\"odorful\",\n\"odorize\",\n\"odorous\",\n\"odso\",\n\"odum\",\n\"odyl\",\n\"odylic\",\n\"odylism\",\n\"odylist\",\n\"odylize\",\n\"oe\",\n\"oecist\",\n\"oecus\",\n\"oenin\",\n\"oenolin\",\n\"oenomel\",\n\"oer\",\n\"oersted\",\n\"oes\",\n\"oestrid\",\n\"oestrin\",\n\"oestrum\",\n\"oestrus\",\n\"of\",\n\"off\",\n\"offal\",\n\"offbeat\",\n\"offcast\",\n\"offcome\",\n\"offcut\",\n\"offend\",\n\"offense\",\n\"offer\",\n\"offeree\",\n\"offerer\",\n\"offeror\",\n\"offhand\",\n\"office\",\n\"officer\",\n\"offing\",\n\"offish\",\n\"offlet\",\n\"offlook\",\n\"offscum\",\n\"offset\",\n\"offtake\",\n\"offtype\",\n\"offward\",\n\"oflete\",\n\"oft\",\n\"often\",\n\"oftens\",\n\"ofter\",\n\"oftest\",\n\"oftly\",\n\"oftness\",\n\"ofttime\",\n\"ogaire\",\n\"ogam\",\n\"ogamic\",\n\"ogdoad\",\n\"ogdoas\",\n\"ogee\",\n\"ogeed\",\n\"ogham\",\n\"oghamic\",\n\"ogival\",\n\"ogive\",\n\"ogived\",\n\"ogle\",\n\"ogler\",\n\"ogmic\",\n\"ogre\",\n\"ogreish\",\n\"ogreism\",\n\"ogress\",\n\"ogrish\",\n\"ogrism\",\n\"ogtiern\",\n\"ogum\",\n\"oh\",\n\"ohelo\",\n\"ohia\",\n\"ohm\",\n\"ohmage\",\n\"ohmic\",\n\"oho\",\n\"ohoy\",\n\"oidioid\",\n\"oii\",\n\"oil\",\n\"oilbird\",\n\"oilcan\",\n\"oilcoat\",\n\"oilcup\",\n\"oildom\",\n\"oiled\",\n\"oiler\",\n\"oilery\",\n\"oilfish\",\n\"oilhole\",\n\"oilily\",\n\"oilless\",\n\"oillet\",\n\"oillike\",\n\"oilman\",\n\"oilseed\",\n\"oilskin\",\n\"oilway\",\n\"oily\",\n\"oilyish\",\n\"oime\",\n\"oinomel\",\n\"oint\",\n\"oisin\",\n\"oitava\",\n\"oka\",\n\"okapi\",\n\"okee\",\n\"okenite\",\n\"oket\",\n\"oki\",\n\"okia\",\n\"okonite\",\n\"okra\",\n\"okrug\",\n\"olam\",\n\"olamic\",\n\"old\",\n\"olden\",\n\"older\",\n\"oldish\",\n\"oldland\",\n\"oldness\",\n\"oldster\",\n\"oldwife\",\n\"oleana\",\n\"olease\",\n\"oleate\",\n\"olefin\",\n\"olefine\",\n\"oleic\",\n\"olein\",\n\"olena\",\n\"olenid\",\n\"olent\",\n\"oleo\",\n\"oleose\",\n\"oleous\",\n\"olfact\",\n\"olfacty\",\n\"oliban\",\n\"olid\",\n\"oligist\",\n\"olio\",\n\"olitory\",\n\"oliva\",\n\"olivary\",\n\"olive\",\n\"olived\",\n\"olivet\",\n\"olivil\",\n\"olivile\",\n\"olivine\",\n\"olla\",\n\"ollamh\",\n\"ollapod\",\n\"ollock\",\n\"olm\",\n\"ologist\",\n\"ology\",\n\"olomao\",\n\"olona\",\n\"oloroso\",\n\"olpe\",\n\"oltonde\",\n\"oltunna\",\n\"olycook\",\n\"olykoek\",\n\"om\",\n\"omagra\",\n\"omalgia\",\n\"omao\",\n\"omasum\",\n\"omber\",\n\"omega\",\n\"omegoid\",\n\"omelet\",\n\"omen\",\n\"omened\",\n\"omental\",\n\"omentum\",\n\"omer\",\n\"omicron\",\n\"omina\",\n\"ominous\",\n\"omit\",\n\"omitis\",\n\"omitter\",\n\"omlah\",\n\"omneity\",\n\"omniana\",\n\"omnibus\",\n\"omnific\",\n\"omnify\",\n\"omnist\",\n\"omnium\",\n\"on\",\n\"ona\",\n\"onager\",\n\"onagra\",\n\"onanism\",\n\"onanist\",\n\"onca\",\n\"once\",\n\"oncetta\",\n\"oncia\",\n\"oncin\",\n\"oncome\",\n\"oncosis\",\n\"oncost\",\n\"ondatra\",\n\"ondine\",\n\"ondy\",\n\"one\",\n\"onefold\",\n\"onegite\",\n\"onehow\",\n\"oneiric\",\n\"oneism\",\n\"onement\",\n\"oneness\",\n\"oner\",\n\"onerary\",\n\"onerous\",\n\"onery\",\n\"oneself\",\n\"onetime\",\n\"oneyer\",\n\"onfall\",\n\"onflow\",\n\"ongaro\",\n\"ongoing\",\n\"onicolo\",\n\"onion\",\n\"onionet\",\n\"oniony\",\n\"onium\",\n\"onkos\",\n\"onlay\",\n\"onlepy\",\n\"onliest\",\n\"onlook\",\n\"only\",\n\"onmarch\",\n\"onrush\",\n\"ons\",\n\"onset\",\n\"onshore\",\n\"onside\",\n\"onsight\",\n\"onstand\",\n\"onstead\",\n\"onsweep\",\n\"ontal\",\n\"onto\",\n\"onus\",\n\"onward\",\n\"onwards\",\n\"onycha\",\n\"onychia\",\n\"onychin\",\n\"onym\",\n\"onymal\",\n\"onymity\",\n\"onymize\",\n\"onymous\",\n\"onymy\",\n\"onyx\",\n\"onyxis\",\n\"onza\",\n\"ooblast\",\n\"oocyst\",\n\"oocyte\",\n\"oodles\",\n\"ooecial\",\n\"ooecium\",\n\"oofbird\",\n\"ooftish\",\n\"oofy\",\n\"oogamy\",\n\"oogeny\",\n\"ooglea\",\n\"oogone\",\n\"oograph\",\n\"ooid\",\n\"ooidal\",\n\"oolak\",\n\"oolemma\",\n\"oolite\",\n\"oolitic\",\n\"oolly\",\n\"oologic\",\n\"oology\",\n\"oolong\",\n\"oomancy\",\n\"oometer\",\n\"oometry\",\n\"oons\",\n\"oont\",\n\"oopak\",\n\"oophore\",\n\"oophyte\",\n\"ooplasm\",\n\"ooplast\",\n\"oopod\",\n\"oopodal\",\n\"oorali\",\n\"oord\",\n\"ooscope\",\n\"ooscopy\",\n\"oosperm\",\n\"oospore\",\n\"ootheca\",\n\"ootid\",\n\"ootype\",\n\"ooze\",\n\"oozily\",\n\"oozooid\",\n\"oozy\",\n\"opacate\",\n\"opacify\",\n\"opacite\",\n\"opacity\",\n\"opacous\",\n\"opah\",\n\"opal\",\n\"opaled\",\n\"opaline\",\n\"opalish\",\n\"opalize\",\n\"opaloid\",\n\"opaque\",\n\"ope\",\n\"opelet\",\n\"open\",\n\"opener\",\n\"opening\",\n\"openly\",\n\"opera\",\n\"operae\",\n\"operand\",\n\"operant\",\n\"operate\",\n\"opercle\",\n\"operose\",\n\"ophic\",\n\"ophioid\",\n\"ophite\",\n\"ophitic\",\n\"ophryon\",\n\"opianic\",\n\"opianyl\",\n\"opiate\",\n\"opiatic\",\n\"opiism\",\n\"opinant\",\n\"opine\",\n\"opiner\",\n\"opinion\",\n\"opium\",\n\"opossum\",\n\"oppidan\",\n\"oppose\",\n\"opposed\",\n\"opposer\",\n\"opposit\",\n\"oppress\",\n\"oppugn\",\n\"opsonic\",\n\"opsonin\",\n\"opsy\",\n\"opt\",\n\"optable\",\n\"optably\",\n\"optant\",\n\"optate\",\n\"optic\",\n\"optical\",\n\"opticon\",\n\"optics\",\n\"optimal\",\n\"optime\",\n\"optimum\",\n\"option\",\n\"optive\",\n\"opulent\",\n\"opulus\",\n\"opus\",\n\"oquassa\",\n\"or\",\n\"ora\",\n\"orach\",\n\"oracle\",\n\"orad\",\n\"orage\",\n\"oral\",\n\"oraler\",\n\"oralism\",\n\"oralist\",\n\"orality\",\n\"oralize\",\n\"orally\",\n\"oralogy\",\n\"orang\",\n\"orange\",\n\"oranger\",\n\"orangey\",\n\"orant\",\n\"orarian\",\n\"orarion\",\n\"orarium\",\n\"orary\",\n\"orate\",\n\"oration\",\n\"orator\",\n\"oratory\",\n\"oratrix\",\n\"orb\",\n\"orbed\",\n\"orbic\",\n\"orbical\",\n\"orbicle\",\n\"orbific\",\n\"orbit\",\n\"orbital\",\n\"orbitar\",\n\"orbite\",\n\"orbless\",\n\"orblet\",\n\"orby\",\n\"orc\",\n\"orcanet\",\n\"orcein\",\n\"orchard\",\n\"orchat\",\n\"orchel\",\n\"orchic\",\n\"orchid\",\n\"orchil\",\n\"orcin\",\n\"orcinol\",\n\"ordain\",\n\"ordeal\",\n\"order\",\n\"ordered\",\n\"orderer\",\n\"orderly\",\n\"ordinal\",\n\"ordinar\",\n\"ordinee\",\n\"ordines\",\n\"ordu\",\n\"ordure\",\n\"ore\",\n\"oread\",\n\"orectic\",\n\"orellin\",\n\"oreman\",\n\"orenda\",\n\"oreweed\",\n\"orewood\",\n\"orexis\",\n\"orf\",\n\"orfgild\",\n\"organ\",\n\"organal\",\n\"organdy\",\n\"organer\",\n\"organic\",\n\"organon\",\n\"organry\",\n\"organum\",\n\"orgasm\",\n\"orgeat\",\n\"orgia\",\n\"orgiac\",\n\"orgiacs\",\n\"orgiasm\",\n\"orgiast\",\n\"orgic\",\n\"orgue\",\n\"orgy\",\n\"orgyia\",\n\"oribi\",\n\"oriel\",\n\"oriency\",\n\"orient\",\n\"orifice\",\n\"oriform\",\n\"origan\",\n\"origin\",\n\"orignal\",\n\"orihon\",\n\"orillon\",\n\"oriole\",\n\"orison\",\n\"oristic\",\n\"orle\",\n\"orlean\",\n\"orlet\",\n\"orlo\",\n\"orlop\",\n\"ormer\",\n\"ormolu\",\n\"orna\",\n\"ornate\",\n\"ornery\",\n\"ornis\",\n\"ornoite\",\n\"oroanal\",\n\"orogen\",\n\"orogeny\",\n\"oroide\",\n\"orology\",\n\"oronoco\",\n\"orotund\",\n\"orphan\",\n\"orpheon\",\n\"orpheum\",\n\"orphrey\",\n\"orpine\",\n\"orrery\",\n\"orrhoid\",\n\"orris\",\n\"orsel\",\n\"orselle\",\n\"ort\",\n\"ortalid\",\n\"ortet\",\n\"orthal\",\n\"orthian\",\n\"orthic\",\n\"orthid\",\n\"orthite\",\n\"ortho\",\n\"orthose\",\n\"orthron\",\n\"ortiga\",\n\"ortive\",\n\"ortolan\",\n\"ortygan\",\n\"ory\",\n\"oryssid\",\n\"os\",\n\"osamin\",\n\"osamine\",\n\"osazone\",\n\"oscella\",\n\"oscheal\",\n\"oscin\",\n\"oscine\",\n\"oscnode\",\n\"oscular\",\n\"oscule\",\n\"osculum\",\n\"ose\",\n\"osela\",\n\"oshac\",\n\"oside\",\n\"osier\",\n\"osiered\",\n\"osiery\",\n\"osmate\",\n\"osmatic\",\n\"osmesis\",\n\"osmetic\",\n\"osmic\",\n\"osmin\",\n\"osmina\",\n\"osmious\",\n\"osmium\",\n\"osmose\",\n\"osmosis\",\n\"osmotic\",\n\"osmous\",\n\"osmund\",\n\"osone\",\n\"osophy\",\n\"osprey\",\n\"ossal\",\n\"osse\",\n\"ossein\",\n\"osselet\",\n\"osseous\",\n\"ossicle\",\n\"ossific\",\n\"ossify\",\n\"ossuary\",\n\"osteal\",\n\"ostein\",\n\"ostemia\",\n\"ostent\",\n\"osteoid\",\n\"osteoma\",\n\"ostial\",\n\"ostiary\",\n\"ostiate\",\n\"ostiole\",\n\"ostitis\",\n\"ostium\",\n\"ostmark\",\n\"ostosis\",\n\"ostrich\",\n\"otalgia\",\n\"otalgic\",\n\"otalgy\",\n\"otarian\",\n\"otarine\",\n\"otary\",\n\"otate\",\n\"other\",\n\"othmany\",\n\"otiant\",\n\"otiatry\",\n\"otic\",\n\"otidine\",\n\"otidium\",\n\"otiose\",\n\"otitic\",\n\"otitis\",\n\"otkon\",\n\"otocyst\",\n\"otolite\",\n\"otolith\",\n\"otology\",\n\"otosis\",\n\"ototomy\",\n\"ottar\",\n\"otter\",\n\"otterer\",\n\"otto\",\n\"oturia\",\n\"ouabain\",\n\"ouabaio\",\n\"ouabe\",\n\"ouakari\",\n\"ouch\",\n\"ouenite\",\n\"ouf\",\n\"ough\",\n\"ought\",\n\"oughtnt\",\n\"oukia\",\n\"oulap\",\n\"ounce\",\n\"ounds\",\n\"ouphe\",\n\"ouphish\",\n\"our\",\n\"ourie\",\n\"ouroub\",\n\"ours\",\n\"ourself\",\n\"oust\",\n\"ouster\",\n\"out\",\n\"outact\",\n\"outage\",\n\"outarde\",\n\"outask\",\n\"outawe\",\n\"outback\",\n\"outbake\",\n\"outban\",\n\"outbar\",\n\"outbark\",\n\"outbawl\",\n\"outbeam\",\n\"outbear\",\n\"outbeg\",\n\"outbent\",\n\"outbid\",\n\"outblot\",\n\"outblow\",\n\"outbond\",\n\"outbook\",\n\"outborn\",\n\"outbow\",\n\"outbowl\",\n\"outbox\",\n\"outbrag\",\n\"outbray\",\n\"outbred\",\n\"outbud\",\n\"outbulk\",\n\"outburn\",\n\"outbuy\",\n\"outbuzz\",\n\"outby\",\n\"outcant\",\n\"outcase\",\n\"outcast\",\n\"outcity\",\n\"outcome\",\n\"outcrop\",\n\"outcrow\",\n\"outcry\",\n\"outcull\",\n\"outcure\",\n\"outcut\",\n\"outdare\",\n\"outdate\",\n\"outdo\",\n\"outdoer\",\n\"outdoor\",\n\"outdraw\",\n\"outdure\",\n\"outeat\",\n\"outecho\",\n\"outed\",\n\"outedge\",\n\"outen\",\n\"outer\",\n\"outerly\",\n\"outeye\",\n\"outeyed\",\n\"outface\",\n\"outfall\",\n\"outfame\",\n\"outfast\",\n\"outfawn\",\n\"outfeat\",\n\"outfish\",\n\"outfit\",\n\"outflow\",\n\"outflue\",\n\"outflux\",\n\"outfly\",\n\"outfold\",\n\"outfool\",\n\"outfoot\",\n\"outform\",\n\"outfort\",\n\"outgain\",\n\"outgame\",\n\"outgang\",\n\"outgas\",\n\"outgate\",\n\"outgaze\",\n\"outgive\",\n\"outglad\",\n\"outglow\",\n\"outgnaw\",\n\"outgo\",\n\"outgoer\",\n\"outgone\",\n\"outgrin\",\n\"outgrow\",\n\"outgun\",\n\"outgush\",\n\"outhaul\",\n\"outhear\",\n\"outheel\",\n\"outher\",\n\"outhire\",\n\"outhiss\",\n\"outhit\",\n\"outhold\",\n\"outhowl\",\n\"outhue\",\n\"outhunt\",\n\"outhurl\",\n\"outhut\",\n\"outhymn\",\n\"outing\",\n\"outish\",\n\"outjazz\",\n\"outjest\",\n\"outjet\",\n\"outjinx\",\n\"outjump\",\n\"outjut\",\n\"outkick\",\n\"outkill\",\n\"outking\",\n\"outkiss\",\n\"outknee\",\n\"outlaid\",\n\"outland\",\n\"outlash\",\n\"outlast\",\n\"outlaw\",\n\"outlay\",\n\"outlean\",\n\"outleap\",\n\"outler\",\n\"outlet\",\n\"outlie\",\n\"outlier\",\n\"outlimb\",\n\"outlimn\",\n\"outline\",\n\"outlip\",\n\"outlive\",\n\"outlook\",\n\"outlord\",\n\"outlove\",\n\"outlung\",\n\"outly\",\n\"outman\",\n\"outmate\",\n\"outmode\",\n\"outmost\",\n\"outmove\",\n\"outname\",\n\"outness\",\n\"outnook\",\n\"outoven\",\n\"outpace\",\n\"outpage\",\n\"outpart\",\n\"outpass\",\n\"outpath\",\n\"outpay\",\n\"outpeal\",\n\"outpeep\",\n\"outpeer\",\n\"outpick\",\n\"outpipe\",\n\"outpity\",\n\"outplan\",\n\"outplay\",\n\"outplod\",\n\"outplot\",\n\"outpoll\",\n\"outpomp\",\n\"outpop\",\n\"outport\",\n\"outpost\",\n\"outpour\",\n\"outpray\",\n\"outpry\",\n\"outpull\",\n\"outpurl\",\n\"outpush\",\n\"output\",\n\"outrace\",\n\"outrage\",\n\"outrail\",\n\"outrank\",\n\"outrant\",\n\"outrap\",\n\"outrate\",\n\"outrave\",\n\"outray\",\n\"outre\",\n\"outread\",\n\"outrede\",\n\"outrick\",\n\"outride\",\n\"outrig\",\n\"outring\",\n\"outroar\",\n\"outroll\",\n\"outroot\",\n\"outrove\",\n\"outrow\",\n\"outrun\",\n\"outrush\",\n\"outsail\",\n\"outsay\",\n\"outsea\",\n\"outseam\",\n\"outsee\",\n\"outseek\",\n\"outsell\",\n\"outsert\",\n\"outset\",\n\"outshot\",\n\"outshow\",\n\"outshut\",\n\"outside\",\n\"outsift\",\n\"outsigh\",\n\"outsin\",\n\"outsing\",\n\"outsit\",\n\"outsize\",\n\"outskip\",\n\"outsoar\",\n\"outsole\",\n\"outspan\",\n\"outspin\",\n\"outspit\",\n\"outspue\",\n\"outstay\",\n\"outstep\",\n\"outsuck\",\n\"outsulk\",\n\"outsum\",\n\"outswim\",\n\"outtalk\",\n\"outtask\",\n\"outtear\",\n\"outtell\",\n\"outtire\",\n\"outtoil\",\n\"outtop\",\n\"outtrot\",\n\"outturn\",\n\"outvie\",\n\"outvier\",\n\"outvote\",\n\"outwait\",\n\"outwake\",\n\"outwale\",\n\"outwalk\",\n\"outwall\",\n\"outwar\",\n\"outward\",\n\"outwash\",\n\"outwave\",\n\"outwear\",\n\"outweed\",\n\"outweep\",\n\"outwell\",\n\"outwent\",\n\"outwick\",\n\"outwile\",\n\"outwill\",\n\"outwind\",\n\"outwing\",\n\"outwish\",\n\"outwit\",\n\"outwith\",\n\"outwoe\",\n\"outwood\",\n\"outword\",\n\"outwore\",\n\"outwork\",\n\"outworn\",\n\"outyard\",\n\"outyell\",\n\"outyelp\",\n\"outzany\",\n\"ouzel\",\n\"ova\",\n\"oval\",\n\"ovalish\",\n\"ovalize\",\n\"ovally\",\n\"ovaloid\",\n\"ovant\",\n\"ovarial\",\n\"ovarian\",\n\"ovarin\",\n\"ovarium\",\n\"ovary\",\n\"ovate\",\n\"ovated\",\n\"ovately\",\n\"ovation\",\n\"oven\",\n\"ovenful\",\n\"ovenly\",\n\"ovenman\",\n\"over\",\n\"overact\",\n\"overage\",\n\"overall\",\n\"overapt\",\n\"overarm\",\n\"overawe\",\n\"overawn\",\n\"overbet\",\n\"overbid\",\n\"overbig\",\n\"overbit\",\n\"overbow\",\n\"overbuy\",\n\"overby\",\n\"overcap\",\n\"overcow\",\n\"overcoy\",\n\"overcry\",\n\"overcup\",\n\"overcut\",\n\"overdo\",\n\"overdry\",\n\"overdue\",\n\"overdye\",\n\"overeat\",\n\"overegg\",\n\"overeye\",\n\"overfag\",\n\"overfar\",\n\"overfat\",\n\"overfed\",\n\"overfee\",\n\"overfew\",\n\"overfit\",\n\"overfix\",\n\"overfly\",\n\"overget\",\n\"overgo\",\n\"overgod\",\n\"overgun\",\n\"overhit\",\n\"overhot\",\n\"overink\",\n\"overjob\",\n\"overjoy\",\n\"overlap\",\n\"overlax\",\n\"overlay\",\n\"overleg\",\n\"overlie\",\n\"overlip\",\n\"overlow\",\n\"overly\",\n\"overman\",\n\"overmix\",\n\"overnet\",\n\"overnew\",\n\"overpay\",\n\"overpet\",\n\"overply\",\n\"overpot\",\n\"overrim\",\n\"overrun\",\n\"oversad\",\n\"oversea\",\n\"oversee\",\n\"overset\",\n\"oversew\",\n\"oversot\",\n\"oversow\",\n\"overt\",\n\"overtax\",\n\"overtip\",\n\"overtly\",\n\"overtoe\",\n\"overtop\",\n\"overuse\",\n\"overway\",\n\"overweb\",\n\"overwet\",\n\"overwin\",\n\"ovest\",\n\"ovey\",\n\"ovicell\",\n\"ovicide\",\n\"ovicyst\",\n\"oviduct\",\n\"oviform\",\n\"ovigerm\",\n\"ovile\",\n\"ovine\",\n\"ovinia\",\n\"ovipara\",\n\"ovisac\",\n\"ovism\",\n\"ovist\",\n\"ovistic\",\n\"ovocyte\",\n\"ovoid\",\n\"ovoidal\",\n\"ovolo\",\n\"ovology\",\n\"ovular\",\n\"ovulary\",\n\"ovulate\",\n\"ovule\",\n\"ovulist\",\n\"ovum\",\n\"ow\",\n\"owd\",\n\"owe\",\n\"owelty\",\n\"ower\",\n\"owerby\",\n\"owght\",\n\"owing\",\n\"owk\",\n\"owl\",\n\"owldom\",\n\"owler\",\n\"owlery\",\n\"owlet\",\n\"owlhead\",\n\"owling\",\n\"owlish\",\n\"owlism\",\n\"owllike\",\n\"owly\",\n\"own\",\n\"owner\",\n\"ownhood\",\n\"ownness\",\n\"ownself\",\n\"owrehip\",\n\"owrelay\",\n\"owse\",\n\"owsen\",\n\"owser\",\n\"owtchah\",\n\"ox\",\n\"oxacid\",\n\"oxalan\",\n\"oxalate\",\n\"oxalic\",\n\"oxalite\",\n\"oxalyl\",\n\"oxamate\",\n\"oxamic\",\n\"oxamid\",\n\"oxamide\",\n\"oxan\",\n\"oxanate\",\n\"oxane\",\n\"oxanic\",\n\"oxazine\",\n\"oxazole\",\n\"oxbane\",\n\"oxberry\",\n\"oxbird\",\n\"oxbiter\",\n\"oxblood\",\n\"oxbow\",\n\"oxboy\",\n\"oxbrake\",\n\"oxcart\",\n\"oxcheek\",\n\"oxea\",\n\"oxeate\",\n\"oxen\",\n\"oxeote\",\n\"oxer\",\n\"oxetone\",\n\"oxeye\",\n\"oxfly\",\n\"oxgang\",\n\"oxgoad\",\n\"oxhead\",\n\"oxheal\",\n\"oxheart\",\n\"oxhide\",\n\"oxhoft\",\n\"oxhorn\",\n\"oxhouse\",\n\"oxhuvud\",\n\"oxidant\",\n\"oxidase\",\n\"oxidate\",\n\"oxide\",\n\"oxidic\",\n\"oxidize\",\n\"oximate\",\n\"oxime\",\n\"oxland\",\n\"oxlike\",\n\"oxlip\",\n\"oxman\",\n\"oxonic\",\n\"oxonium\",\n\"oxozone\",\n\"oxphony\",\n\"oxreim\",\n\"oxshoe\",\n\"oxskin\",\n\"oxtail\",\n\"oxter\",\n\"oxwort\",\n\"oxy\",\n\"oxyacid\",\n\"oxygas\",\n\"oxygen\",\n\"oxyl\",\n\"oxymel\",\n\"oxyntic\",\n\"oxyopia\",\n\"oxysalt\",\n\"oxytone\",\n\"oyapock\",\n\"oyer\",\n\"oyster\",\n\"ozena\",\n\"ozonate\",\n\"ozone\",\n\"ozoned\",\n\"ozonic\",\n\"ozonide\",\n\"ozonify\",\n\"ozonize\",\n\"ozonous\",\n\"ozophen\",\n\"ozotype\",\n\"p\",\n\"pa\",\n\"paal\",\n\"paar\",\n\"paauw\",\n\"pabble\",\n\"pablo\",\n\"pabouch\",\n\"pabular\",\n\"pabulum\",\n\"pac\",\n\"paca\",\n\"pacable\",\n\"pacate\",\n\"pacay\",\n\"pacaya\",\n\"pace\",\n\"paced\",\n\"pacer\",\n\"pachak\",\n\"pachisi\",\n\"pacific\",\n\"pacify\",\n\"pack\",\n\"package\",\n\"packer\",\n\"packery\",\n\"packet\",\n\"packly\",\n\"packman\",\n\"packway\",\n\"paco\",\n\"pact\",\n\"paction\",\n\"pad\",\n\"padder\",\n\"padding\",\n\"paddle\",\n\"paddled\",\n\"paddler\",\n\"paddock\",\n\"paddy\",\n\"padella\",\n\"padfoot\",\n\"padge\",\n\"padle\",\n\"padlike\",\n\"padlock\",\n\"padnag\",\n\"padre\",\n\"padtree\",\n\"paean\",\n\"paegel\",\n\"paegle\",\n\"paenula\",\n\"paeon\",\n\"paeonic\",\n\"paga\",\n\"pagan\",\n\"paganic\",\n\"paganly\",\n\"paganry\",\n\"page\",\n\"pageant\",\n\"pagedom\",\n\"pageful\",\n\"pager\",\n\"pagina\",\n\"paginal\",\n\"pagoda\",\n\"pagrus\",\n\"pagurid\",\n\"pagus\",\n\"pah\",\n\"paha\",\n\"pahi\",\n\"pahlavi\",\n\"pahmi\",\n\"paho\",\n\"pahutan\",\n\"paigle\",\n\"paik\",\n\"pail\",\n\"pailful\",\n\"pailou\",\n\"pain\",\n\"pained\",\n\"painful\",\n\"paining\",\n\"paint\",\n\"painted\",\n\"painter\",\n\"painty\",\n\"paip\",\n\"pair\",\n\"paired\",\n\"pairer\",\n\"pais\",\n\"paisa\",\n\"paiwari\",\n\"pajama\",\n\"pajock\",\n\"pakchoi\",\n\"pakeha\",\n\"paktong\",\n\"pal\",\n\"palace\",\n\"palaced\",\n\"paladin\",\n\"palaite\",\n\"palama\",\n\"palame\",\n\"palanka\",\n\"palar\",\n\"palas\",\n\"palatal\",\n\"palate\",\n\"palated\",\n\"palatic\",\n\"palaver\",\n\"palay\",\n\"palazzi\",\n\"palch\",\n\"pale\",\n\"palea\",\n\"paleate\",\n\"paled\",\n\"palely\",\n\"paleola\",\n\"paler\",\n\"palet\",\n\"paletot\",\n\"palette\",\n\"paletz\",\n\"palfrey\",\n\"palgat\",\n\"pali\",\n\"palikar\",\n\"palila\",\n\"palinal\",\n\"paling\",\n\"palisfy\",\n\"palish\",\n\"palkee\",\n\"pall\",\n\"palla\",\n\"pallae\",\n\"pallah\",\n\"pallall\",\n\"palled\",\n\"pallet\",\n\"palli\",\n\"pallial\",\n\"pallid\",\n\"pallion\",\n\"pallium\",\n\"pallone\",\n\"pallor\",\n\"pally\",\n\"palm\",\n\"palma\",\n\"palmad\",\n\"palmar\",\n\"palmary\",\n\"palmate\",\n\"palmed\",\n\"palmer\",\n\"palmery\",\n\"palmful\",\n\"palmist\",\n\"palmite\",\n\"palmito\",\n\"palmo\",\n\"palmula\",\n\"palmus\",\n\"palmy\",\n\"palmyra\",\n\"palolo\",\n\"palp\",\n\"palpal\",\n\"palpate\",\n\"palped\",\n\"palpi\",\n\"palpon\",\n\"palpus\",\n\"palsied\",\n\"palster\",\n\"palsy\",\n\"palt\",\n\"palter\",\n\"paltry\",\n\"paludal\",\n\"paludic\",\n\"palule\",\n\"palulus\",\n\"palus\",\n\"paly\",\n\"pam\",\n\"pament\",\n\"pamment\",\n\"pampas\",\n\"pampean\",\n\"pamper\",\n\"pampero\",\n\"pampre\",\n\"pan\",\n\"panace\",\n\"panacea\",\n\"panache\",\n\"panada\",\n\"panade\",\n\"panama\",\n\"panaris\",\n\"panary\",\n\"panax\",\n\"pancake\",\n\"pand\",\n\"panda\",\n\"pandal\",\n\"pandan\",\n\"pandect\",\n\"pandemy\",\n\"pander\",\n\"pandita\",\n\"pandle\",\n\"pandora\",\n\"pandour\",\n\"pandrop\",\n\"pandura\",\n\"pandy\",\n\"pane\",\n\"paned\",\n\"paneity\",\n\"panel\",\n\"panela\",\n\"paneler\",\n\"panfil\",\n\"panfish\",\n\"panful\",\n\"pang\",\n\"pangamy\",\n\"pangane\",\n\"pangen\",\n\"pangene\",\n\"pangful\",\n\"pangi\",\n\"panhead\",\n\"panic\",\n\"panical\",\n\"panicky\",\n\"panicle\",\n\"panisc\",\n\"panisca\",\n\"panisic\",\n\"pank\",\n\"pankin\",\n\"panman\",\n\"panmixy\",\n\"panmug\",\n\"pannade\",\n\"pannage\",\n\"pannam\",\n\"panne\",\n\"pannel\",\n\"panner\",\n\"pannery\",\n\"pannier\",\n\"panning\",\n\"pannose\",\n\"pannum\",\n\"pannus\",\n\"panocha\",\n\"panoche\",\n\"panoply\",\n\"panoram\",\n\"panse\",\n\"panside\",\n\"pansied\",\n\"pansy\",\n\"pant\",\n\"pantas\",\n\"panter\",\n\"panther\",\n\"pantie\",\n\"panties\",\n\"pantile\",\n\"panting\",\n\"pantle\",\n\"pantler\",\n\"panto\",\n\"pantod\",\n\"panton\",\n\"pantoon\",\n\"pantoum\",\n\"pantry\",\n\"pants\",\n\"pantun\",\n\"panty\",\n\"panung\",\n\"panurgy\",\n\"panyar\",\n\"paolo\",\n\"paon\",\n\"pap\",\n\"papa\",\n\"papable\",\n\"papabot\",\n\"papacy\",\n\"papain\",\n\"papal\",\n\"papally\",\n\"papalty\",\n\"papane\",\n\"papaw\",\n\"papaya\",\n\"papboat\",\n\"pape\",\n\"paper\",\n\"papered\",\n\"paperer\",\n\"papern\",\n\"papery\",\n\"papess\",\n\"papey\",\n\"papilla\",\n\"papion\",\n\"papish\",\n\"papism\",\n\"papist\",\n\"papize\",\n\"papless\",\n\"papmeat\",\n\"papoose\",\n\"pappi\",\n\"pappose\",\n\"pappox\",\n\"pappus\",\n\"pappy\",\n\"papreg\",\n\"paprica\",\n\"paprika\",\n\"papula\",\n\"papular\",\n\"papule\",\n\"papyr\",\n\"papyral\",\n\"papyri\",\n\"papyrin\",\n\"papyrus\",\n\"paquet\",\n\"par\",\n\"para\",\n\"parable\",\n\"paracme\",\n\"parade\",\n\"parader\",\n\"parado\",\n\"parados\",\n\"paradox\",\n\"parafle\",\n\"parage\",\n\"paragon\",\n\"parah\",\n\"paraiba\",\n\"parale\",\n\"param\",\n\"paramo\",\n\"parang\",\n\"parao\",\n\"parapet\",\n\"paraph\",\n\"parapod\",\n\"pararek\",\n\"parasol\",\n\"paraspy\",\n\"parate\",\n\"paraxon\",\n\"parbake\",\n\"parboil\",\n\"parcel\",\n\"parch\",\n\"parcher\",\n\"parchy\",\n\"parcook\",\n\"pard\",\n\"pardao\",\n\"parded\",\n\"pardesi\",\n\"pardine\",\n\"pardner\",\n\"pardo\",\n\"pardon\",\n\"pare\",\n\"parel\",\n\"parella\",\n\"paren\",\n\"parent\",\n\"parer\",\n\"paresis\",\n\"paretic\",\n\"parfait\",\n\"pargana\",\n\"parge\",\n\"parget\",\n\"pargo\",\n\"pari\",\n\"pariah\",\n\"parial\",\n\"parian\",\n\"paries\",\n\"parify\",\n\"parilla\",\n\"parine\",\n\"paring\",\n\"parish\",\n\"parisis\",\n\"parison\",\n\"parity\",\n\"park\",\n\"parka\",\n\"parkee\",\n\"parker\",\n\"parkin\",\n\"parking\",\n\"parkish\",\n\"parkway\",\n\"parky\",\n\"parlay\",\n\"parle\",\n\"parley\",\n\"parling\",\n\"parlish\",\n\"parlor\",\n\"parlous\",\n\"parly\",\n\"parma\",\n\"parmak\",\n\"parnas\",\n\"parnel\",\n\"paroch\",\n\"parode\",\n\"parodic\",\n\"parodos\",\n\"parody\",\n\"paroecy\",\n\"parol\",\n\"parole\",\n\"parolee\",\n\"paroli\",\n\"paronym\",\n\"parotic\",\n\"parotid\",\n\"parotis\",\n\"parous\",\n\"parpal\",\n\"parquet\",\n\"parr\",\n\"parrel\",\n\"parrier\",\n\"parrock\",\n\"parrot\",\n\"parroty\",\n\"parry\",\n\"parse\",\n\"parsec\",\n\"parser\",\n\"parsley\",\n\"parsnip\",\n\"parson\",\n\"parsony\",\n\"part\",\n\"partake\",\n\"partan\",\n\"parted\",\n\"parter\",\n\"partial\",\n\"partile\",\n\"partite\",\n\"partlet\",\n\"partly\",\n\"partner\",\n\"parto\",\n\"partook\",\n\"parture\",\n\"party\",\n\"parulis\",\n\"parure\",\n\"paruria\",\n\"parvenu\",\n\"parvis\",\n\"parvule\",\n\"pasan\",\n\"pasang\",\n\"paschal\",\n\"pascual\",\n\"pash\",\n\"pasha\",\n\"pashm\",\n\"pasi\",\n\"pasmo\",\n\"pasquil\",\n\"pasquin\",\n\"pass\",\n\"passade\",\n\"passado\",\n\"passage\",\n\"passant\",\n\"passe\",\n\"passee\",\n\"passen\",\n\"passer\",\n\"passewa\",\n\"passing\",\n\"passion\",\n\"passir\",\n\"passive\",\n\"passkey\",\n\"passman\",\n\"passo\",\n\"passout\",\n\"passus\",\n\"passway\",\n\"past\",\n\"paste\",\n\"pasted\",\n\"pastel\",\n\"paster\",\n\"pastern\",\n\"pasteur\",\n\"pastil\",\n\"pastile\",\n\"pastime\",\n\"pasting\",\n\"pastor\",\n\"pastose\",\n\"pastry\",\n\"pasture\",\n\"pasty\",\n\"pasul\",\n\"pat\",\n\"pata\",\n\"pataca\",\n\"patacao\",\n\"pataco\",\n\"patagon\",\n\"pataka\",\n\"patamar\",\n\"patao\",\n\"patapat\",\n\"pataque\",\n\"patas\",\n\"patball\",\n\"patch\",\n\"patcher\",\n\"patchy\",\n\"pate\",\n\"patefy\",\n\"patel\",\n\"patella\",\n\"paten\",\n\"patency\",\n\"patener\",\n\"patent\",\n\"pater\",\n\"patera\",\n\"patesi\",\n\"path\",\n\"pathed\",\n\"pathema\",\n\"pathic\",\n\"pathlet\",\n\"pathos\",\n\"pathway\",\n\"pathy\",\n\"patible\",\n\"patient\",\n\"patina\",\n\"patine\",\n\"patined\",\n\"patio\",\n\"patly\",\n\"patness\",\n\"pato\",\n\"patois\",\n\"patola\",\n\"patonce\",\n\"patria\",\n\"patrial\",\n\"patrice\",\n\"patrico\",\n\"patrin\",\n\"patriot\",\n\"patrist\",\n\"patrix\",\n\"patrol\",\n\"patron\",\n\"patroon\",\n\"patta\",\n\"patte\",\n\"pattee\",\n\"patten\",\n\"patter\",\n\"pattern\",\n\"pattu\",\n\"patty\",\n\"patu\",\n\"patwari\",\n\"paty\",\n\"pau\",\n\"paucify\",\n\"paucity\",\n\"paughty\",\n\"paukpan\",\n\"paular\",\n\"paulie\",\n\"paulin\",\n\"paunch\",\n\"paunchy\",\n\"paup\",\n\"pauper\",\n\"pausal\",\n\"pause\",\n\"pauser\",\n\"paussid\",\n\"paut\",\n\"pauxi\",\n\"pavage\",\n\"pavan\",\n\"pavane\",\n\"pave\",\n\"paver\",\n\"pavid\",\n\"pavier\",\n\"paving\",\n\"pavior\",\n\"paviour\",\n\"pavis\",\n\"paviser\",\n\"pavisor\",\n\"pavy\",\n\"paw\",\n\"pawdite\",\n\"pawer\",\n\"pawing\",\n\"pawk\",\n\"pawkery\",\n\"pawkily\",\n\"pawkrie\",\n\"pawky\",\n\"pawl\",\n\"pawn\",\n\"pawnage\",\n\"pawnee\",\n\"pawner\",\n\"pawnie\",\n\"pawnor\",\n\"pawpaw\",\n\"pax\",\n\"paxilla\",\n\"paxiuba\",\n\"paxwax\",\n\"pay\",\n\"payable\",\n\"payably\",\n\"payday\",\n\"payed\",\n\"payee\",\n\"payeny\",\n\"payer\",\n\"paying\",\n\"payment\",\n\"paynim\",\n\"payoff\",\n\"payong\",\n\"payor\",\n\"payroll\",\n\"pea\",\n\"peace\",\n\"peach\",\n\"peachen\",\n\"peacher\",\n\"peachy\",\n\"peacoat\",\n\"peacock\",\n\"peacod\",\n\"peafowl\",\n\"peag\",\n\"peage\",\n\"peahen\",\n\"peai\",\n\"peaiism\",\n\"peak\",\n\"peaked\",\n\"peaker\",\n\"peakily\",\n\"peaking\",\n\"peakish\",\n\"peaky\",\n\"peal\",\n\"pealike\",\n\"pean\",\n\"peanut\",\n\"pear\",\n\"pearl\",\n\"pearled\",\n\"pearler\",\n\"pearlet\",\n\"pearlin\",\n\"pearly\",\n\"peart\",\n\"pearten\",\n\"peartly\",\n\"peasant\",\n\"peasen\",\n\"peason\",\n\"peasy\",\n\"peat\",\n\"peatery\",\n\"peatman\",\n\"peaty\",\n\"peavey\",\n\"peavy\",\n\"peba\",\n\"pebble\",\n\"pebbled\",\n\"pebbly\",\n\"pebrine\",\n\"pecan\",\n\"peccant\",\n\"peccary\",\n\"peccavi\",\n\"pech\",\n\"pecht\",\n\"pecite\",\n\"peck\",\n\"pecked\",\n\"pecker\",\n\"pecket\",\n\"peckful\",\n\"peckish\",\n\"peckle\",\n\"peckled\",\n\"peckly\",\n\"pecky\",\n\"pectase\",\n\"pectate\",\n\"pecten\",\n\"pectic\",\n\"pectin\",\n\"pectize\",\n\"pectora\",\n\"pectose\",\n\"pectous\",\n\"pectus\",\n\"ped\",\n\"peda\",\n\"pedage\",\n\"pedagog\",\n\"pedal\",\n\"pedaler\",\n\"pedant\",\n\"pedary\",\n\"pedate\",\n\"pedated\",\n\"pedder\",\n\"peddle\",\n\"peddler\",\n\"pedee\",\n\"pedes\",\n\"pedesis\",\n\"pedicab\",\n\"pedicel\",\n\"pedicle\",\n\"pedion\",\n\"pedlar\",\n\"pedlary\",\n\"pedocal\",\n\"pedrail\",\n\"pedrero\",\n\"pedro\",\n\"pedule\",\n\"pedum\",\n\"pee\",\n\"peed\",\n\"peek\",\n\"peel\",\n\"peele\",\n\"peeled\",\n\"peeler\",\n\"peeling\",\n\"peelman\",\n\"peen\",\n\"peenge\",\n\"peeoy\",\n\"peep\",\n\"peeper\",\n\"peepeye\",\n\"peepy\",\n\"peer\",\n\"peerage\",\n\"peerdom\",\n\"peeress\",\n\"peerie\",\n\"peerly\",\n\"peery\",\n\"peesash\",\n\"peeve\",\n\"peeved\",\n\"peever\",\n\"peevish\",\n\"peewee\",\n\"peg\",\n\"pega\",\n\"pegall\",\n\"pegasid\",\n\"pegbox\",\n\"pegged\",\n\"pegger\",\n\"pegging\",\n\"peggle\",\n\"peggy\",\n\"pegless\",\n\"peglet\",\n\"peglike\",\n\"pegman\",\n\"pegwood\",\n\"peho\",\n\"peine\",\n\"peisage\",\n\"peise\",\n\"peiser\",\n\"peixere\",\n\"pekan\",\n\"pekin\",\n\"pekoe\",\n\"peladic\",\n\"pelage\",\n\"pelagic\",\n\"pelamyd\",\n\"pelanos\",\n\"pelean\",\n\"pelecan\",\n\"pelf\",\n\"pelican\",\n\"pelick\",\n\"pelike\",\n\"peliom\",\n\"pelioma\",\n\"pelisse\",\n\"pelite\",\n\"pelitic\",\n\"pell\",\n\"pellage\",\n\"pellar\",\n\"pellard\",\n\"pellas\",\n\"pellate\",\n\"peller\",\n\"pellet\",\n\"pellety\",\n\"pellile\",\n\"pellock\",\n\"pelmet\",\n\"pelon\",\n\"peloria\",\n\"peloric\",\n\"pelorus\",\n\"pelota\",\n\"peloton\",\n\"pelt\",\n\"pelta\",\n\"peltast\",\n\"peltate\",\n\"pelter\",\n\"pelting\",\n\"peltry\",\n\"pelu\",\n\"peludo\",\n\"pelves\",\n\"pelvic\",\n\"pelvis\",\n\"pembina\",\n\"pemican\",\n\"pen\",\n\"penal\",\n\"penally\",\n\"penalty\",\n\"penance\",\n\"penang\",\n\"penates\",\n\"penbard\",\n\"pence\",\n\"pencel\",\n\"pencil\",\n\"pend\",\n\"penda\",\n\"pendant\",\n\"pendent\",\n\"pending\",\n\"pendle\",\n\"pendom\",\n\"pendule\",\n\"penfold\",\n\"penful\",\n\"pengo\",\n\"penguin\",\n\"penhead\",\n\"penial\",\n\"penide\",\n\"penile\",\n\"penis\",\n\"penk\",\n\"penlike\",\n\"penman\",\n\"penna\",\n\"pennae\",\n\"pennage\",\n\"pennant\",\n\"pennate\",\n\"penner\",\n\"pennet\",\n\"penni\",\n\"pennia\",\n\"pennied\",\n\"pennill\",\n\"penning\",\n\"pennon\",\n\"penny\",\n\"penrack\",\n\"penship\",\n\"pensile\",\n\"pension\",\n\"pensive\",\n\"penster\",\n\"pensum\",\n\"pensy\",\n\"pent\",\n\"penta\",\n\"pentace\",\n\"pentad\",\n\"pentail\",\n\"pentane\",\n\"pentene\",\n\"pentine\",\n\"pentit\",\n\"pentite\",\n\"pentode\",\n\"pentoic\",\n\"pentol\",\n\"pentose\",\n\"pentrit\",\n\"pentyl\",\n\"pentyne\",\n\"penuchi\",\n\"penult\",\n\"penury\",\n\"peon\",\n\"peonage\",\n\"peonism\",\n\"peony\",\n\"people\",\n\"peopler\",\n\"peoplet\",\n\"peotomy\",\n\"pep\",\n\"pepful\",\n\"pepino\",\n\"peplos\",\n\"peplum\",\n\"peplus\",\n\"pepo\",\n\"pepper\",\n\"peppery\",\n\"peppily\",\n\"peppin\",\n\"peppy\",\n\"pepsin\",\n\"pepsis\",\n\"peptic\",\n\"peptide\",\n\"peptize\",\n\"peptone\",\n\"per\",\n\"peracid\",\n\"peract\",\n\"perbend\",\n\"percale\",\n\"percent\",\n\"percept\",\n\"perch\",\n\"percha\",\n\"percher\",\n\"percid\",\n\"percoct\",\n\"percoid\",\n\"percur\",\n\"percuss\",\n\"perdu\",\n\"perdure\",\n\"pereion\",\n\"pereira\",\n\"peres\",\n\"perfect\",\n\"perfidy\",\n\"perform\",\n\"perfume\",\n\"perfumy\",\n\"perfuse\",\n\"pergola\",\n\"perhaps\",\n\"peri\",\n\"periapt\",\n\"peridot\",\n\"perigee\",\n\"perigon\",\n\"peril\",\n\"perine\",\n\"period\",\n\"periost\",\n\"perique\",\n\"perish\",\n\"perit\",\n\"perite\",\n\"periwig\",\n\"perjink\",\n\"perjure\",\n\"perjury\",\n\"perk\",\n\"perkily\",\n\"perkin\",\n\"perking\",\n\"perkish\",\n\"perky\",\n\"perle\",\n\"perlid\",\n\"perlite\",\n\"perloir\",\n\"perm\",\n\"permit\",\n\"permute\",\n\"pern\",\n\"pernine\",\n\"pernor\",\n\"pernyi\",\n\"peroba\",\n\"peropod\",\n\"peropus\",\n\"peroral\",\n\"perosis\",\n\"perotic\",\n\"peroxy\",\n\"peroxyl\",\n\"perpend\",\n\"perpera\",\n\"perplex\",\n\"perrier\",\n\"perron\",\n\"perry\",\n\"persalt\",\n\"perse\",\n\"persico\",\n\"persis\",\n\"persist\",\n\"person\",\n\"persona\",\n\"pert\",\n\"pertain\",\n\"perten\",\n\"pertish\",\n\"pertly\",\n\"perturb\",\n\"pertuse\",\n\"perty\",\n\"peruke\",\n\"perula\",\n\"perule\",\n\"perusal\",\n\"peruse\",\n\"peruser\",\n\"pervade\",\n\"pervert\",\n\"pes\",\n\"pesa\",\n\"pesade\",\n\"pesage\",\n\"peseta\",\n\"peshkar\",\n\"peshwa\",\n\"peskily\",\n\"pesky\",\n\"peso\",\n\"pess\",\n\"pessary\",\n\"pest\",\n\"peste\",\n\"pester\",\n\"pestful\",\n\"pestify\",\n\"pestle\",\n\"pet\",\n\"petal\",\n\"petaled\",\n\"petalon\",\n\"petaly\",\n\"petard\",\n\"petary\",\n\"petasos\",\n\"petasus\",\n\"petcock\",\n\"pete\",\n\"peteca\",\n\"peteman\",\n\"peter\",\n\"petful\",\n\"petiole\",\n\"petit\",\n\"petite\",\n\"petitor\",\n\"petkin\",\n\"petling\",\n\"peto\",\n\"petrary\",\n\"petre\",\n\"petrean\",\n\"petrel\",\n\"petrie\",\n\"petrify\",\n\"petrol\",\n\"petrosa\",\n\"petrous\",\n\"petted\",\n\"petter\",\n\"pettily\",\n\"pettish\",\n\"pettle\",\n\"petty\",\n\"petune\",\n\"petwood\",\n\"petzite\",\n\"peuhl\",\n\"pew\",\n\"pewage\",\n\"pewdom\",\n\"pewee\",\n\"pewful\",\n\"pewing\",\n\"pewit\",\n\"pewless\",\n\"pewmate\",\n\"pewter\",\n\"pewtery\",\n\"pewy\",\n\"peyote\",\n\"peyotl\",\n\"peyton\",\n\"peytrel\",\n\"pfennig\",\n\"pfui\",\n\"pfund\",\n\"phacoid\",\n\"phaeism\",\n\"phaeton\",\n\"phage\",\n\"phalanx\",\n\"phalera\",\n\"phallic\",\n\"phallin\",\n\"phallus\",\n\"phanic\",\n\"phano\",\n\"phantom\",\n\"phare\",\n\"pharmic\",\n\"pharos\",\n\"pharynx\",\n\"phase\",\n\"phaseal\",\n\"phasemy\",\n\"phases\",\n\"phasic\",\n\"phasis\",\n\"phasm\",\n\"phasma\",\n\"phasmid\",\n\"pheal\",\n\"phellem\",\n\"phemic\",\n\"phenate\",\n\"phene\",\n\"phenene\",\n\"phenic\",\n\"phenin\",\n\"phenol\",\n\"phenyl\",\n\"pheon\",\n\"phew\",\n\"phi\",\n\"phial\",\n\"phiale\",\n\"philter\",\n\"philtra\",\n\"phit\",\n\"phiz\",\n\"phizes\",\n\"phizog\",\n\"phlegm\",\n\"phlegma\",\n\"phlegmy\",\n\"phloem\",\n\"phloxin\",\n\"pho\",\n\"phobiac\",\n\"phobic\",\n\"phobism\",\n\"phobist\",\n\"phoby\",\n\"phoca\",\n\"phocal\",\n\"phocid\",\n\"phocine\",\n\"phocoid\",\n\"phoebe\",\n\"phoenix\",\n\"phoh\",\n\"pholad\",\n\"pholcid\",\n\"pholido\",\n\"phon\",\n\"phonal\",\n\"phonate\",\n\"phone\",\n\"phoneme\",\n\"phonic\",\n\"phonics\",\n\"phonism\",\n\"phono\",\n\"phony\",\n\"phoo\",\n\"phoresy\",\n\"phoria\",\n\"phorid\",\n\"phorone\",\n\"phos\",\n\"phose\",\n\"phosis\",\n\"phospho\",\n\"phossy\",\n\"phot\",\n\"photal\",\n\"photic\",\n\"photics\",\n\"photism\",\n\"photo\",\n\"photoma\",\n\"photon\",\n\"phragma\",\n\"phrasal\",\n\"phrase\",\n\"phraser\",\n\"phrasy\",\n\"phrator\",\n\"phratry\",\n\"phrenic\",\n\"phrynid\",\n\"phrynin\",\n\"phthor\",\n\"phu\",\n\"phugoid\",\n\"phulwa\",\n\"phut\",\n\"phycite\",\n\"phyla\",\n\"phyle\",\n\"phylic\",\n\"phyllin\",\n\"phylon\",\n\"phylum\",\n\"phyma\",\n\"phymata\",\n\"physic\",\n\"physics\",\n\"phytase\",\n\"phytic\",\n\"phytin\",\n\"phytoid\",\n\"phytol\",\n\"phytoma\",\n\"phytome\",\n\"phyton\",\n\"phytyl\",\n\"pi\",\n\"pia\",\n\"piaba\",\n\"piacaba\",\n\"piacle\",\n\"piaffe\",\n\"piaffer\",\n\"pial\",\n\"pialyn\",\n\"pian\",\n\"pianic\",\n\"pianino\",\n\"pianism\",\n\"pianist\",\n\"piannet\",\n\"piano\",\n\"pianola\",\n\"piaster\",\n\"piastre\",\n\"piation\",\n\"piazine\",\n\"piazza\",\n\"pibcorn\",\n\"pibroch\",\n\"pic\",\n\"pica\",\n\"picador\",\n\"pical\",\n\"picamar\",\n\"picara\",\n\"picarel\",\n\"picaro\",\n\"picary\",\n\"piccolo\",\n\"pice\",\n\"picene\",\n\"piceous\",\n\"pichi\",\n\"picine\",\n\"pick\",\n\"pickage\",\n\"pickax\",\n\"picked\",\n\"pickee\",\n\"pickeer\",\n\"picker\",\n\"pickery\",\n\"picket\",\n\"pickle\",\n\"pickler\",\n\"pickman\",\n\"pickmaw\",\n\"pickup\",\n\"picky\",\n\"picnic\",\n\"pico\",\n\"picoid\",\n\"picot\",\n\"picotah\",\n\"picotee\",\n\"picra\",\n\"picrate\",\n\"picric\",\n\"picrite\",\n\"picrol\",\n\"picryl\",\n\"pict\",\n\"picture\",\n\"pictury\",\n\"picuda\",\n\"picudo\",\n\"picul\",\n\"piculet\",\n\"pidan\",\n\"piddle\",\n\"piddler\",\n\"piddock\",\n\"pidgin\",\n\"pie\",\n\"piebald\",\n\"piece\",\n\"piecen\",\n\"piecer\",\n\"piecing\",\n\"pied\",\n\"piedly\",\n\"pieless\",\n\"pielet\",\n\"pielum\",\n\"piemag\",\n\"pieman\",\n\"pien\",\n\"piend\",\n\"piepan\",\n\"pier\",\n\"pierage\",\n\"pierce\",\n\"pierced\",\n\"piercel\",\n\"piercer\",\n\"pierid\",\n\"pierine\",\n\"pierrot\",\n\"pieshop\",\n\"piet\",\n\"pietas\",\n\"pietic\",\n\"pietism\",\n\"pietist\",\n\"pietose\",\n\"piety\",\n\"piewife\",\n\"piewipe\",\n\"piezo\",\n\"piff\",\n\"piffle\",\n\"piffler\",\n\"pifine\",\n\"pig\",\n\"pigdan\",\n\"pigdom\",\n\"pigeon\",\n\"pigface\",\n\"pigfish\",\n\"pigfoot\",\n\"pigful\",\n\"piggery\",\n\"piggin\",\n\"pigging\",\n\"piggish\",\n\"piggle\",\n\"piggy\",\n\"pighead\",\n\"pigherd\",\n\"pightle\",\n\"pigless\",\n\"piglet\",\n\"pigling\",\n\"pigly\",\n\"pigman\",\n\"pigment\",\n\"pignon\",\n\"pignus\",\n\"pignut\",\n\"pigpen\",\n\"pigroot\",\n\"pigskin\",\n\"pigsney\",\n\"pigsty\",\n\"pigtail\",\n\"pigwash\",\n\"pigweed\",\n\"pigyard\",\n\"piitis\",\n\"pik\",\n\"pika\",\n\"pike\",\n\"piked\",\n\"pikel\",\n\"pikelet\",\n\"pikeman\",\n\"piker\",\n\"pikey\",\n\"piki\",\n\"piking\",\n\"pikle\",\n\"piky\",\n\"pilage\",\n\"pilapil\",\n\"pilar\",\n\"pilary\",\n\"pilau\",\n\"pilaued\",\n\"pilch\",\n\"pilcher\",\n\"pilcorn\",\n\"pilcrow\",\n\"pile\",\n\"pileata\",\n\"pileate\",\n\"piled\",\n\"pileous\",\n\"piler\",\n\"piles\",\n\"pileus\",\n\"pilfer\",\n\"pilger\",\n\"pilgrim\",\n\"pili\",\n\"pilifer\",\n\"piligan\",\n\"pilikai\",\n\"pilin\",\n\"piline\",\n\"piling\",\n\"pilkins\",\n\"pill\",\n\"pillage\",\n\"pillar\",\n\"pillary\",\n\"pillas\",\n\"pillbox\",\n\"pilled\",\n\"pillet\",\n\"pilleus\",\n\"pillion\",\n\"pillory\",\n\"pillow\",\n\"pillowy\",\n\"pilm\",\n\"pilmy\",\n\"pilon\",\n\"pilori\",\n\"pilose\",\n\"pilosis\",\n\"pilot\",\n\"pilotee\",\n\"pilotry\",\n\"pilous\",\n\"pilpul\",\n\"piltock\",\n\"pilula\",\n\"pilular\",\n\"pilule\",\n\"pilum\",\n\"pilus\",\n\"pily\",\n\"pimaric\",\n\"pimelic\",\n\"pimento\",\n\"pimlico\",\n\"pimola\",\n\"pimp\",\n\"pimpery\",\n\"pimping\",\n\"pimpish\",\n\"pimple\",\n\"pimpled\",\n\"pimplo\",\n\"pimploe\",\n\"pimply\",\n\"pin\",\n\"pina\",\n\"pinaces\",\n\"pinacle\",\n\"pinacol\",\n\"pinang\",\n\"pinax\",\n\"pinball\",\n\"pinbone\",\n\"pinbush\",\n\"pincase\",\n\"pincer\",\n\"pincers\",\n\"pinch\",\n\"pinche\",\n\"pinched\",\n\"pinchem\",\n\"pincher\",\n\"pind\",\n\"pinda\",\n\"pinder\",\n\"pindy\",\n\"pine\",\n\"pineal\",\n\"pined\",\n\"pinene\",\n\"piner\",\n\"pinery\",\n\"pinesap\",\n\"pinetum\",\n\"piney\",\n\"pinfall\",\n\"pinfish\",\n\"pinfold\",\n\"ping\",\n\"pingle\",\n\"pingler\",\n\"pingue\",\n\"pinguid\",\n\"pinguin\",\n\"pinhead\",\n\"pinhold\",\n\"pinhole\",\n\"pinhook\",\n\"pinic\",\n\"pining\",\n\"pinion\",\n\"pinite\",\n\"pinitol\",\n\"pinjane\",\n\"pinjra\",\n\"pink\",\n\"pinked\",\n\"pinkeen\",\n\"pinken\",\n\"pinker\",\n\"pinkeye\",\n\"pinkie\",\n\"pinkify\",\n\"pinkily\",\n\"pinking\",\n\"pinkish\",\n\"pinkly\",\n\"pinky\",\n\"pinless\",\n\"pinlock\",\n\"pinna\",\n\"pinnace\",\n\"pinnae\",\n\"pinnal\",\n\"pinnate\",\n\"pinned\",\n\"pinnel\",\n\"pinner\",\n\"pinnet\",\n\"pinning\",\n\"pinnock\",\n\"pinnula\",\n\"pinnule\",\n\"pinny\",\n\"pino\",\n\"pinole\",\n\"pinolia\",\n\"pinolin\",\n\"pinon\",\n\"pinonic\",\n\"pinrail\",\n\"pinsons\",\n\"pint\",\n\"pinta\",\n\"pintado\",\n\"pintail\",\n\"pintano\",\n\"pinte\",\n\"pintle\",\n\"pinto\",\n\"pintura\",\n\"pinulus\",\n\"pinweed\",\n\"pinwing\",\n\"pinwork\",\n\"pinworm\",\n\"piny\",\n\"pinyl\",\n\"pinyon\",\n\"pioneer\",\n\"pioted\",\n\"piotine\",\n\"piotty\",\n\"pioury\",\n\"pious\",\n\"piously\",\n\"pip\",\n\"pipa\",\n\"pipage\",\n\"pipal\",\n\"pipe\",\n\"pipeage\",\n\"piped\",\n\"pipeful\",\n\"pipeman\",\n\"piper\",\n\"piperic\",\n\"piperly\",\n\"piperno\",\n\"pipery\",\n\"pipet\",\n\"pipette\",\n\"pipi\",\n\"piping\",\n\"pipiri\",\n\"pipit\",\n\"pipkin\",\n\"pipless\",\n\"pipped\",\n\"pipper\",\n\"pippin\",\n\"pippy\",\n\"piprine\",\n\"piproid\",\n\"pipy\",\n\"piquant\",\n\"pique\",\n\"piquet\",\n\"piquia\",\n\"piqure\",\n\"pir\",\n\"piracy\",\n\"piragua\",\n\"piranha\",\n\"pirate\",\n\"piraty\",\n\"pirl\",\n\"pirn\",\n\"pirner\",\n\"pirnie\",\n\"pirny\",\n\"pirogue\",\n\"pirol\",\n\"pirr\",\n\"pirrmaw\",\n\"pisaca\",\n\"pisang\",\n\"pisay\",\n\"piscary\",\n\"piscian\",\n\"piscina\",\n\"piscine\",\n\"pisco\",\n\"pise\",\n\"pish\",\n\"pishaug\",\n\"pishu\",\n\"pisk\",\n\"pisky\",\n\"pismire\",\n\"piso\",\n\"piss\",\n\"pissant\",\n\"pist\",\n\"pistic\",\n\"pistil\",\n\"pistle\",\n\"pistol\",\n\"pistole\",\n\"piston\",\n\"pistrix\",\n\"pit\",\n\"pita\",\n\"pitanga\",\n\"pitapat\",\n\"pitarah\",\n\"pitau\",\n\"pitaya\",\n\"pitch\",\n\"pitcher\",\n\"pitchi\",\n\"pitchy\",\n\"piteous\",\n\"pitfall\",\n\"pith\",\n\"pithful\",\n\"pithily\",\n\"pithole\",\n\"pithos\",\n\"pithy\",\n\"pitier\",\n\"pitiful\",\n\"pitless\",\n\"pitlike\",\n\"pitman\",\n\"pitmark\",\n\"pitmirk\",\n\"pitpan\",\n\"pitpit\",\n\"pitside\",\n\"pitted\",\n\"pitter\",\n\"pittine\",\n\"pitting\",\n\"pittite\",\n\"pittoid\",\n\"pituite\",\n\"pituri\",\n\"pitwood\",\n\"pitwork\",\n\"pity\",\n\"pitying\",\n\"piuri\",\n\"pivalic\",\n\"pivot\",\n\"pivotal\",\n\"pivoter\",\n\"pix\",\n\"pixie\",\n\"pixy\",\n\"pize\",\n\"pizza\",\n\"pizzle\",\n\"placard\",\n\"placate\",\n\"place\",\n\"placebo\",\n\"placer\",\n\"placet\",\n\"placid\",\n\"plack\",\n\"placket\",\n\"placode\",\n\"placoid\",\n\"placula\",\n\"plaga\",\n\"plagal\",\n\"plagate\",\n\"plage\",\n\"plagium\",\n\"plagose\",\n\"plague\",\n\"plagued\",\n\"plaguer\",\n\"plaguy\",\n\"plaice\",\n\"plaid\",\n\"plaided\",\n\"plaidie\",\n\"plaidy\",\n\"plain\",\n\"plainer\",\n\"plainly\",\n\"plaint\",\n\"plait\",\n\"plaited\",\n\"plaiter\",\n\"plak\",\n\"plakat\",\n\"plan\",\n\"planaea\",\n\"planar\",\n\"planate\",\n\"planch\",\n\"plandok\",\n\"plane\",\n\"planer\",\n\"planet\",\n\"planeta\",\n\"planful\",\n\"plang\",\n\"plangor\",\n\"planish\",\n\"planity\",\n\"plank\",\n\"planker\",\n\"planky\",\n\"planner\",\n\"plant\",\n\"planta\",\n\"plantad\",\n\"plantal\",\n\"plantar\",\n\"planter\",\n\"planula\",\n\"planury\",\n\"planxty\",\n\"plap\",\n\"plaque\",\n\"plash\",\n\"plasher\",\n\"plashet\",\n\"plashy\",\n\"plasm\",\n\"plasma\",\n\"plasmic\",\n\"plasome\",\n\"plass\",\n\"plasson\",\n\"plaster\",\n\"plastic\",\n\"plastid\",\n\"plastin\",\n\"plat\",\n\"platan\",\n\"platane\",\n\"platano\",\n\"platch\",\n\"plate\",\n\"platea\",\n\"plateau\",\n\"plated\",\n\"platen\",\n\"plater\",\n\"platery\",\n\"platic\",\n\"platina\",\n\"plating\",\n\"platode\",\n\"platoid\",\n\"platoon\",\n\"platted\",\n\"platten\",\n\"platter\",\n\"platty\",\n\"platy\",\n\"plaud\",\n\"plaudit\",\n\"play\",\n\"playa\",\n\"playbox\",\n\"playboy\",\n\"playday\",\n\"player\",\n\"playful\",\n\"playlet\",\n\"playman\",\n\"playock\",\n\"playpen\",\n\"plaza\",\n\"plea\",\n\"pleach\",\n\"plead\",\n\"pleader\",\n\"please\",\n\"pleaser\",\n\"pleat\",\n\"pleater\",\n\"pleb\",\n\"plebe\",\n\"plebify\",\n\"plebs\",\n\"pleck\",\n\"plectre\",\n\"pled\",\n\"pledge\",\n\"pledgee\",\n\"pledger\",\n\"pledget\",\n\"pledgor\",\n\"pleion\",\n\"plenary\",\n\"plenipo\",\n\"plenish\",\n\"plenism\",\n\"plenist\",\n\"plenty\",\n\"plenum\",\n\"pleny\",\n\"pleon\",\n\"pleonal\",\n\"pleonic\",\n\"pleopod\",\n\"pleroma\",\n\"plerome\",\n\"plessor\",\n\"pleura\",\n\"pleural\",\n\"pleuric\",\n\"pleuron\",\n\"pleurum\",\n\"plew\",\n\"plex\",\n\"plexal\",\n\"plexor\",\n\"plexure\",\n\"plexus\",\n\"pliable\",\n\"pliably\",\n\"pliancy\",\n\"pliant\",\n\"plica\",\n\"plical\",\n\"plicate\",\n\"plied\",\n\"plier\",\n\"plies\",\n\"pliers\",\n\"plight\",\n\"plim\",\n\"plinth\",\n\"pliskie\",\n\"plisky\",\n\"ploat\",\n\"ploce\",\n\"plock\",\n\"plod\",\n\"plodder\",\n\"plodge\",\n\"plomb\",\n\"plook\",\n\"plop\",\n\"plosion\",\n\"plosive\",\n\"plot\",\n\"plote\",\n\"plotful\",\n\"plotted\",\n\"plotter\",\n\"plotty\",\n\"plough\",\n\"plouk\",\n\"plouked\",\n\"plouky\",\n\"plounce\",\n\"plout\",\n\"plouter\",\n\"plover\",\n\"plovery\",\n\"plow\",\n\"plowboy\",\n\"plower\",\n\"plowing\",\n\"plowman\",\n\"ploy\",\n\"pluck\",\n\"plucked\",\n\"plucker\",\n\"plucky\",\n\"plud\",\n\"pluff\",\n\"pluffer\",\n\"pluffy\",\n\"plug\",\n\"plugged\",\n\"plugger\",\n\"pluggy\",\n\"plugman\",\n\"plum\",\n\"pluma\",\n\"plumach\",\n\"plumade\",\n\"plumage\",\n\"plumate\",\n\"plumb\",\n\"plumber\",\n\"plumbet\",\n\"plumbic\",\n\"plumbog\",\n\"plumbum\",\n\"plumcot\",\n\"plume\",\n\"plumed\",\n\"plumer\",\n\"plumery\",\n\"plumet\",\n\"plumier\",\n\"plumify\",\n\"plumist\",\n\"plumlet\",\n\"plummer\",\n\"plummet\",\n\"plummy\",\n\"plumose\",\n\"plumous\",\n\"plump\",\n\"plumpen\",\n\"plumper\",\n\"plumply\",\n\"plumps\",\n\"plumpy\",\n\"plumula\",\n\"plumule\",\n\"plumy\",\n\"plunder\",\n\"plunge\",\n\"plunger\",\n\"plunk\",\n\"plup\",\n\"plural\",\n\"pluries\",\n\"plurify\",\n\"plus\",\n\"plush\",\n\"plushed\",\n\"plushy\",\n\"pluteal\",\n\"plutean\",\n\"pluteus\",\n\"pluvial\",\n\"pluvian\",\n\"pluvine\",\n\"ply\",\n\"plyer\",\n\"plying\",\n\"plywood\",\n\"pneuma\",\n\"po\",\n\"poach\",\n\"poacher\",\n\"poachy\",\n\"poalike\",\n\"pob\",\n\"pobby\",\n\"pobs\",\n\"pochade\",\n\"pochard\",\n\"pochay\",\n\"poche\",\n\"pock\",\n\"pocket\",\n\"pockety\",\n\"pockily\",\n\"pocky\",\n\"poco\",\n\"pocosin\",\n\"pod\",\n\"podagra\",\n\"podal\",\n\"podalic\",\n\"podatus\",\n\"podded\",\n\"podder\",\n\"poddish\",\n\"poddle\",\n\"poddy\",\n\"podeon\",\n\"podesta\",\n\"podex\",\n\"podge\",\n\"podger\",\n\"podgily\",\n\"podgy\",\n\"podial\",\n\"podical\",\n\"podices\",\n\"podite\",\n\"poditic\",\n\"poditti\",\n\"podium\",\n\"podler\",\n\"podley\",\n\"podlike\",\n\"podogyn\",\n\"podsol\",\n\"poduran\",\n\"podurid\",\n\"podware\",\n\"podzol\",\n\"poe\",\n\"poem\",\n\"poemet\",\n\"poemlet\",\n\"poesie\",\n\"poesis\",\n\"poesy\",\n\"poet\",\n\"poetdom\",\n\"poetess\",\n\"poetic\",\n\"poetics\",\n\"poetito\",\n\"poetize\",\n\"poetly\",\n\"poetry\",\n\"pogge\",\n\"poggy\",\n\"pogonip\",\n\"pogrom\",\n\"pogy\",\n\"poh\",\n\"poha\",\n\"pohna\",\n\"poi\",\n\"poietic\",\n\"poignet\",\n\"poil\",\n\"poilu\",\n\"poind\",\n\"poinder\",\n\"point\",\n\"pointed\",\n\"pointel\",\n\"pointer\",\n\"pointy\",\n\"poise\",\n\"poised\",\n\"poiser\",\n\"poison\",\n\"poitrel\",\n\"pokable\",\n\"poke\",\n\"poked\",\n\"pokeful\",\n\"pokeout\",\n\"poker\",\n\"pokey\",\n\"pokily\",\n\"poking\",\n\"pokomoo\",\n\"pokunt\",\n\"poky\",\n\"pol\",\n\"polacca\",\n\"polack\",\n\"polacre\",\n\"polar\",\n\"polaric\",\n\"polarly\",\n\"polaxis\",\n\"poldavy\",\n\"polder\",\n\"pole\",\n\"polearm\",\n\"poleax\",\n\"poleaxe\",\n\"polecat\",\n\"poleman\",\n\"polemic\",\n\"polenta\",\n\"poler\",\n\"poley\",\n\"poliad\",\n\"police\",\n\"policed\",\n\"policy\",\n\"poligar\",\n\"polio\",\n\"polis\",\n\"polish\",\n\"polite\",\n\"politic\",\n\"polity\",\n\"polk\",\n\"polka\",\n\"poll\",\n\"pollack\",\n\"polladz\",\n\"pollage\",\n\"pollam\",\n\"pollan\",\n\"pollard\",\n\"polled\",\n\"pollen\",\n\"pollent\",\n\"poller\",\n\"pollex\",\n\"polling\",\n\"pollock\",\n\"polloi\",\n\"pollute\",\n\"pollux\",\n\"polo\",\n\"poloist\",\n\"polony\",\n\"polos\",\n\"polska\",\n\"polt\",\n\"poltina\",\n\"poly\",\n\"polyact\",\n\"polyad\",\n\"polygam\",\n\"polygon\",\n\"polygyn\",\n\"polymer\",\n\"polyose\",\n\"polyp\",\n\"polyped\",\n\"polypi\",\n\"polypod\",\n\"polypus\",\n\"pom\",\n\"pomace\",\n\"pomade\",\n\"pomane\",\n\"pomate\",\n\"pomato\",\n\"pomatum\",\n\"pombe\",\n\"pombo\",\n\"pome\",\n\"pomelo\",\n\"pomey\",\n\"pomfret\",\n\"pomme\",\n\"pommee\",\n\"pommel\",\n\"pommet\",\n\"pommey\",\n\"pommy\",\n\"pomonal\",\n\"pomonic\",\n\"pomp\",\n\"pompa\",\n\"pompal\",\n\"pompano\",\n\"pompey\",\n\"pomphus\",\n\"pompier\",\n\"pompion\",\n\"pompist\",\n\"pompon\",\n\"pompous\",\n\"pomster\",\n\"pon\",\n\"ponce\",\n\"ponceau\",\n\"poncho\",\n\"pond\",\n\"pondage\",\n\"ponder\",\n\"pondful\",\n\"pondlet\",\n\"pondman\",\n\"pondok\",\n\"pondus\",\n\"pondy\",\n\"pone\",\n\"ponent\",\n\"ponerid\",\n\"poney\",\n\"pong\",\n\"ponga\",\n\"pongee\",\n\"poniard\",\n\"ponica\",\n\"ponier\",\n\"ponja\",\n\"pont\",\n\"pontage\",\n\"pontal\",\n\"pontee\",\n\"pontes\",\n\"pontic\",\n\"pontiff\",\n\"pontify\",\n\"pontil\",\n\"pontile\",\n\"pontin\",\n\"pontine\",\n\"pontist\",\n\"ponto\",\n\"ponton\",\n\"pontoon\",\n\"pony\",\n\"ponzite\",\n\"pooa\",\n\"pooch\",\n\"pooder\",\n\"poodle\",\n\"poof\",\n\"poogye\",\n\"pooh\",\n\"pook\",\n\"pooka\",\n\"pookaun\",\n\"pookoo\",\n\"pool\",\n\"pooler\",\n\"pooli\",\n\"pooly\",\n\"poon\",\n\"poonac\",\n\"poonga\",\n\"poop\",\n\"pooped\",\n\"poor\",\n\"poorish\",\n\"poorly\",\n\"poot\",\n\"pop\",\n\"popadam\",\n\"popal\",\n\"popcorn\",\n\"popdock\",\n\"pope\",\n\"popedom\",\n\"popeism\",\n\"popeler\",\n\"popely\",\n\"popery\",\n\"popess\",\n\"popeye\",\n\"popeyed\",\n\"popgun\",\n\"popify\",\n\"popinac\",\n\"popish\",\n\"popjoy\",\n\"poplar\",\n\"poplin\",\n\"popover\",\n\"poppa\",\n\"poppean\",\n\"poppel\",\n\"popper\",\n\"poppet\",\n\"poppied\",\n\"poppin\",\n\"popple\",\n\"popply\",\n\"poppy\",\n\"popshop\",\n\"popular\",\n\"populin\",\n\"popweed\",\n\"poral\",\n\"porcate\",\n\"porch\",\n\"porched\",\n\"porcine\",\n\"pore\",\n\"pored\",\n\"porer\",\n\"porge\",\n\"porger\",\n\"porgy\",\n\"poring\",\n\"porism\",\n\"porite\",\n\"pork\",\n\"porker\",\n\"porkery\",\n\"porket\",\n\"porkish\",\n\"porkman\",\n\"porkpie\",\n\"porky\",\n\"porogam\",\n\"poroma\",\n\"poros\",\n\"porose\",\n\"porosis\",\n\"porotic\",\n\"porous\",\n\"porr\",\n\"porrect\",\n\"porret\",\n\"porrigo\",\n\"porry\",\n\"port\",\n\"porta\",\n\"portage\",\n\"portail\",\n\"portal\",\n\"portass\",\n\"ported\",\n\"portend\",\n\"portent\",\n\"porter\",\n\"portia\",\n\"portico\",\n\"portify\",\n\"portio\",\n\"portion\",\n\"portlet\",\n\"portly\",\n\"portman\",\n\"porto\",\n\"portray\",\n\"portway\",\n\"porty\",\n\"porule\",\n\"porus\",\n\"pory\",\n\"posca\",\n\"pose\",\n\"poser\",\n\"poseur\",\n\"posey\",\n\"posh\",\n\"posing\",\n\"posit\",\n\"positor\",\n\"positum\",\n\"posnet\",\n\"posole\",\n\"poss\",\n\"posse\",\n\"possess\",\n\"posset\",\n\"possum\",\n\"post\",\n\"postage\",\n\"postal\",\n\"postbag\",\n\"postbox\",\n\"postboy\",\n\"posted\",\n\"posteen\",\n\"poster\",\n\"postern\",\n\"postfix\",\n\"postic\",\n\"postil\",\n\"posting\",\n\"postman\",\n\"posture\",\n\"postwar\",\n\"posy\",\n\"pot\",\n\"potable\",\n\"potamic\",\n\"potash\",\n\"potass\",\n\"potassa\",\n\"potate\",\n\"potato\",\n\"potator\",\n\"potbank\",\n\"potboil\",\n\"potboy\",\n\"potch\",\n\"potcher\",\n\"potdar\",\n\"pote\",\n\"poteen\",\n\"potence\",\n\"potency\",\n\"potent\",\n\"poter\",\n\"poteye\",\n\"potful\",\n\"potgirl\",\n\"potgun\",\n\"pothead\",\n\"potheen\",\n\"pother\",\n\"potherb\",\n\"pothery\",\n\"pothole\",\n\"pothook\",\n\"pothunt\",\n\"potifer\",\n\"potion\",\n\"potleg\",\n\"potlid\",\n\"potlike\",\n\"potluck\",\n\"potman\",\n\"potong\",\n\"potoo\",\n\"potoroo\",\n\"potpie\",\n\"potrack\",\n\"pott\",\n\"pottage\",\n\"pottagy\",\n\"pottah\",\n\"potted\",\n\"potter\",\n\"pottery\",\n\"potting\",\n\"pottle\",\n\"pottled\",\n\"potto\",\n\"potty\",\n\"potware\",\n\"potwork\",\n\"potwort\",\n\"pouce\",\n\"poucer\",\n\"poucey\",\n\"pouch\",\n\"pouched\",\n\"pouchy\",\n\"pouf\",\n\"poulard\",\n\"poulp\",\n\"poulpe\",\n\"poult\",\n\"poulter\",\n\"poultry\",\n\"pounamu\",\n\"pounce\",\n\"pounced\",\n\"pouncer\",\n\"pouncet\",\n\"pound\",\n\"poundal\",\n\"pounder\",\n\"pour\",\n\"pourer\",\n\"pourie\",\n\"pouring\",\n\"pouser\",\n\"pout\",\n\"pouter\",\n\"poutful\",\n\"pouting\",\n\"pouty\",\n\"poverty\",\n\"pow\",\n\"powder\",\n\"powdery\",\n\"powdike\",\n\"powdry\",\n\"power\",\n\"powered\",\n\"powitch\",\n\"pownie\",\n\"powwow\",\n\"pox\",\n\"poxy\",\n\"poy\",\n\"poyou\",\n\"praam\",\n\"prabble\",\n\"prabhu\",\n\"practic\",\n\"prad\",\n\"praecox\",\n\"praetor\",\n\"prairie\",\n\"praise\",\n\"praiser\",\n\"prajna\",\n\"praline\",\n\"pram\",\n\"prana\",\n\"prance\",\n\"prancer\",\n\"prancy\",\n\"prank\",\n\"pranked\",\n\"pranker\",\n\"prankle\",\n\"pranky\",\n\"prase\",\n\"prasine\",\n\"prasoid\",\n\"prastha\",\n\"prat\",\n\"pratal\",\n\"prate\",\n\"prater\",\n\"pratey\",\n\"prating\",\n\"prattle\",\n\"prattly\",\n\"prau\",\n\"pravity\",\n\"prawn\",\n\"prawner\",\n\"prawny\",\n\"praxis\",\n\"pray\",\n\"praya\",\n\"prayer\",\n\"prayful\",\n\"praying\",\n\"preach\",\n\"preachy\",\n\"preacid\",\n\"preact\",\n\"preaged\",\n\"preally\",\n\"preanal\",\n\"prearm\",\n\"preaver\",\n\"prebake\",\n\"prebend\",\n\"prebid\",\n\"prebill\",\n\"preboil\",\n\"preborn\",\n\"preburn\",\n\"precant\",\n\"precary\",\n\"precast\",\n\"precava\",\n\"precede\",\n\"precent\",\n\"precept\",\n\"preces\",\n\"precess\",\n\"precipe\",\n\"precis\",\n\"precise\",\n\"precite\",\n\"precoil\",\n\"precook\",\n\"precool\",\n\"precopy\",\n\"precox\",\n\"precure\",\n\"precut\",\n\"precyst\",\n\"predamn\",\n\"predark\",\n\"predata\",\n\"predate\",\n\"predawn\",\n\"preday\",\n\"predefy\",\n\"predeny\",\n\"predial\",\n\"predict\",\n\"prediet\",\n\"predine\",\n\"predoom\",\n\"predraw\",\n\"predry\",\n\"predusk\",\n\"preen\",\n\"preener\",\n\"preeze\",\n\"prefab\",\n\"preface\",\n\"prefect\",\n\"prefer\",\n\"prefine\",\n\"prefix\",\n\"prefool\",\n\"preform\",\n\"pregain\",\n\"pregust\",\n\"prehaps\",\n\"preheal\",\n\"preheat\",\n\"prehend\",\n\"preidea\",\n\"preknit\",\n\"preknow\",\n\"prelacy\",\n\"prelate\",\n\"prelect\",\n\"prelim\",\n\"preloan\",\n\"preloss\",\n\"prelude\",\n\"premake\",\n\"premate\",\n\"premial\",\n\"premier\",\n\"premise\",\n\"premiss\",\n\"premium\",\n\"premix\",\n\"premold\",\n\"premove\",\n\"prename\",\n\"prender\",\n\"prendre\",\n\"preomit\",\n\"preopen\",\n\"preoral\",\n\"prep\",\n\"prepare\",\n\"prepave\",\n\"prepay\",\n\"prepink\",\n\"preplan\",\n\"preplot\",\n\"prepose\",\n\"prepuce\",\n\"prepupa\",\n\"prerent\",\n\"prerich\",\n\"prerupt\",\n\"presage\",\n\"presay\",\n\"preseal\",\n\"presee\",\n\"presell\",\n\"present\",\n\"preses\",\n\"preset\",\n\"preship\",\n\"preshow\",\n\"preside\",\n\"presift\",\n\"presign\",\n\"prespur\",\n\"press\",\n\"pressel\",\n\"presser\",\n\"pressor\",\n\"prest\",\n\"prester\",\n\"presto\",\n\"presume\",\n\"pretan\",\n\"pretell\",\n\"pretend\",\n\"pretest\",\n\"pretext\",\n\"pretire\",\n\"pretone\",\n\"pretry\",\n\"pretty\",\n\"pretzel\",\n\"prevail\",\n\"prevene\",\n\"prevent\",\n\"preverb\",\n\"preveto\",\n\"previde\",\n\"preview\",\n\"previse\",\n\"prevoid\",\n\"prevote\",\n\"prevue\",\n\"prewar\",\n\"prewarn\",\n\"prewash\",\n\"prewhip\",\n\"prewire\",\n\"prewrap\",\n\"prexy\",\n\"prey\",\n\"preyer\",\n\"preyful\",\n\"prezone\",\n\"price\",\n\"priced\",\n\"pricer\",\n\"prich\",\n\"prick\",\n\"pricked\",\n\"pricker\",\n\"pricket\",\n\"prickle\",\n\"prickly\",\n\"pricks\",\n\"pricky\",\n\"pride\",\n\"pridian\",\n\"priding\",\n\"pridy\",\n\"pried\",\n\"prier\",\n\"priest\",\n\"prig\",\n\"prigdom\",\n\"prigger\",\n\"prigman\",\n\"prill\",\n\"prim\",\n\"prima\",\n\"primacy\",\n\"primage\",\n\"primal\",\n\"primar\",\n\"primary\",\n\"primate\",\n\"prime\",\n\"primely\",\n\"primer\",\n\"primero\",\n\"primine\",\n\"priming\",\n\"primly\",\n\"primost\",\n\"primp\",\n\"primsie\",\n\"primula\",\n\"primus\",\n\"primy\",\n\"prince\",\n\"princox\",\n\"prine\",\n\"pringle\",\n\"prink\",\n\"prinker\",\n\"prinkle\",\n\"prinky\",\n\"print\",\n\"printed\",\n\"printer\",\n\"prion\",\n\"prionid\",\n\"prior\",\n\"prioral\",\n\"priorly\",\n\"priory\",\n\"prisage\",\n\"prisal\",\n\"priscan\",\n\"prism\",\n\"prismal\",\n\"prismed\",\n\"prismy\",\n\"prison\",\n\"priss\",\n\"prissy\",\n\"pritch\",\n\"prithee\",\n\"prius\",\n\"privacy\",\n\"privant\",\n\"private\",\n\"privet\",\n\"privily\",\n\"privity\",\n\"privy\",\n\"prize\",\n\"prizer\",\n\"prizery\",\n\"pro\",\n\"proa\",\n\"proal\",\n\"proarmy\",\n\"prob\",\n\"probabl\",\n\"probal\",\n\"probang\",\n\"probant\",\n\"probate\",\n\"probe\",\n\"probeer\",\n\"prober\",\n\"probity\",\n\"problem\",\n\"procarp\",\n\"proceed\",\n\"process\",\n\"proctal\",\n\"proctor\",\n\"procure\",\n\"prod\",\n\"prodder\",\n\"proddle\",\n\"prodigy\",\n\"produce\",\n\"product\",\n\"proem\",\n\"proetid\",\n\"prof\",\n\"profane\",\n\"profert\",\n\"profess\",\n\"proffer\",\n\"profile\",\n\"profit\",\n\"profuse\",\n\"prog\",\n\"progeny\",\n\"progger\",\n\"progne\",\n\"program\",\n\"project\",\n\"proke\",\n\"proker\",\n\"prolan\",\n\"prolate\",\n\"proleg\",\n\"prolify\",\n\"proline\",\n\"prolix\",\n\"prolong\",\n\"prolyl\",\n\"promic\",\n\"promise\",\n\"promote\",\n\"prompt\",\n\"pronaos\",\n\"pronate\",\n\"pronavy\",\n\"prone\",\n\"pronely\",\n\"proneur\",\n\"prong\",\n\"pronged\",\n\"pronger\",\n\"pronic\",\n\"pronoun\",\n\"pronpl\",\n\"pronto\",\n\"pronuba\",\n\"proo\",\n\"proof\",\n\"proofer\",\n\"proofy\",\n\"prop\",\n\"propago\",\n\"propale\",\n\"propane\",\n\"propend\",\n\"propene\",\n\"proper\",\n\"prophet\",\n\"propine\",\n\"proplex\",\n\"propone\",\n\"propons\",\n\"propose\",\n\"propoxy\",\n\"propper\",\n\"props\",\n\"propupa\",\n\"propyl\",\n\"propyne\",\n\"prorata\",\n\"prorate\",\n\"prore\",\n\"prorean\",\n\"prorsad\",\n\"prorsal\",\n\"prosaic\",\n\"prosar\",\n\"prose\",\n\"prosect\",\n\"proser\",\n\"prosify\",\n\"prosily\",\n\"prosing\",\n\"prosish\",\n\"prosist\",\n\"proso\",\n\"prosode\",\n\"prosody\",\n\"prosoma\",\n\"prosper\",\n\"pross\",\n\"prossy\",\n\"prosy\",\n\"protax\",\n\"prote\",\n\"protea\",\n\"protead\",\n\"protean\",\n\"protect\",\n\"protege\",\n\"proteic\",\n\"protein\",\n\"protend\",\n\"protest\",\n\"protext\",\n\"prothyl\",\n\"protide\",\n\"protist\",\n\"protium\",\n\"proto\",\n\"protoma\",\n\"protome\",\n\"proton\",\n\"protone\",\n\"protore\",\n\"protyl\",\n\"protyle\",\n\"protype\",\n\"proudly\",\n\"provand\",\n\"provant\",\n\"prove\",\n\"provect\",\n\"proved\",\n\"proven\",\n\"prover\",\n\"proverb\",\n\"provide\",\n\"provine\",\n\"proving\",\n\"proviso\",\n\"provoke\",\n\"provost\",\n\"prow\",\n\"prowar\",\n\"prowed\",\n\"prowess\",\n\"prowl\",\n\"prowler\",\n\"proxeny\",\n\"proximo\",\n\"proxy\",\n\"proxysm\",\n\"prozone\",\n\"prude\",\n\"prudely\",\n\"prudent\",\n\"prudery\",\n\"prudish\",\n\"prudist\",\n\"prudity\",\n\"pruh\",\n\"prunase\",\n\"prune\",\n\"prunell\",\n\"pruner\",\n\"pruning\",\n\"prunt\",\n\"prunted\",\n\"prurigo\",\n\"prussic\",\n\"prut\",\n\"prutah\",\n\"pry\",\n\"pryer\",\n\"prying\",\n\"pryler\",\n\"pryse\",\n\"prytany\",\n\"psalis\",\n\"psalm\",\n\"psalmic\",\n\"psalmy\",\n\"psaloid\",\n\"psalter\",\n\"psaltes\",\n\"pschent\",\n\"pseudo\",\n\"psha\",\n\"pshaw\",\n\"psi\",\n\"psiloi\",\n\"psoadic\",\n\"psoas\",\n\"psoatic\",\n\"psocid\",\n\"psocine\",\n\"psoitis\",\n\"psora\",\n\"psoric\",\n\"psoroid\",\n\"psorous\",\n\"pst\",\n\"psych\",\n\"psychal\",\n\"psyche\",\n\"psychic\",\n\"psychid\",\n\"psychon\",\n\"psykter\",\n\"psylla\",\n\"psyllid\",\n\"ptarmic\",\n\"ptereal\",\n\"pteric\",\n\"pterion\",\n\"pteroid\",\n\"pteroma\",\n\"pteryla\",\n\"ptinid\",\n\"ptinoid\",\n\"ptisan\",\n\"ptomain\",\n\"ptosis\",\n\"ptotic\",\n\"ptyalin\",\n\"ptyxis\",\n\"pu\",\n\"pua\",\n\"puan\",\n\"pub\",\n\"pubal\",\n\"pubble\",\n\"puberal\",\n\"puberty\",\n\"pubes\",\n\"pubian\",\n\"pubic\",\n\"pubis\",\n\"public\",\n\"publish\",\n\"puccoon\",\n\"puce\",\n\"pucelle\",\n\"puchero\",\n\"puck\",\n\"pucka\",\n\"pucker\",\n\"puckery\",\n\"puckish\",\n\"puckle\",\n\"puckrel\",\n\"pud\",\n\"puddee\",\n\"pudder\",\n\"pudding\",\n\"puddle\",\n\"puddled\",\n\"puddler\",\n\"puddly\",\n\"puddock\",\n\"puddy\",\n\"pudency\",\n\"pudenda\",\n\"pudent\",\n\"pudge\",\n\"pudgily\",\n\"pudgy\",\n\"pudiano\",\n\"pudic\",\n\"pudical\",\n\"pudsey\",\n\"pudsy\",\n\"pudu\",\n\"pueblo\",\n\"puerer\",\n\"puerile\",\n\"puerman\",\n\"puff\",\n\"puffed\",\n\"puffer\",\n\"puffery\",\n\"puffily\",\n\"puffin\",\n\"puffing\",\n\"pufflet\",\n\"puffwig\",\n\"puffy\",\n\"pug\",\n\"pugged\",\n\"pugger\",\n\"puggi\",\n\"pugging\",\n\"puggish\",\n\"puggle\",\n\"puggree\",\n\"puggy\",\n\"pugh\",\n\"pugil\",\n\"pugman\",\n\"pugmill\",\n\"puisne\",\n\"puist\",\n\"puistie\",\n\"puja\",\n\"puka\",\n\"pukatea\",\n\"puke\",\n\"pukeko\",\n\"puker\",\n\"pukish\",\n\"pukras\",\n\"puku\",\n\"puky\",\n\"pul\",\n\"pulahan\",\n\"pulasan\",\n\"pule\",\n\"pulegol\",\n\"puler\",\n\"puli\",\n\"pulicat\",\n\"pulicid\",\n\"puling\",\n\"pulish\",\n\"pulk\",\n\"pulka\",\n\"pull\",\n\"pulldoo\",\n\"pullen\",\n\"puller\",\n\"pullery\",\n\"pullet\",\n\"pulley\",\n\"pulli\",\n\"pullus\",\n\"pulp\",\n\"pulpal\",\n\"pulper\",\n\"pulpify\",\n\"pulpily\",\n\"pulpit\",\n\"pulpous\",\n\"pulpy\",\n\"pulque\",\n\"pulsant\",\n\"pulsate\",\n\"pulse\",\n\"pulsion\",\n\"pulsive\",\n\"pulton\",\n\"pulu\",\n\"pulvic\",\n\"pulvil\",\n\"pulvino\",\n\"pulwar\",\n\"puly\",\n\"puma\",\n\"pumice\",\n\"pumiced\",\n\"pumicer\",\n\"pummel\",\n\"pummice\",\n\"pump\",\n\"pumpage\",\n\"pumper\",\n\"pumpkin\",\n\"pumple\",\n\"pumpman\",\n\"pun\",\n\"puna\",\n\"punaise\",\n\"punalua\",\n\"punatoo\",\n\"punch\",\n\"puncher\",\n\"punchy\",\n\"punct\",\n\"punctal\",\n\"punctum\",\n\"pundit\",\n\"pundita\",\n\"pundum\",\n\"puneca\",\n\"pung\",\n\"punga\",\n\"pungar\",\n\"pungent\",\n\"punger\",\n\"pungey\",\n\"pungi\",\n\"pungle\",\n\"pungled\",\n\"punicin\",\n\"punily\",\n\"punish\",\n\"punjum\",\n\"punk\",\n\"punkah\",\n\"punkie\",\n\"punky\",\n\"punless\",\n\"punlet\",\n\"punnage\",\n\"punner\",\n\"punnet\",\n\"punnic\",\n\"punster\",\n\"punt\",\n\"punta\",\n\"puntal\",\n\"puntel\",\n\"punter\",\n\"punti\",\n\"puntil\",\n\"puntist\",\n\"punto\",\n\"puntout\",\n\"punty\",\n\"puny\",\n\"punyish\",\n\"punyism\",\n\"pup\",\n\"pupa\",\n\"pupal\",\n\"pupate\",\n\"pupelo\",\n\"pupil\",\n\"pupilar\",\n\"pupiled\",\n\"pupoid\",\n\"puppet\",\n\"puppify\",\n\"puppily\",\n\"puppy\",\n\"pupulo\",\n\"pupunha\",\n\"pur\",\n\"purana\",\n\"puranic\",\n\"puraque\",\n\"purdah\",\n\"purdy\",\n\"pure\",\n\"pured\",\n\"puree\",\n\"purely\",\n\"purer\",\n\"purfle\",\n\"purfled\",\n\"purfler\",\n\"purfly\",\n\"purga\",\n\"purge\",\n\"purger\",\n\"purgery\",\n\"purging\",\n\"purify\",\n\"purine\",\n\"puriri\",\n\"purism\",\n\"purist\",\n\"purity\",\n\"purl\",\n\"purler\",\n\"purlieu\",\n\"purlin\",\n\"purlman\",\n\"purloin\",\n\"purpart\",\n\"purple\",\n\"purply\",\n\"purport\",\n\"purpose\",\n\"purpura\",\n\"purpure\",\n\"purr\",\n\"purre\",\n\"purree\",\n\"purreic\",\n\"purrel\",\n\"purrer\",\n\"purring\",\n\"purrone\",\n\"purry\",\n\"purse\",\n\"pursed\",\n\"purser\",\n\"pursily\",\n\"purslet\",\n\"pursley\",\n\"pursual\",\n\"pursue\",\n\"pursuer\",\n\"pursuit\",\n\"pursy\",\n\"purusha\",\n\"purvey\",\n\"purview\",\n\"purvoe\",\n\"pus\",\n\"push\",\n\"pusher\",\n\"pushful\",\n\"pushing\",\n\"pushpin\",\n\"puss\",\n\"pusscat\",\n\"pussley\",\n\"pussy\",\n\"pustule\",\n\"put\",\n\"putage\",\n\"putamen\",\n\"putback\",\n\"putchen\",\n\"putcher\",\n\"puteal\",\n\"putelee\",\n\"puther\",\n\"puthery\",\n\"putid\",\n\"putidly\",\n\"putlog\",\n\"putois\",\n\"putrefy\",\n\"putrid\",\n\"putt\",\n\"puttee\",\n\"putter\",\n\"puttier\",\n\"puttock\",\n\"putty\",\n\"puture\",\n\"puxy\",\n\"puzzle\",\n\"puzzled\",\n\"puzzler\",\n\"pya\",\n\"pyal\",\n\"pyche\",\n\"pycnia\",\n\"pycnial\",\n\"pycnid\",\n\"pycnite\",\n\"pycnium\",\n\"pyelic\",\n\"pyemia\",\n\"pyemic\",\n\"pygal\",\n\"pygarg\",\n\"pygidid\",\n\"pygmoid\",\n\"pygmy\",\n\"pygofer\",\n\"pygopod\",\n\"pyic\",\n\"pyin\",\n\"pyjama\",\n\"pyke\",\n\"pyknic\",\n\"pyla\",\n\"pylar\",\n\"pylic\",\n\"pylon\",\n\"pyloric\",\n\"pylorus\",\n\"pyocele\",\n\"pyocyst\",\n\"pyocyte\",\n\"pyoid\",\n\"pyosis\",\n\"pyr\",\n\"pyral\",\n\"pyralid\",\n\"pyralis\",\n\"pyramid\",\n\"pyran\",\n\"pyranyl\",\n\"pyre\",\n\"pyrena\",\n\"pyrene\",\n\"pyrenic\",\n\"pyrenin\",\n\"pyretic\",\n\"pyrex\",\n\"pyrexia\",\n\"pyrexic\",\n\"pyrgom\",\n\"pyridic\",\n\"pyridyl\",\n\"pyrite\",\n\"pyrites\",\n\"pyritic\",\n\"pyro\",\n\"pyrogen\",\n\"pyroid\",\n\"pyrone\",\n\"pyrope\",\n\"pyropen\",\n\"pyropus\",\n\"pyrosis\",\n\"pyrotic\",\n\"pyrrhic\",\n\"pyrrol\",\n\"pyrrole\",\n\"pyrroyl\",\n\"pyrryl\",\n\"pyruvic\",\n\"pyruvil\",\n\"pyruvyl\",\n\"python\",\n\"pyuria\",\n\"pyvuril\",\n\"pyx\",\n\"pyxides\",\n\"pyxie\",\n\"pyxis\",\n\"q\",\n\"qasida\",\n\"qere\",\n\"qeri\",\n\"qintar\",\n\"qoph\",\n\"qua\",\n\"quab\",\n\"quabird\",\n\"quachil\",\n\"quack\",\n\"quackle\",\n\"quacky\",\n\"quad\",\n\"quadded\",\n\"quaddle\",\n\"quadra\",\n\"quadral\",\n\"quadrat\",\n\"quadric\",\n\"quadrum\",\n\"quaedam\",\n\"quaff\",\n\"quaffer\",\n\"quag\",\n\"quagga\",\n\"quaggle\",\n\"quaggy\",\n\"quahog\",\n\"quail\",\n\"quaily\",\n\"quaint\",\n\"quake\",\n\"quaker\",\n\"quaking\",\n\"quaky\",\n\"quale\",\n\"qualify\",\n\"quality\",\n\"qualm\",\n\"qualmy\",\n\"quan\",\n\"quandy\",\n\"quannet\",\n\"quant\",\n\"quanta\",\n\"quantic\",\n\"quantum\",\n\"quar\",\n\"quare\",\n\"quark\",\n\"quarl\",\n\"quarle\",\n\"quarred\",\n\"quarrel\",\n\"quarry\",\n\"quart\",\n\"quartan\",\n\"quarter\",\n\"quartet\",\n\"quartic\",\n\"quarto\",\n\"quartz\",\n\"quartzy\",\n\"quash\",\n\"quashey\",\n\"quashy\",\n\"quasi\",\n\"quasky\",\n\"quassin\",\n\"quat\",\n\"quata\",\n\"quatch\",\n\"quatern\",\n\"quaters\",\n\"quatral\",\n\"quatre\",\n\"quatrin\",\n\"quattie\",\n\"quatuor\",\n\"quauk\",\n\"quave\",\n\"quaver\",\n\"quavery\",\n\"quaw\",\n\"quawk\",\n\"quay\",\n\"quayage\",\n\"quayful\",\n\"quayman\",\n\"qubba\",\n\"queach\",\n\"queachy\",\n\"queak\",\n\"queal\",\n\"quean\",\n\"queasom\",\n\"queasy\",\n\"quedful\",\n\"queechy\",\n\"queen\",\n\"queenly\",\n\"queer\",\n\"queerer\",\n\"queerly\",\n\"queery\",\n\"queest\",\n\"queet\",\n\"queeve\",\n\"quegh\",\n\"quei\",\n\"quelch\",\n\"quell\",\n\"queller\",\n\"quemado\",\n\"queme\",\n\"quemely\",\n\"quench\",\n\"quercic\",\n\"quercin\",\n\"querent\",\n\"querier\",\n\"querist\",\n\"querken\",\n\"querl\",\n\"quern\",\n\"quernal\",\n\"query\",\n\"quest\",\n\"quester\",\n\"questor\",\n\"quet\",\n\"quetch\",\n\"quetzal\",\n\"queue\",\n\"quey\",\n\"quiapo\",\n\"quib\",\n\"quibble\",\n\"quiblet\",\n\"quica\",\n\"quick\",\n\"quicken\",\n\"quickie\",\n\"quickly\",\n\"quid\",\n\"quidder\",\n\"quiddit\",\n\"quiddle\",\n\"quiesce\",\n\"quiet\",\n\"quieten\",\n\"quieter\",\n\"quietly\",\n\"quietus\",\n\"quiff\",\n\"quila\",\n\"quiles\",\n\"quilkin\",\n\"quill\",\n\"quillai\",\n\"quilled\",\n\"quiller\",\n\"quillet\",\n\"quilly\",\n\"quilt\",\n\"quilted\",\n\"quilter\",\n\"quin\",\n\"quina\",\n\"quinary\",\n\"quinate\",\n\"quince\",\n\"quinch\",\n\"quinia\",\n\"quinic\",\n\"quinin\",\n\"quinina\",\n\"quinine\",\n\"quinism\",\n\"quinite\",\n\"quinize\",\n\"quink\",\n\"quinnat\",\n\"quinnet\",\n\"quinoa\",\n\"quinoid\",\n\"quinol\",\n\"quinone\",\n\"quinova\",\n\"quinoyl\",\n\"quinse\",\n\"quinsy\",\n\"quint\",\n\"quintad\",\n\"quintal\",\n\"quintan\",\n\"quinte\",\n\"quintet\",\n\"quintic\",\n\"quintin\",\n\"quinto\",\n\"quinton\",\n\"quintus\",\n\"quinyl\",\n\"quinze\",\n\"quip\",\n\"quipful\",\n\"quipo\",\n\"quipper\",\n\"quippy\",\n\"quipu\",\n\"quira\",\n\"quire\",\n\"quirk\",\n\"quirky\",\n\"quirl\",\n\"quirt\",\n\"quis\",\n\"quisby\",\n\"quiscos\",\n\"quisle\",\n\"quit\",\n\"quitch\",\n\"quite\",\n\"quits\",\n\"quitted\",\n\"quitter\",\n\"quittor\",\n\"quiver\",\n\"quivery\",\n\"quiz\",\n\"quizzee\",\n\"quizzer\",\n\"quizzy\",\n\"quo\",\n\"quod\",\n\"quoin\",\n\"quoined\",\n\"quoit\",\n\"quoiter\",\n\"quoits\",\n\"quondam\",\n\"quoniam\",\n\"quop\",\n\"quorum\",\n\"quot\",\n\"quota\",\n\"quote\",\n\"quotee\",\n\"quoter\",\n\"quoth\",\n\"quotha\",\n\"quotity\",\n\"quotum\",\n\"r\",\n\"ra\",\n\"raad\",\n\"raash\",\n\"rab\",\n\"raband\",\n\"rabanna\",\n\"rabat\",\n\"rabatte\",\n\"rabbet\",\n\"rabbi\",\n\"rabbin\",\n\"rabbit\",\n\"rabbity\",\n\"rabble\",\n\"rabbler\",\n\"rabboni\",\n\"rabic\",\n\"rabid\",\n\"rabidly\",\n\"rabies\",\n\"rabific\",\n\"rabinet\",\n\"rabitic\",\n\"raccoon\",\n\"raccroc\",\n\"race\",\n\"raceme\",\n\"racemed\",\n\"racemic\",\n\"racer\",\n\"raceway\",\n\"rach\",\n\"rache\",\n\"rachial\",\n\"rachis\",\n\"racial\",\n\"racily\",\n\"racing\",\n\"racism\",\n\"racist\",\n\"rack\",\n\"rackan\",\n\"racker\",\n\"racket\",\n\"rackett\",\n\"rackety\",\n\"rackful\",\n\"racking\",\n\"rackle\",\n\"rackway\",\n\"racloir\",\n\"racon\",\n\"racoon\",\n\"racy\",\n\"rad\",\n\"rada\",\n\"radar\",\n\"raddle\",\n\"radial\",\n\"radiale\",\n\"radian\",\n\"radiant\",\n\"radiate\",\n\"radical\",\n\"radicel\",\n\"radices\",\n\"radicle\",\n\"radii\",\n\"radio\",\n\"radiode\",\n\"radish\",\n\"radium\",\n\"radius\",\n\"radix\",\n\"radman\",\n\"radome\",\n\"radon\",\n\"radula\",\n\"raff\",\n\"raffe\",\n\"raffee\",\n\"raffery\",\n\"raffia\",\n\"raffing\",\n\"raffish\",\n\"raffle\",\n\"raffler\",\n\"raft\",\n\"raftage\",\n\"rafter\",\n\"raftman\",\n\"rafty\",\n\"rag\",\n\"raga\",\n\"rage\",\n\"rageful\",\n\"rageous\",\n\"rager\",\n\"ragfish\",\n\"ragged\",\n\"raggedy\",\n\"raggee\",\n\"ragger\",\n\"raggery\",\n\"raggety\",\n\"raggil\",\n\"raggily\",\n\"ragging\",\n\"raggle\",\n\"raggled\",\n\"raggy\",\n\"raging\",\n\"raglan\",\n\"raglet\",\n\"raglin\",\n\"ragman\",\n\"ragout\",\n\"ragshag\",\n\"ragtag\",\n\"ragtime\",\n\"ragule\",\n\"raguly\",\n\"ragweed\",\n\"ragwort\",\n\"rah\",\n\"rahdar\",\n\"raia\",\n\"raid\",\n\"raider\",\n\"rail\",\n\"railage\",\n\"railer\",\n\"railing\",\n\"railly\",\n\"railman\",\n\"railway\",\n\"raiment\",\n\"rain\",\n\"rainbow\",\n\"rainer\",\n\"rainful\",\n\"rainily\",\n\"rainy\",\n\"raioid\",\n\"rais\",\n\"raise\",\n\"raised\",\n\"raiser\",\n\"raisin\",\n\"raising\",\n\"raisiny\",\n\"raj\",\n\"raja\",\n\"rajah\",\n\"rakan\",\n\"rake\",\n\"rakeage\",\n\"rakeful\",\n\"raker\",\n\"rakery\",\n\"rakh\",\n\"raki\",\n\"rakily\",\n\"raking\",\n\"rakish\",\n\"rakit\",\n\"raku\",\n\"rallier\",\n\"ralline\",\n\"rally\",\n\"ralph\",\n\"ram\",\n\"ramada\",\n\"ramage\",\n\"ramal\",\n\"ramanas\",\n\"ramass\",\n\"ramate\",\n\"rambeh\",\n\"ramble\",\n\"rambler\",\n\"rambong\",\n\"rame\",\n\"rameal\",\n\"ramed\",\n\"ramekin\",\n\"rament\",\n\"rameous\",\n\"ramet\",\n\"ramex\",\n\"ramhead\",\n\"ramhood\",\n\"rami\",\n\"ramie\",\n\"ramify\",\n\"ramlike\",\n\"ramline\",\n\"rammack\",\n\"rammel\",\n\"rammer\",\n\"rammish\",\n\"rammy\",\n\"ramose\",\n\"ramous\",\n\"ramp\",\n\"rampage\",\n\"rampant\",\n\"rampart\",\n\"ramped\",\n\"ramper\",\n\"rampick\",\n\"rampike\",\n\"ramping\",\n\"rampion\",\n\"rampire\",\n\"rampler\",\n\"ramplor\",\n\"ramrace\",\n\"ramrod\",\n\"ramsch\",\n\"ramson\",\n\"ramstam\",\n\"ramtil\",\n\"ramular\",\n\"ramule\",\n\"ramulus\",\n\"ramus\",\n\"ran\",\n\"rana\",\n\"ranal\",\n\"rance\",\n\"rancel\",\n\"rancer\",\n\"ranch\",\n\"ranche\",\n\"rancher\",\n\"rancho\",\n\"rancid\",\n\"rancor\",\n\"rand\",\n\"randan\",\n\"randem\",\n\"rander\",\n\"randing\",\n\"randir\",\n\"randle\",\n\"random\",\n\"randy\",\n\"rane\",\n\"rang\",\n\"range\",\n\"ranged\",\n\"ranger\",\n\"rangey\",\n\"ranging\",\n\"rangle\",\n\"rangler\",\n\"rangy\",\n\"rani\",\n\"ranid\",\n\"ranine\",\n\"rank\",\n\"ranked\",\n\"ranker\",\n\"rankish\",\n\"rankle\",\n\"rankly\",\n\"rann\",\n\"rannel\",\n\"ranny\",\n\"ransack\",\n\"ransel\",\n\"ransom\",\n\"rant\",\n\"rantan\",\n\"ranter\",\n\"ranting\",\n\"rantock\",\n\"ranty\",\n\"ranula\",\n\"ranular\",\n\"rap\",\n\"rape\",\n\"rapeful\",\n\"raper\",\n\"raphany\",\n\"raphe\",\n\"raphide\",\n\"raphis\",\n\"rapic\",\n\"rapid\",\n\"rapidly\",\n\"rapier\",\n\"rapillo\",\n\"rapine\",\n\"rapiner\",\n\"raping\",\n\"rapinic\",\n\"rapist\",\n\"raploch\",\n\"rappage\",\n\"rappe\",\n\"rappel\",\n\"rapper\",\n\"rapping\",\n\"rappist\",\n\"rapport\",\n\"rapt\",\n\"raptly\",\n\"raptor\",\n\"raptril\",\n\"rapture\",\n\"raptury\",\n\"raptus\",\n\"rare\",\n\"rarebit\",\n\"rarefy\",\n\"rarely\",\n\"rarish\",\n\"rarity\",\n\"ras\",\n\"rasa\",\n\"rasant\",\n\"rascal\",\n\"rasceta\",\n\"rase\",\n\"rasen\",\n\"raser\",\n\"rasgado\",\n\"rash\",\n\"rasher\",\n\"rashful\",\n\"rashing\",\n\"rashly\",\n\"rasion\",\n\"rasp\",\n\"rasped\",\n\"rasper\",\n\"rasping\",\n\"raspish\",\n\"raspite\",\n\"raspy\",\n\"rasse\",\n\"rassle\",\n\"raster\",\n\"rastik\",\n\"rastle\",\n\"rasure\",\n\"rat\",\n\"rata\",\n\"ratable\",\n\"ratably\",\n\"ratafee\",\n\"ratafia\",\n\"ratal\",\n\"ratbite\",\n\"ratch\",\n\"ratchel\",\n\"ratcher\",\n\"ratchet\",\n\"rate\",\n\"rated\",\n\"ratel\",\n\"rater\",\n\"ratfish\",\n\"rath\",\n\"rathe\",\n\"rathed\",\n\"rathely\",\n\"rather\",\n\"rathest\",\n\"rathite\",\n\"rathole\",\n\"ratify\",\n\"ratine\",\n\"rating\",\n\"ratio\",\n\"ration\",\n\"ratite\",\n\"ratlike\",\n\"ratline\",\n\"ratoon\",\n\"rattage\",\n\"rattail\",\n\"rattan\",\n\"ratteen\",\n\"ratten\",\n\"ratter\",\n\"rattery\",\n\"ratti\",\n\"rattish\",\n\"rattle\",\n\"rattled\",\n\"rattler\",\n\"rattles\",\n\"rattly\",\n\"ratton\",\n\"rattrap\",\n\"ratty\",\n\"ratwa\",\n\"ratwood\",\n\"raucid\",\n\"raucity\",\n\"raucous\",\n\"raught\",\n\"rauk\",\n\"raukle\",\n\"rauli\",\n\"raun\",\n\"raunge\",\n\"raupo\",\n\"rauque\",\n\"ravage\",\n\"ravager\",\n\"rave\",\n\"ravel\",\n\"raveler\",\n\"ravelin\",\n\"ravelly\",\n\"raven\",\n\"ravener\",\n\"ravenry\",\n\"ravens\",\n\"raver\",\n\"ravin\",\n\"ravine\",\n\"ravined\",\n\"raviney\",\n\"raving\",\n\"ravioli\",\n\"ravish\",\n\"ravison\",\n\"raw\",\n\"rawhead\",\n\"rawhide\",\n\"rawish\",\n\"rawness\",\n\"rax\",\n\"ray\",\n\"raya\",\n\"rayage\",\n\"rayed\",\n\"rayful\",\n\"rayless\",\n\"raylet\",\n\"rayon\",\n\"raze\",\n\"razee\",\n\"razer\",\n\"razoo\",\n\"razor\",\n\"razz\",\n\"razzia\",\n\"razzly\",\n\"re\",\n\"rea\",\n\"reaal\",\n\"reabuse\",\n\"reach\",\n\"reacher\",\n\"reachy\",\n\"react\",\n\"reactor\",\n\"read\",\n\"readapt\",\n\"readd\",\n\"reader\",\n\"readily\",\n\"reading\",\n\"readmit\",\n\"readopt\",\n\"readorn\",\n\"ready\",\n\"reagent\",\n\"reagin\",\n\"reagree\",\n\"reak\",\n\"real\",\n\"realarm\",\n\"reales\",\n\"realest\",\n\"realgar\",\n\"realign\",\n\"realism\",\n\"realist\",\n\"reality\",\n\"realive\",\n\"realize\",\n\"reallot\",\n\"reallow\",\n\"really\",\n\"realm\",\n\"realter\",\n\"realtor\",\n\"realty\",\n\"ream\",\n\"reamage\",\n\"reamass\",\n\"reamend\",\n\"reamer\",\n\"reamuse\",\n\"reamy\",\n\"reannex\",\n\"reannoy\",\n\"reanvil\",\n\"reap\",\n\"reaper\",\n\"reapply\",\n\"rear\",\n\"rearer\",\n\"reargue\",\n\"rearise\",\n\"rearm\",\n\"rearray\",\n\"reask\",\n\"reason\",\n\"reassay\",\n\"reasty\",\n\"reasy\",\n\"reatus\",\n\"reaudit\",\n\"reavail\",\n\"reave\",\n\"reaver\",\n\"reavoid\",\n\"reavow\",\n\"reawait\",\n\"reawake\",\n\"reaward\",\n\"reaware\",\n\"reb\",\n\"rebab\",\n\"reback\",\n\"rebag\",\n\"rebait\",\n\"rebake\",\n\"rebale\",\n\"reban\",\n\"rebar\",\n\"rebase\",\n\"rebasis\",\n\"rebate\",\n\"rebater\",\n\"rebathe\",\n\"rebato\",\n\"rebawl\",\n\"rebear\",\n\"rebeat\",\n\"rebec\",\n\"rebeck\",\n\"rebed\",\n\"rebeg\",\n\"rebeget\",\n\"rebegin\",\n\"rebel\",\n\"rebelly\",\n\"rebend\",\n\"rebeset\",\n\"rebia\",\n\"rebias\",\n\"rebid\",\n\"rebill\",\n\"rebind\",\n\"rebirth\",\n\"rebite\",\n\"reblade\",\n\"reblame\",\n\"reblast\",\n\"reblend\",\n\"rebless\",\n\"reblock\",\n\"rebloom\",\n\"reblot\",\n\"reblow\",\n\"reblue\",\n\"rebluff\",\n\"reboant\",\n\"reboard\",\n\"reboast\",\n\"rebob\",\n\"reboil\",\n\"reboise\",\n\"rebold\",\n\"rebolt\",\n\"rebone\",\n\"rebook\",\n\"rebop\",\n\"rebore\",\n\"reborn\",\n\"rebound\",\n\"rebox\",\n\"rebrace\",\n\"rebraid\",\n\"rebrand\",\n\"rebreed\",\n\"rebrew\",\n\"rebribe\",\n\"rebrick\",\n\"rebring\",\n\"rebrown\",\n\"rebrush\",\n\"rebud\",\n\"rebuff\",\n\"rebuild\",\n\"rebuilt\",\n\"rebuke\",\n\"rebuker\",\n\"rebulk\",\n\"rebunch\",\n\"rebuoy\",\n\"reburn\",\n\"reburst\",\n\"rebury\",\n\"rebus\",\n\"rebush\",\n\"rebusy\",\n\"rebut\",\n\"rebute\",\n\"rebuy\",\n\"recable\",\n\"recage\",\n\"recalk\",\n\"recall\",\n\"recant\",\n\"recap\",\n\"recarry\",\n\"recart\",\n\"recarve\",\n\"recase\",\n\"recash\",\n\"recast\",\n\"recatch\",\n\"recce\",\n\"recco\",\n\"reccy\",\n\"recede\",\n\"receder\",\n\"receipt\",\n\"receive\",\n\"recency\",\n\"recense\",\n\"recent\",\n\"recept\",\n\"recess\",\n\"rechafe\",\n\"rechain\",\n\"rechal\",\n\"rechant\",\n\"rechaos\",\n\"rechar\",\n\"rechase\",\n\"rechaw\",\n\"recheat\",\n\"recheck\",\n\"recheer\",\n\"rechew\",\n\"rechip\",\n\"rechuck\",\n\"rechurn\",\n\"recipe\",\n\"recital\",\n\"recite\",\n\"reciter\",\n\"reck\",\n\"reckla\",\n\"reckon\",\n\"reclaim\",\n\"reclama\",\n\"reclang\",\n\"reclasp\",\n\"reclass\",\n\"reclean\",\n\"reclear\",\n\"reclimb\",\n\"recline\",\n\"reclose\",\n\"recluse\",\n\"recoach\",\n\"recoal\",\n\"recoast\",\n\"recoat\",\n\"recock\",\n\"recoct\",\n\"recode\",\n\"recoil\",\n\"recoin\",\n\"recoke\",\n\"recolor\",\n\"recomb\",\n\"recon\",\n\"recook\",\n\"recool\",\n\"recopy\",\n\"record\",\n\"recork\",\n\"recount\",\n\"recoup\",\n\"recover\",\n\"recramp\",\n\"recrank\",\n\"recrate\",\n\"recrew\",\n\"recroon\",\n\"recrop\",\n\"recross\",\n\"recrowd\",\n\"recrown\",\n\"recruit\",\n\"recrush\",\n\"rect\",\n\"recta\",\n\"rectal\",\n\"recti\",\n\"rectify\",\n\"rection\",\n\"recto\",\n\"rector\",\n\"rectory\",\n\"rectrix\",\n\"rectum\",\n\"rectus\",\n\"recur\",\n\"recure\",\n\"recurl\",\n\"recurse\",\n\"recurve\",\n\"recuse\",\n\"recut\",\n\"recycle\",\n\"red\",\n\"redact\",\n\"redan\",\n\"redare\",\n\"redarn\",\n\"redart\",\n\"redate\",\n\"redaub\",\n\"redawn\",\n\"redback\",\n\"redbait\",\n\"redbill\",\n\"redbird\",\n\"redbone\",\n\"redbuck\",\n\"redbud\",\n\"redcap\",\n\"redcoat\",\n\"redd\",\n\"redden\",\n\"redder\",\n\"redding\",\n\"reddish\",\n\"reddock\",\n\"reddy\",\n\"rede\",\n\"redeal\",\n\"redebit\",\n\"redeck\",\n\"redeed\",\n\"redeem\",\n\"redefer\",\n\"redefy\",\n\"redeify\",\n\"redelay\",\n\"redeny\",\n\"redeye\",\n\"redfin\",\n\"redfish\",\n\"redfoot\",\n\"redhead\",\n\"redhoop\",\n\"redia\",\n\"redient\",\n\"redig\",\n\"redip\",\n\"redive\",\n\"redleg\",\n\"redlegs\",\n\"redly\",\n\"redness\",\n\"redo\",\n\"redock\",\n\"redoom\",\n\"redoubt\",\n\"redound\",\n\"redowa\",\n\"redox\",\n\"redpoll\",\n\"redraft\",\n\"redrag\",\n\"redrape\",\n\"redraw\",\n\"redream\",\n\"redress\",\n\"redrill\",\n\"redrive\",\n\"redroot\",\n\"redry\",\n\"redsear\",\n\"redskin\",\n\"redtab\",\n\"redtail\",\n\"redtop\",\n\"redub\",\n\"reduce\",\n\"reduced\",\n\"reducer\",\n\"reduct\",\n\"redue\",\n\"redux\",\n\"redward\",\n\"redware\",\n\"redweed\",\n\"redwing\",\n\"redwood\",\n\"redye\",\n\"ree\",\n\"reechy\",\n\"reed\",\n\"reeded\",\n\"reeden\",\n\"reeder\",\n\"reedily\",\n\"reeding\",\n\"reedish\",\n\"reedman\",\n\"reedy\",\n\"reef\",\n\"reefer\",\n\"reefing\",\n\"reefy\",\n\"reek\",\n\"reeker\",\n\"reeky\",\n\"reel\",\n\"reeled\",\n\"reeler\",\n\"reem\",\n\"reeming\",\n\"reemish\",\n\"reen\",\n\"reenge\",\n\"reeper\",\n\"reese\",\n\"reeshle\",\n\"reesk\",\n\"reesle\",\n\"reest\",\n\"reester\",\n\"reestle\",\n\"reesty\",\n\"reet\",\n\"reetam\",\n\"reetle\",\n\"reeve\",\n\"ref\",\n\"reface\",\n\"refall\",\n\"refan\",\n\"refavor\",\n\"refect\",\n\"refeed\",\n\"refeel\",\n\"refeign\",\n\"refel\",\n\"refence\",\n\"refer\",\n\"referee\",\n\"refetch\",\n\"refight\",\n\"refill\",\n\"refilm\",\n\"refind\",\n\"refine\",\n\"refined\",\n\"refiner\",\n\"refire\",\n\"refit\",\n\"refix\",\n\"reflag\",\n\"reflame\",\n\"reflash\",\n\"reflate\",\n\"reflect\",\n\"reflee\",\n\"reflex\",\n\"refling\",\n\"refloat\",\n\"reflog\",\n\"reflood\",\n\"refloor\",\n\"reflow\",\n\"reflush\",\n\"reflux\",\n\"refly\",\n\"refocus\",\n\"refold\",\n\"refont\",\n\"refool\",\n\"refoot\",\n\"reforce\",\n\"reford\",\n\"reforge\",\n\"reform\",\n\"refound\",\n\"refract\",\n\"refrain\",\n\"reframe\",\n\"refresh\",\n\"refront\",\n\"reft\",\n\"refuel\",\n\"refuge\",\n\"refugee\",\n\"refulge\",\n\"refund\",\n\"refurl\",\n\"refusal\",\n\"refuse\",\n\"refuser\",\n\"refutal\",\n\"refute\",\n\"refuter\",\n\"reg\",\n\"regain\",\n\"regal\",\n\"regale\",\n\"regaler\",\n\"regalia\",\n\"regally\",\n\"regard\",\n\"regatta\",\n\"regauge\",\n\"regency\",\n\"regent\",\n\"reges\",\n\"reget\",\n\"regia\",\n\"regift\",\n\"regild\",\n\"regill\",\n\"regime\",\n\"regimen\",\n\"regin\",\n\"reginal\",\n\"region\",\n\"regive\",\n\"reglair\",\n\"reglaze\",\n\"regle\",\n\"reglet\",\n\"regloss\",\n\"reglove\",\n\"reglow\",\n\"reglue\",\n\"regma\",\n\"regnal\",\n\"regnant\",\n\"regorge\",\n\"regrade\",\n\"regraft\",\n\"regrant\",\n\"regrasp\",\n\"regrass\",\n\"regrate\",\n\"regrede\",\n\"regreen\",\n\"regreet\",\n\"regress\",\n\"regret\",\n\"regrind\",\n\"regrip\",\n\"regroup\",\n\"regrow\",\n\"reguard\",\n\"reguide\",\n\"regula\",\n\"regular\",\n\"reguli\",\n\"regulus\",\n\"regur\",\n\"regurge\",\n\"regush\",\n\"reh\",\n\"rehair\",\n\"rehale\",\n\"rehang\",\n\"reharm\",\n\"rehash\",\n\"rehaul\",\n\"rehead\",\n\"reheal\",\n\"reheap\",\n\"rehear\",\n\"reheat\",\n\"rehedge\",\n\"reheel\",\n\"rehoe\",\n\"rehoist\",\n\"rehonor\",\n\"rehood\",\n\"rehook\",\n\"rehoop\",\n\"rehouse\",\n\"rehung\",\n\"reif\",\n\"reify\",\n\"reign\",\n\"reim\",\n\"reimage\",\n\"reimpel\",\n\"reimply\",\n\"rein\",\n\"reina\",\n\"reincur\",\n\"reindue\",\n\"reinfer\",\n\"reins\",\n\"reinter\",\n\"reis\",\n\"reissue\",\n\"reit\",\n\"reitbok\",\n\"reiter\",\n\"reiver\",\n\"rejail\",\n\"reject\",\n\"rejerk\",\n\"rejoice\",\n\"rejoin\",\n\"rejolt\",\n\"rejudge\",\n\"rekick\",\n\"rekill\",\n\"reking\",\n\"rekiss\",\n\"reknit\",\n\"reknow\",\n\"rel\",\n\"relabel\",\n\"relace\",\n\"relade\",\n\"reladen\",\n\"relais\",\n\"relamp\",\n\"reland\",\n\"relap\",\n\"relapse\",\n\"relast\",\n\"relata\",\n\"relatch\",\n\"relate\",\n\"related\",\n\"relater\",\n\"relator\",\n\"relatum\",\n\"relax\",\n\"relaxed\",\n\"relaxer\",\n\"relay\",\n\"relbun\",\n\"relead\",\n\"releap\",\n\"relearn\",\n\"release\",\n\"relend\",\n\"relent\",\n\"relet\",\n\"relevel\",\n\"relevy\",\n\"reliant\",\n\"relic\",\n\"relick\",\n\"relict\",\n\"relief\",\n\"relier\",\n\"relieve\",\n\"relievo\",\n\"relift\",\n\"relight\",\n\"relime\",\n\"relimit\",\n\"reline\",\n\"reliner\",\n\"relink\",\n\"relish\",\n\"relishy\",\n\"relist\",\n\"relive\",\n\"reload\",\n\"reloan\",\n\"relock\",\n\"relodge\",\n\"relook\",\n\"relose\",\n\"relost\",\n\"relot\",\n\"relove\",\n\"relower\",\n\"reluct\",\n\"relume\",\n\"rely\",\n\"remade\",\n\"remail\",\n\"remain\",\n\"remains\",\n\"remake\",\n\"remaker\",\n\"reman\",\n\"remand\",\n\"remanet\",\n\"remap\",\n\"remarch\",\n\"remark\",\n\"remarry\",\n\"remask\",\n\"remass\",\n\"remast\",\n\"rematch\",\n\"remble\",\n\"remeant\",\n\"remede\",\n\"remedy\",\n\"remeet\",\n\"remelt\",\n\"remend\",\n\"remerge\",\n\"remetal\",\n\"remex\",\n\"remica\",\n\"remicle\",\n\"remiges\",\n\"remill\",\n\"remimic\",\n\"remind\",\n\"remint\",\n\"remiped\",\n\"remise\",\n\"remiss\",\n\"remit\",\n\"remix\",\n\"remnant\",\n\"remock\",\n\"remodel\",\n\"remold\",\n\"remop\",\n\"remora\",\n\"remord\",\n\"remorse\",\n\"remote\",\n\"remould\",\n\"remount\",\n\"removal\",\n\"remove\",\n\"removed\",\n\"remover\",\n\"renable\",\n\"renably\",\n\"renail\",\n\"renal\",\n\"rename\",\n\"rend\",\n\"render\",\n\"reneg\",\n\"renege\",\n\"reneger\",\n\"renegue\",\n\"renerve\",\n\"renes\",\n\"renet\",\n\"renew\",\n\"renewal\",\n\"renewer\",\n\"renin\",\n\"renish\",\n\"renk\",\n\"renky\",\n\"renne\",\n\"rennet\",\n\"rennin\",\n\"renown\",\n\"rent\",\n\"rentage\",\n\"rental\",\n\"rented\",\n\"rentee\",\n\"renter\",\n\"renvoi\",\n\"renvoy\",\n\"reoccur\",\n\"reoffer\",\n\"reoil\",\n\"reomit\",\n\"reopen\",\n\"reorder\",\n\"reown\",\n\"rep\",\n\"repace\",\n\"repack\",\n\"repage\",\n\"repaint\",\n\"repair\",\n\"repale\",\n\"repand\",\n\"repanel\",\n\"repaper\",\n\"repark\",\n\"repass\",\n\"repast\",\n\"repaste\",\n\"repatch\",\n\"repave\",\n\"repawn\",\n\"repay\",\n\"repayal\",\n\"repeal\",\n\"repeat\",\n\"repeg\",\n\"repel\",\n\"repen\",\n\"repent\",\n\"repew\",\n\"rephase\",\n\"repic\",\n\"repick\",\n\"repiece\",\n\"repile\",\n\"repin\",\n\"repine\",\n\"repiner\",\n\"repipe\",\n\"repique\",\n\"repitch\",\n\"repkie\",\n\"replace\",\n\"replait\",\n\"replan\",\n\"replane\",\n\"replant\",\n\"replate\",\n\"replay\",\n\"replead\",\n\"repleat\",\n\"replete\",\n\"replevy\",\n\"replica\",\n\"replier\",\n\"replod\",\n\"replot\",\n\"replow\",\n\"replum\",\n\"replume\",\n\"reply\",\n\"repoint\",\n\"repoll\",\n\"repolon\",\n\"repone\",\n\"repope\",\n\"report\",\n\"reposal\",\n\"repose\",\n\"reposed\",\n\"reposer\",\n\"reposit\",\n\"repost\",\n\"repot\",\n\"repound\",\n\"repour\",\n\"repp\",\n\"repped\",\n\"repray\",\n\"repress\",\n\"reprice\",\n\"reprime\",\n\"reprint\",\n\"reprise\",\n\"reproof\",\n\"reprove\",\n\"reprune\",\n\"reps\",\n\"reptant\",\n\"reptile\",\n\"repuff\",\n\"repugn\",\n\"repulse\",\n\"repump\",\n\"repurge\",\n\"repute\",\n\"reputed\",\n\"requeen\",\n\"request\",\n\"requiem\",\n\"requin\",\n\"require\",\n\"requit\",\n\"requite\",\n\"requiz\",\n\"requote\",\n\"rerack\",\n\"rerail\",\n\"reraise\",\n\"rerake\",\n\"rerank\",\n\"rerate\",\n\"reread\",\n\"reredos\",\n\"reree\",\n\"rereel\",\n\"rereeve\",\n\"rereign\",\n\"rerent\",\n\"rerig\",\n\"rering\",\n\"rerise\",\n\"rerival\",\n\"rerivet\",\n\"rerob\",\n\"rerobe\",\n\"reroll\",\n\"reroof\",\n\"reroot\",\n\"rerope\",\n\"reroute\",\n\"rerow\",\n\"rerub\",\n\"rerun\",\n\"resaca\",\n\"resack\",\n\"resail\",\n\"resale\",\n\"resalt\",\n\"resaw\",\n\"resawer\",\n\"resay\",\n\"rescan\",\n\"rescind\",\n\"rescore\",\n\"rescrub\",\n\"rescue\",\n\"rescuer\",\n\"reseal\",\n\"reseam\",\n\"reseat\",\n\"resect\",\n\"reseda\",\n\"resee\",\n\"reseed\",\n\"reseek\",\n\"reseise\",\n\"reseize\",\n\"reself\",\n\"resell\",\n\"resend\",\n\"resene\",\n\"resent\",\n\"reserve\",\n\"reset\",\n\"resever\",\n\"resew\",\n\"resex\",\n\"resh\",\n\"reshake\",\n\"reshape\",\n\"reshare\",\n\"reshave\",\n\"reshear\",\n\"reshift\",\n\"reshine\",\n\"reship\",\n\"reshoe\",\n\"reshoot\",\n\"reshun\",\n\"reshunt\",\n\"reshut\",\n\"reside\",\n\"resider\",\n\"residua\",\n\"residue\",\n\"resift\",\n\"resigh\",\n\"resign\",\n\"resile\",\n\"resin\",\n\"resina\",\n\"resiner\",\n\"resing\",\n\"resinic\",\n\"resink\",\n\"resinol\",\n\"resiny\",\n\"resist\",\n\"resize\",\n\"resizer\",\n\"reskin\",\n\"reslash\",\n\"reslate\",\n\"reslay\",\n\"reslide\",\n\"reslot\",\n\"resmell\",\n\"resmelt\",\n\"resmile\",\n\"resnap\",\n\"resnub\",\n\"resoak\",\n\"resoap\",\n\"resoil\",\n\"resole\",\n\"resolve\",\n\"resorb\",\n\"resort\",\n\"resound\",\n\"resow\",\n\"resp\",\n\"respace\",\n\"respade\",\n\"respan\",\n\"respeak\",\n\"respect\",\n\"respell\",\n\"respin\",\n\"respire\",\n\"respite\",\n\"resplit\",\n\"respoke\",\n\"respond\",\n\"respot\",\n\"respray\",\n\"respue\",\n\"ressala\",\n\"ressaut\",\n\"rest\",\n\"restack\",\n\"restaff\",\n\"restain\",\n\"restake\",\n\"restamp\",\n\"restant\",\n\"restart\",\n\"restate\",\n\"restaur\",\n\"resteal\",\n\"resteel\",\n\"resteep\",\n\"restem\",\n\"restep\",\n\"rester\",\n\"restes\",\n\"restful\",\n\"restiad\",\n\"restiff\",\n\"resting\",\n\"restir\",\n\"restis\",\n\"restive\",\n\"restock\",\n\"restore\",\n\"restow\",\n\"restrap\",\n\"restrip\",\n\"restudy\",\n\"restuff\",\n\"resty\",\n\"restyle\",\n\"resuck\",\n\"resue\",\n\"resuing\",\n\"resuit\",\n\"result\",\n\"resume\",\n\"resumer\",\n\"resun\",\n\"resup\",\n\"resurge\",\n\"reswage\",\n\"resward\",\n\"reswarm\",\n\"reswear\",\n\"resweat\",\n\"resweep\",\n\"reswell\",\n\"reswill\",\n\"reswim\",\n\"ret\",\n\"retable\",\n\"retack\",\n\"retag\",\n\"retail\",\n\"retain\",\n\"retake\",\n\"retaker\",\n\"retalk\",\n\"retama\",\n\"retame\",\n\"retan\",\n\"retape\",\n\"retard\",\n\"retare\",\n\"retaste\",\n\"retax\",\n\"retch\",\n\"reteach\",\n\"retell\",\n\"retem\",\n\"retempt\",\n\"retene\",\n\"retent\",\n\"retest\",\n\"rethank\",\n\"rethaw\",\n\"rethe\",\n\"rethink\",\n\"rethrow\",\n\"retia\",\n\"retial\",\n\"retiary\",\n\"reticle\",\n\"retie\",\n\"retier\",\n\"retile\",\n\"retill\",\n\"retime\",\n\"retin\",\n\"retina\",\n\"retinal\",\n\"retinol\",\n\"retinue\",\n\"retip\",\n\"retiral\",\n\"retire\",\n\"retired\",\n\"retirer\",\n\"retoast\",\n\"retold\",\n\"retomb\",\n\"retook\",\n\"retool\",\n\"retooth\",\n\"retort\",\n\"retoss\",\n\"retotal\",\n\"retouch\",\n\"retour\",\n\"retrace\",\n\"retrack\",\n\"retract\",\n\"retrad\",\n\"retrade\",\n\"retrain\",\n\"retral\",\n\"retramp\",\n\"retread\",\n\"retreat\",\n\"retree\",\n\"retrial\",\n\"retrim\",\n\"retrip\",\n\"retrot\",\n\"retrude\",\n\"retrue\",\n\"retrust\",\n\"retry\",\n\"retted\",\n\"retter\",\n\"rettery\",\n\"retting\",\n\"rettory\",\n\"retube\",\n\"retuck\",\n\"retune\",\n\"returf\",\n\"return\",\n\"retuse\",\n\"retwine\",\n\"retwist\",\n\"retying\",\n\"retype\",\n\"retzian\",\n\"reune\",\n\"reunify\",\n\"reunion\",\n\"reunite\",\n\"reurge\",\n\"reuse\",\n\"reutter\",\n\"rev\",\n\"revalue\",\n\"revamp\",\n\"revary\",\n\"reve\",\n\"reveal\",\n\"reveil\",\n\"revel\",\n\"reveler\",\n\"revelly\",\n\"revelry\",\n\"revend\",\n\"revenge\",\n\"revent\",\n\"revenue\",\n\"rever\",\n\"reverb\",\n\"revere\",\n\"revered\",\n\"reverer\",\n\"reverie\",\n\"revers\",\n\"reverse\",\n\"reversi\",\n\"reverso\",\n\"revert\",\n\"revery\",\n\"revest\",\n\"revet\",\n\"revete\",\n\"revie\",\n\"review\",\n\"revile\",\n\"reviler\",\n\"revisal\",\n\"revise\",\n\"revisee\",\n\"reviser\",\n\"revisit\",\n\"revisor\",\n\"revival\",\n\"revive\",\n\"reviver\",\n\"revivor\",\n\"revoice\",\n\"revoke\",\n\"revoker\",\n\"revolt\",\n\"revolve\",\n\"revomit\",\n\"revote\",\n\"revue\",\n\"revuist\",\n\"rewade\",\n\"rewager\",\n\"rewake\",\n\"rewaken\",\n\"rewall\",\n\"reward\",\n\"rewarm\",\n\"rewarn\",\n\"rewash\",\n\"rewater\",\n\"rewave\",\n\"rewax\",\n\"rewayle\",\n\"rewear\",\n\"reweave\",\n\"rewed\",\n\"reweigh\",\n\"reweld\",\n\"rewend\",\n\"rewet\",\n\"rewhelp\",\n\"rewhirl\",\n\"rewiden\",\n\"rewin\",\n\"rewind\",\n\"rewire\",\n\"rewish\",\n\"rewood\",\n\"reword\",\n\"rework\",\n\"rewound\",\n\"rewove\",\n\"rewoven\",\n\"rewrap\",\n\"rewrite\",\n\"rex\",\n\"rexen\",\n\"reyield\",\n\"reyoke\",\n\"reyouth\",\n\"rhabdom\",\n\"rhabdos\",\n\"rhabdus\",\n\"rhagite\",\n\"rhagon\",\n\"rhagose\",\n\"rhamn\",\n\"rhamnal\",\n\"rhason\",\n\"rhatany\",\n\"rhe\",\n\"rhea\",\n\"rhebok\",\n\"rheeboc\",\n\"rheebok\",\n\"rheen\",\n\"rheic\",\n\"rhein\",\n\"rheinic\",\n\"rhema\",\n\"rheme\",\n\"rhenium\",\n\"rheotan\",\n\"rhesian\",\n\"rhesus\",\n\"rhetor\",\n\"rheum\",\n\"rheumed\",\n\"rheumic\",\n\"rheumy\",\n\"rhexis\",\n\"rhinal\",\n\"rhine\",\n\"rhinion\",\n\"rhino\",\n\"rhizine\",\n\"rhizoid\",\n\"rhizoma\",\n\"rhizome\",\n\"rhizote\",\n\"rho\",\n\"rhodic\",\n\"rhoding\",\n\"rhodite\",\n\"rhodium\",\n\"rhomb\",\n\"rhombic\",\n\"rhombos\",\n\"rhombus\",\n\"rhubarb\",\n\"rhumb\",\n\"rhumba\",\n\"rhyme\",\n\"rhymer\",\n\"rhymery\",\n\"rhymic\",\n\"rhymist\",\n\"rhymy\",\n\"rhyptic\",\n\"rhythm\",\n\"rhyton\",\n\"ria\",\n\"rial\",\n\"riancy\",\n\"riant\",\n\"riantly\",\n\"riata\",\n\"rib\",\n\"ribald\",\n\"riband\",\n\"ribat\",\n\"ribband\",\n\"ribbed\",\n\"ribber\",\n\"ribbet\",\n\"ribbing\",\n\"ribble\",\n\"ribbon\",\n\"ribbony\",\n\"ribby\",\n\"ribe\",\n\"ribless\",\n\"riblet\",\n\"riblike\",\n\"ribonic\",\n\"ribose\",\n\"ribskin\",\n\"ribwork\",\n\"ribwort\",\n\"rice\",\n\"ricer\",\n\"ricey\",\n\"rich\",\n\"richdom\",\n\"richen\",\n\"riches\",\n\"richly\",\n\"richt\",\n\"ricin\",\n\"ricine\",\n\"ricinic\",\n\"ricinus\",\n\"rick\",\n\"ricker\",\n\"rickets\",\n\"rickety\",\n\"rickey\",\n\"rickle\",\n\"ricksha\",\n\"ricrac\",\n\"rictal\",\n\"rictus\",\n\"rid\",\n\"ridable\",\n\"ridably\",\n\"riddam\",\n\"riddel\",\n\"ridden\",\n\"ridder\",\n\"ridding\",\n\"riddle\",\n\"riddler\",\n\"ride\",\n\"rideau\",\n\"riden\",\n\"rident\",\n\"rider\",\n\"ridered\",\n\"ridge\",\n\"ridged\",\n\"ridgel\",\n\"ridger\",\n\"ridgil\",\n\"ridging\",\n\"ridgy\",\n\"riding\",\n\"ridotto\",\n\"rie\",\n\"riem\",\n\"riempie\",\n\"rier\",\n\"rife\",\n\"rifely\",\n\"riff\",\n\"riffle\",\n\"riffler\",\n\"rifle\",\n\"rifler\",\n\"riflery\",\n\"rifling\",\n\"rift\",\n\"rifter\",\n\"rifty\",\n\"rig\",\n\"rigbane\",\n\"riggald\",\n\"rigger\",\n\"rigging\",\n\"riggish\",\n\"riggite\",\n\"riggot\",\n\"right\",\n\"righten\",\n\"righter\",\n\"rightle\",\n\"rightly\",\n\"righto\",\n\"righty\",\n\"rigid\",\n\"rigidly\",\n\"rigling\",\n\"rignum\",\n\"rigol\",\n\"rigor\",\n\"rigsby\",\n\"rikisha\",\n\"rikk\",\n\"riksha\",\n\"rikshaw\",\n\"rilawa\",\n\"rile\",\n\"riley\",\n\"rill\",\n\"rillet\",\n\"rillett\",\n\"rillock\",\n\"rilly\",\n\"rim\",\n\"rima\",\n\"rimal\",\n\"rimate\",\n\"rimbase\",\n\"rime\",\n\"rimer\",\n\"rimfire\",\n\"rimland\",\n\"rimless\",\n\"rimmed\",\n\"rimmer\",\n\"rimose\",\n\"rimous\",\n\"rimpi\",\n\"rimple\",\n\"rimrock\",\n\"rimu\",\n\"rimula\",\n\"rimy\",\n\"rinceau\",\n\"rinch\",\n\"rincon\",\n\"rind\",\n\"rinded\",\n\"rindle\",\n\"rindy\",\n\"rine\",\n\"ring\",\n\"ringe\",\n\"ringed\",\n\"ringent\",\n\"ringer\",\n\"ringeye\",\n\"ringing\",\n\"ringite\",\n\"ringle\",\n\"ringlet\",\n\"ringman\",\n\"ringtaw\",\n\"ringy\",\n\"rink\",\n\"rinka\",\n\"rinker\",\n\"rinkite\",\n\"rinner\",\n\"rinse\",\n\"rinser\",\n\"rinsing\",\n\"rio\",\n\"riot\",\n\"rioter\",\n\"rioting\",\n\"riotist\",\n\"riotous\",\n\"riotry\",\n\"rip\",\n\"ripa\",\n\"ripal\",\n\"ripcord\",\n\"ripe\",\n\"ripely\",\n\"ripen\",\n\"ripener\",\n\"riper\",\n\"ripgut\",\n\"ripieno\",\n\"ripier\",\n\"ripost\",\n\"riposte\",\n\"ripper\",\n\"rippet\",\n\"rippier\",\n\"ripping\",\n\"rippit\",\n\"ripple\",\n\"rippler\",\n\"ripplet\",\n\"ripply\",\n\"rippon\",\n\"riprap\",\n\"ripsack\",\n\"ripsaw\",\n\"ripup\",\n\"risala\",\n\"risberm\",\n\"rise\",\n\"risen\",\n\"riser\",\n\"rishi\",\n\"risible\",\n\"risibly\",\n\"rising\",\n\"risk\",\n\"risker\",\n\"riskful\",\n\"riskily\",\n\"riskish\",\n\"risky\",\n\"risp\",\n\"risper\",\n\"risque\",\n\"risquee\",\n\"rissel\",\n\"risser\",\n\"rissle\",\n\"rissoid\",\n\"rist\",\n\"ristori\",\n\"rit\",\n\"rita\",\n\"rite\",\n\"ritling\",\n\"ritual\",\n\"ritzy\",\n\"riva\",\n\"rivage\",\n\"rival\",\n\"rivalry\",\n\"rive\",\n\"rivel\",\n\"rivell\",\n\"riven\",\n\"river\",\n\"rivered\",\n\"riverly\",\n\"rivery\",\n\"rivet\",\n\"riveter\",\n\"riving\",\n\"rivose\",\n\"rivulet\",\n\"rix\",\n\"rixy\",\n\"riyal\",\n\"rizzar\",\n\"rizzle\",\n\"rizzom\",\n\"roach\",\n\"road\",\n\"roadbed\",\n\"roaded\",\n\"roader\",\n\"roading\",\n\"roadite\",\n\"roadman\",\n\"roadway\",\n\"roam\",\n\"roamage\",\n\"roamer\",\n\"roaming\",\n\"roan\",\n\"roanoke\",\n\"roar\",\n\"roarer\",\n\"roaring\",\n\"roast\",\n\"roaster\",\n\"rob\",\n\"robalo\",\n\"roband\",\n\"robber\",\n\"robbery\",\n\"robbin\",\n\"robbing\",\n\"robe\",\n\"rober\",\n\"roberd\",\n\"robin\",\n\"robinet\",\n\"robing\",\n\"robinin\",\n\"roble\",\n\"robomb\",\n\"robot\",\n\"robotry\",\n\"robur\",\n\"robust\",\n\"roc\",\n\"rocher\",\n\"rochet\",\n\"rock\",\n\"rockaby\",\n\"rocker\",\n\"rockery\",\n\"rocket\",\n\"rockety\",\n\"rocking\",\n\"rockish\",\n\"rocklay\",\n\"rocklet\",\n\"rockman\",\n\"rocky\",\n\"rococo\",\n\"rocta\",\n\"rod\",\n\"rodd\",\n\"roddin\",\n\"rodding\",\n\"rode\",\n\"rodent\",\n\"rodeo\",\n\"rodge\",\n\"rodham\",\n\"roding\",\n\"rodless\",\n\"rodlet\",\n\"rodlike\",\n\"rodman\",\n\"rodney\",\n\"rodsman\",\n\"rodster\",\n\"rodwood\",\n\"roe\",\n\"roebuck\",\n\"roed\",\n\"roelike\",\n\"roer\",\n\"roey\",\n\"rog\",\n\"rogan\",\n\"roger\",\n\"roggle\",\n\"rogue\",\n\"roguery\",\n\"roguing\",\n\"roguish\",\n\"rohan\",\n\"rohob\",\n\"rohun\",\n\"rohuna\",\n\"roi\",\n\"roid\",\n\"roil\",\n\"roily\",\n\"roister\",\n\"roit\",\n\"roka\",\n\"roke\",\n\"rokeage\",\n\"rokee\",\n\"rokelay\",\n\"roker\",\n\"rokey\",\n\"roky\",\n\"role\",\n\"roleo\",\n\"roll\",\n\"rolled\",\n\"roller\",\n\"rolley\",\n\"rollick\",\n\"rolling\",\n\"rollix\",\n\"rollmop\",\n\"rollock\",\n\"rollway\",\n\"roloway\",\n\"romaika\",\n\"romaine\",\n\"romal\",\n\"romance\",\n\"romancy\",\n\"romanza\",\n\"romaunt\",\n\"rombos\",\n\"romeite\",\n\"romero\",\n\"rommack\",\n\"romp\",\n\"romper\",\n\"romping\",\n\"rompish\",\n\"rompu\",\n\"rompy\",\n\"roncet\",\n\"ronco\",\n\"rond\",\n\"ronde\",\n\"rondeau\",\n\"rondel\",\n\"rondino\",\n\"rondle\",\n\"rondo\",\n\"rondure\",\n\"rone\",\n\"rongeur\",\n\"ronquil\",\n\"rontgen\",\n\"ronyon\",\n\"rood\",\n\"roodle\",\n\"roof\",\n\"roofage\",\n\"roofer\",\n\"roofing\",\n\"rooflet\",\n\"roofman\",\n\"roofy\",\n\"rooibok\",\n\"rooinek\",\n\"rook\",\n\"rooker\",\n\"rookery\",\n\"rookie\",\n\"rookish\",\n\"rooklet\",\n\"rooky\",\n\"rool\",\n\"room\",\n\"roomage\",\n\"roomed\",\n\"roomer\",\n\"roomful\",\n\"roomie\",\n\"roomily\",\n\"roomlet\",\n\"roomth\",\n\"roomthy\",\n\"roomy\",\n\"roon\",\n\"roosa\",\n\"roost\",\n\"roosted\",\n\"rooster\",\n\"root\",\n\"rootage\",\n\"rootcap\",\n\"rooted\",\n\"rooter\",\n\"rootery\",\n\"rootle\",\n\"rootlet\",\n\"rooty\",\n\"roove\",\n\"ropable\",\n\"rope\",\n\"ropeman\",\n\"roper\",\n\"ropery\",\n\"ropes\",\n\"ropeway\",\n\"ropily\",\n\"roping\",\n\"ropish\",\n\"ropp\",\n\"ropy\",\n\"roque\",\n\"roquer\",\n\"roquet\",\n\"roquist\",\n\"roral\",\n\"roric\",\n\"rorqual\",\n\"rorty\",\n\"rory\",\n\"rosal\",\n\"rosario\",\n\"rosary\",\n\"rosated\",\n\"roscid\",\n\"rose\",\n\"roseal\",\n\"roseate\",\n\"rosebay\",\n\"rosebud\",\n\"rosed\",\n\"roseine\",\n\"rosel\",\n\"roselet\",\n\"rosella\",\n\"roselle\",\n\"roseola\",\n\"roseous\",\n\"rosery\",\n\"roset\",\n\"rosetan\",\n\"rosette\",\n\"rosetty\",\n\"rosetum\",\n\"rosety\",\n\"rosied\",\n\"rosier\",\n\"rosilla\",\n\"rosillo\",\n\"rosily\",\n\"rosin\",\n\"rosiny\",\n\"rosland\",\n\"rosoli\",\n\"rosolic\",\n\"rosolio\",\n\"ross\",\n\"rosser\",\n\"rossite\",\n\"rostel\",\n\"roster\",\n\"rostra\",\n\"rostral\",\n\"rostrum\",\n\"rosular\",\n\"rosy\",\n\"rot\",\n\"rota\",\n\"rotal\",\n\"rotaman\",\n\"rotan\",\n\"rotang\",\n\"rotary\",\n\"rotate\",\n\"rotated\",\n\"rotator\",\n\"rotch\",\n\"rote\",\n\"rotella\",\n\"roter\",\n\"rotge\",\n\"rotgut\",\n\"rother\",\n\"rotifer\",\n\"roto\",\n\"rotor\",\n\"rottan\",\n\"rotten\",\n\"rotter\",\n\"rotting\",\n\"rottle\",\n\"rottock\",\n\"rottolo\",\n\"rotula\",\n\"rotulad\",\n\"rotular\",\n\"rotulet\",\n\"rotulus\",\n\"rotund\",\n\"rotunda\",\n\"rotundo\",\n\"roub\",\n\"roucou\",\n\"roud\",\n\"roue\",\n\"rouelle\",\n\"rouge\",\n\"rougeau\",\n\"rougeot\",\n\"rough\",\n\"roughen\",\n\"rougher\",\n\"roughet\",\n\"roughie\",\n\"roughly\",\n\"roughy\",\n\"rougy\",\n\"rouille\",\n\"rouky\",\n\"roulade\",\n\"rouleau\",\n\"roun\",\n\"rounce\",\n\"rouncy\",\n\"round\",\n\"rounded\",\n\"roundel\",\n\"rounder\",\n\"roundly\",\n\"roundup\",\n\"roundy\",\n\"roup\",\n\"rouper\",\n\"roupet\",\n\"roupily\",\n\"roupit\",\n\"roupy\",\n\"rouse\",\n\"rouser\",\n\"rousing\",\n\"roust\",\n\"rouster\",\n\"rout\",\n\"route\",\n\"router\",\n\"routh\",\n\"routhie\",\n\"routhy\",\n\"routine\",\n\"routing\",\n\"routous\",\n\"rove\",\n\"rover\",\n\"rovet\",\n\"rovetto\",\n\"roving\",\n\"row\",\n\"rowable\",\n\"rowan\",\n\"rowboat\",\n\"rowdily\",\n\"rowdy\",\n\"rowed\",\n\"rowel\",\n\"rowen\",\n\"rower\",\n\"rowet\",\n\"rowing\",\n\"rowlet\",\n\"rowlock\",\n\"rowport\",\n\"rowty\",\n\"rowy\",\n\"rox\",\n\"roxy\",\n\"royal\",\n\"royale\",\n\"royalet\",\n\"royally\",\n\"royalty\",\n\"royet\",\n\"royt\",\n\"rozum\",\n\"ruach\",\n\"ruana\",\n\"rub\",\n\"rubasse\",\n\"rubato\",\n\"rubbed\",\n\"rubber\",\n\"rubbers\",\n\"rubbery\",\n\"rubbing\",\n\"rubbish\",\n\"rubble\",\n\"rubbler\",\n\"rubbly\",\n\"rubdown\",\n\"rubelet\",\n\"rubella\",\n\"rubelle\",\n\"rubeola\",\n\"rubiate\",\n\"rubican\",\n\"rubidic\",\n\"rubied\",\n\"rubific\",\n\"rubify\",\n\"rubine\",\n\"rubious\",\n\"ruble\",\n\"rublis\",\n\"rubor\",\n\"rubric\",\n\"rubrica\",\n\"rubrify\",\n\"ruby\",\n\"ruche\",\n\"ruching\",\n\"ruck\",\n\"rucker\",\n\"ruckle\",\n\"rucksey\",\n\"ruckus\",\n\"rucky\",\n\"ruction\",\n\"rud\",\n\"rudas\",\n\"rudd\",\n\"rudder\",\n\"ruddied\",\n\"ruddily\",\n\"ruddle\",\n\"ruddock\",\n\"ruddy\",\n\"rude\",\n\"rudely\",\n\"ruderal\",\n\"rudesby\",\n\"rudge\",\n\"rudish\",\n\"rudity\",\n\"rue\",\n\"rueful\",\n\"ruelike\",\n\"ruelle\",\n\"ruen\",\n\"ruer\",\n\"ruesome\",\n\"ruewort\",\n\"ruff\",\n\"ruffed\",\n\"ruffer\",\n\"ruffian\",\n\"ruffin\",\n\"ruffle\",\n\"ruffled\",\n\"ruffler\",\n\"ruffly\",\n\"rufous\",\n\"rufter\",\n\"rufus\",\n\"rug\",\n\"ruga\",\n\"rugate\",\n\"rugged\",\n\"rugging\",\n\"ruggle\",\n\"ruggy\",\n\"ruglike\",\n\"rugosa\",\n\"rugose\",\n\"rugous\",\n\"ruin\",\n\"ruinate\",\n\"ruined\",\n\"ruiner\",\n\"ruing\",\n\"ruinous\",\n\"rukh\",\n\"rulable\",\n\"rule\",\n\"ruledom\",\n\"ruler\",\n\"ruling\",\n\"rull\",\n\"ruller\",\n\"rullion\",\n\"rum\",\n\"rumal\",\n\"rumble\",\n\"rumbler\",\n\"rumbly\",\n\"rumbo\",\n\"rumen\",\n\"ruminal\",\n\"rumkin\",\n\"rumless\",\n\"rumly\",\n\"rummage\",\n\"rummagy\",\n\"rummer\",\n\"rummily\",\n\"rummish\",\n\"rummy\",\n\"rumness\",\n\"rumney\",\n\"rumor\",\n\"rumorer\",\n\"rump\",\n\"rumpad\",\n\"rumpade\",\n\"rumple\",\n\"rumply\",\n\"rumpus\",\n\"rumshop\",\n\"run\",\n\"runaway\",\n\"runback\",\n\"runby\",\n\"runch\",\n\"rundale\",\n\"rundle\",\n\"rundlet\",\n\"rune\",\n\"runed\",\n\"runer\",\n\"runfish\",\n\"rung\",\n\"runic\",\n\"runite\",\n\"runkle\",\n\"runkly\",\n\"runless\",\n\"runlet\",\n\"runman\",\n\"runnel\",\n\"runner\",\n\"runnet\",\n\"running\",\n\"runny\",\n\"runoff\",\n\"runout\",\n\"runover\",\n\"runrig\",\n\"runt\",\n\"runted\",\n\"runtee\",\n\"runtish\",\n\"runty\",\n\"runway\",\n\"rupa\",\n\"rupee\",\n\"rupia\",\n\"rupiah\",\n\"rupial\",\n\"rupie\",\n\"rupitic\",\n\"ruptile\",\n\"ruption\",\n\"ruptive\",\n\"rupture\",\n\"rural\",\n\"rurally\",\n\"rurban\",\n\"ruru\",\n\"ruse\",\n\"rush\",\n\"rushed\",\n\"rushen\",\n\"rusher\",\n\"rushing\",\n\"rushlit\",\n\"rushy\",\n\"rusine\",\n\"rusk\",\n\"ruskin\",\n\"rusky\",\n\"rusma\",\n\"rusot\",\n\"ruspone\",\n\"russel\",\n\"russet\",\n\"russety\",\n\"russia\",\n\"russud\",\n\"rust\",\n\"rustful\",\n\"rustic\",\n\"rustily\",\n\"rustle\",\n\"rustler\",\n\"rustly\",\n\"rustre\",\n\"rustred\",\n\"rusty\",\n\"ruswut\",\n\"rut\",\n\"rutate\",\n\"rutch\",\n\"ruth\",\n\"ruther\",\n\"ruthful\",\n\"rutic\",\n\"rutile\",\n\"rutin\",\n\"ruttee\",\n\"rutter\",\n\"ruttish\",\n\"rutty\",\n\"rutyl\",\n\"ruvid\",\n\"rux\",\n\"ryal\",\n\"ryania\",\n\"rybat\",\n\"ryder\",\n\"rye\",\n\"ryen\",\n\"ryme\",\n\"rynd\",\n\"rynt\",\n\"ryot\",\n\"ryotwar\",\n\"rype\",\n\"rypeck\",\n\"s\",\n\"sa\",\n\"saa\",\n\"sab\",\n\"sabalo\",\n\"sabanut\",\n\"sabbat\",\n\"sabbath\",\n\"sabe\",\n\"sabeca\",\n\"sabella\",\n\"saber\",\n\"sabered\",\n\"sabicu\",\n\"sabina\",\n\"sabine\",\n\"sabino\",\n\"sable\",\n\"sably\",\n\"sabora\",\n\"sabot\",\n\"saboted\",\n\"sabra\",\n\"sabulum\",\n\"saburra\",\n\"sabutan\",\n\"sabzi\",\n\"sac\",\n\"sacaton\",\n\"sacatra\",\n\"saccade\",\n\"saccate\",\n\"saccos\",\n\"saccule\",\n\"saccus\",\n\"sachem\",\n\"sachet\",\n\"sack\",\n\"sackage\",\n\"sackbag\",\n\"sackbut\",\n\"sacked\",\n\"sacken\",\n\"sacker\",\n\"sackful\",\n\"sacking\",\n\"sackman\",\n\"saclike\",\n\"saco\",\n\"sacope\",\n\"sacque\",\n\"sacra\",\n\"sacrad\",\n\"sacral\",\n\"sacred\",\n\"sacring\",\n\"sacrist\",\n\"sacro\",\n\"sacrum\",\n\"sad\",\n\"sadden\",\n\"saddik\",\n\"saddish\",\n\"saddle\",\n\"saddled\",\n\"saddler\",\n\"sade\",\n\"sadh\",\n\"sadhe\",\n\"sadhu\",\n\"sadic\",\n\"sadiron\",\n\"sadism\",\n\"sadist\",\n\"sadly\",\n\"sadness\",\n\"sado\",\n\"sadr\",\n\"saecula\",\n\"saeter\",\n\"saeume\",\n\"safari\",\n\"safe\",\n\"safely\",\n\"safen\",\n\"safener\",\n\"safety\",\n\"saffian\",\n\"safflor\",\n\"safflow\",\n\"saffron\",\n\"safrole\",\n\"saft\",\n\"sag\",\n\"saga\",\n\"sagaie\",\n\"sagaman\",\n\"sagathy\",\n\"sage\",\n\"sagely\",\n\"sagene\",\n\"sagger\",\n\"sagging\",\n\"saggon\",\n\"saggy\",\n\"saging\",\n\"sagitta\",\n\"sagless\",\n\"sago\",\n\"sagoin\",\n\"saguaro\",\n\"sagum\",\n\"saguran\",\n\"sagwire\",\n\"sagy\",\n\"sah\",\n\"sahh\",\n\"sahib\",\n\"sahme\",\n\"sahukar\",\n\"sai\",\n\"saic\",\n\"said\",\n\"saiga\",\n\"sail\",\n\"sailage\",\n\"sailed\",\n\"sailer\",\n\"sailing\",\n\"sailor\",\n\"saily\",\n\"saim\",\n\"saimiri\",\n\"saimy\",\n\"sain\",\n\"saint\",\n\"sainted\",\n\"saintly\",\n\"saip\",\n\"sair\",\n\"sairly\",\n\"sairve\",\n\"sairy\",\n\"saithe\",\n\"saj\",\n\"sajou\",\n\"sake\",\n\"sakeber\",\n\"sakeen\",\n\"saker\",\n\"sakeret\",\n\"saki\",\n\"sakieh\",\n\"sakulya\",\n\"sal\",\n\"salaam\",\n\"salable\",\n\"salably\",\n\"salacot\",\n\"salad\",\n\"salago\",\n\"salal\",\n\"salamo\",\n\"salar\",\n\"salary\",\n\"salat\",\n\"salay\",\n\"sale\",\n\"salele\",\n\"salema\",\n\"salep\",\n\"salfern\",\n\"salic\",\n\"salicin\",\n\"salicyl\",\n\"salient\",\n\"salify\",\n\"saligot\",\n\"salina\",\n\"saline\",\n\"salite\",\n\"salited\",\n\"saliva\",\n\"salival\",\n\"salix\",\n\"salle\",\n\"sallee\",\n\"sallet\",\n\"sallier\",\n\"salloo\",\n\"sallow\",\n\"sallowy\",\n\"sally\",\n\"salma\",\n\"salmiac\",\n\"salmine\",\n\"salmis\",\n\"salmon\",\n\"salol\",\n\"salomon\",\n\"salon\",\n\"saloon\",\n\"saloop\",\n\"salp\",\n\"salpa\",\n\"salpian\",\n\"salpinx\",\n\"salpoid\",\n\"salse\",\n\"salsify\",\n\"salt\",\n\"salta\",\n\"saltant\",\n\"saltary\",\n\"saltate\",\n\"saltcat\",\n\"salted\",\n\"saltee\",\n\"salten\",\n\"salter\",\n\"saltern\",\n\"saltery\",\n\"saltfat\",\n\"saltier\",\n\"saltine\",\n\"salting\",\n\"saltish\",\n\"saltly\",\n\"saltman\",\n\"saltpan\",\n\"saltus\",\n\"salty\",\n\"saluki\",\n\"salung\",\n\"salute\",\n\"saluter\",\n\"salvage\",\n\"salve\",\n\"salver\",\n\"salviol\",\n\"salvo\",\n\"salvor\",\n\"salvy\",\n\"sam\",\n\"samadh\",\n\"samadhi\",\n\"samaj\",\n\"saman\",\n\"samara\",\n\"samaria\",\n\"samarra\",\n\"samba\",\n\"sambal\",\n\"sambar\",\n\"sambo\",\n\"sambuk\",\n\"sambuke\",\n\"same\",\n\"samekh\",\n\"samel\",\n\"samely\",\n\"samen\",\n\"samh\",\n\"samhita\",\n\"samiel\",\n\"samiri\",\n\"samisen\",\n\"samite\",\n\"samkara\",\n\"samlet\",\n\"sammel\",\n\"sammer\",\n\"sammier\",\n\"sammy\",\n\"samovar\",\n\"samp\",\n\"sampan\",\n\"sampi\",\n\"sample\",\n\"sampler\",\n\"samsara\",\n\"samshu\",\n\"samson\",\n\"samurai\",\n\"san\",\n\"sanable\",\n\"sanai\",\n\"sancho\",\n\"sanct\",\n\"sancta\",\n\"sanctum\",\n\"sand\",\n\"sandak\",\n\"sandal\",\n\"sandan\",\n\"sandbag\",\n\"sandbin\",\n\"sandbox\",\n\"sandboy\",\n\"sandbur\",\n\"sanded\",\n\"sander\",\n\"sanders\",\n\"sandhi\",\n\"sanding\",\n\"sandix\",\n\"sandman\",\n\"sandust\",\n\"sandy\",\n\"sane\",\n\"sanely\",\n\"sang\",\n\"sanga\",\n\"sangar\",\n\"sangei\",\n\"sanger\",\n\"sangha\",\n\"sangley\",\n\"sangrel\",\n\"sangsue\",\n\"sanicle\",\n\"sanies\",\n\"sanify\",\n\"sanious\",\n\"sanity\",\n\"sanjak\",\n\"sank\",\n\"sankha\",\n\"sannup\",\n\"sans\",\n\"sansei\",\n\"sansi\",\n\"sant\",\n\"santal\",\n\"santene\",\n\"santimi\",\n\"santims\",\n\"santir\",\n\"santon\",\n\"sao\",\n\"sap\",\n\"sapa\",\n\"sapajou\",\n\"sapan\",\n\"sapbush\",\n\"sapek\",\n\"sapful\",\n\"saphead\",\n\"saphena\",\n\"saphie\",\n\"sapid\",\n\"sapient\",\n\"sapin\",\n\"sapinda\",\n\"saple\",\n\"sapless\",\n\"sapling\",\n\"sapo\",\n\"saponin\",\n\"sapor\",\n\"sapota\",\n\"sapote\",\n\"sappare\",\n\"sapper\",\n\"sapphic\",\n\"sapping\",\n\"sapples\",\n\"sappy\",\n\"saprine\",\n\"sapsago\",\n\"sapsuck\",\n\"sapwood\",\n\"sapwort\",\n\"sar\",\n\"saraad\",\n\"saraf\",\n\"sarangi\",\n\"sarcasm\",\n\"sarcast\",\n\"sarcine\",\n\"sarcle\",\n\"sarcler\",\n\"sarcode\",\n\"sarcoid\",\n\"sarcoma\",\n\"sarcous\",\n\"sard\",\n\"sardel\",\n\"sardine\",\n\"sardius\",\n\"sare\",\n\"sargo\",\n\"sargus\",\n\"sari\",\n\"sarif\",\n\"sarigue\",\n\"sarinda\",\n\"sarip\",\n\"sark\",\n\"sarkar\",\n\"sarkful\",\n\"sarkine\",\n\"sarking\",\n\"sarkit\",\n\"sarlak\",\n\"sarlyk\",\n\"sarment\",\n\"sarna\",\n\"sarod\",\n\"saron\",\n\"sarong\",\n\"saronic\",\n\"saros\",\n\"sarpler\",\n\"sarpo\",\n\"sarra\",\n\"sarraf\",\n\"sarsa\",\n\"sarsen\",\n\"sart\",\n\"sartage\",\n\"sartain\",\n\"sartor\",\n\"sarus\",\n\"sarwan\",\n\"sasa\",\n\"sasan\",\n\"sasani\",\n\"sash\",\n\"sashay\",\n\"sashery\",\n\"sashing\",\n\"sasin\",\n\"sasine\",\n\"sassaby\",\n\"sassy\",\n\"sat\",\n\"satable\",\n\"satan\",\n\"satang\",\n\"satanic\",\n\"satara\",\n\"satchel\",\n\"sate\",\n\"sateen\",\n\"satiate\",\n\"satient\",\n\"satiety\",\n\"satin\",\n\"satine\",\n\"satined\",\n\"satiny\",\n\"satire\",\n\"satiric\",\n\"satisfy\",\n\"satlijk\",\n\"satrap\",\n\"satrapy\",\n\"satron\",\n\"sattle\",\n\"sattva\",\n\"satura\",\n\"satyr\",\n\"satyric\",\n\"sauce\",\n\"saucer\",\n\"saucily\",\n\"saucy\",\n\"sauf\",\n\"sauger\",\n\"saugh\",\n\"saughen\",\n\"sauld\",\n\"saulie\",\n\"sault\",\n\"saulter\",\n\"saum\",\n\"saumon\",\n\"saumont\",\n\"sauna\",\n\"saunter\",\n\"sauqui\",\n\"saur\",\n\"saurel\",\n\"saurian\",\n\"saury\",\n\"sausage\",\n\"saut\",\n\"saute\",\n\"sauteur\",\n\"sauty\",\n\"sauve\",\n\"savable\",\n\"savacu\",\n\"savage\",\n\"savanna\",\n\"savant\",\n\"savarin\",\n\"save\",\n\"saved\",\n\"saveloy\",\n\"saver\",\n\"savin\",\n\"saving\",\n\"savior\",\n\"savola\",\n\"savor\",\n\"savored\",\n\"savorer\",\n\"savory\",\n\"savour\",\n\"savoy\",\n\"savoyed\",\n\"savssat\",\n\"savvy\",\n\"saw\",\n\"sawah\",\n\"sawali\",\n\"sawarra\",\n\"sawback\",\n\"sawbill\",\n\"sawbuck\",\n\"sawbwa\",\n\"sawder\",\n\"sawdust\",\n\"sawed\",\n\"sawer\",\n\"sawfish\",\n\"sawfly\",\n\"sawing\",\n\"sawish\",\n\"sawlike\",\n\"sawman\",\n\"sawmill\",\n\"sawmon\",\n\"sawmont\",\n\"sawn\",\n\"sawney\",\n\"sawt\",\n\"sawway\",\n\"sawwort\",\n\"sawyer\",\n\"sax\",\n\"saxhorn\",\n\"saxten\",\n\"saxtie\",\n\"saxtuba\",\n\"say\",\n\"saya\",\n\"sayable\",\n\"sayer\",\n\"sayette\",\n\"sayid\",\n\"saying\",\n\"sazen\",\n\"sblood\",\n\"scab\",\n\"scabbed\",\n\"scabble\",\n\"scabby\",\n\"scabid\",\n\"scabies\",\n\"scabish\",\n\"scabrid\",\n\"scad\",\n\"scaddle\",\n\"scads\",\n\"scaff\",\n\"scaffer\",\n\"scaffie\",\n\"scaffle\",\n\"scaglia\",\n\"scala\",\n\"scalage\",\n\"scalar\",\n\"scalare\",\n\"scald\",\n\"scalded\",\n\"scalder\",\n\"scaldic\",\n\"scaldy\",\n\"scale\",\n\"scaled\",\n\"scalena\",\n\"scalene\",\n\"scaler\",\n\"scales\",\n\"scaling\",\n\"scall\",\n\"scalled\",\n\"scallom\",\n\"scallop\",\n\"scalma\",\n\"scaloni\",\n\"scalp\",\n\"scalpel\",\n\"scalper\",\n\"scalt\",\n\"scaly\",\n\"scam\",\n\"scamble\",\n\"scamell\",\n\"scamler\",\n\"scamles\",\n\"scamp\",\n\"scamper\",\n\"scan\",\n\"scandal\",\n\"scandia\",\n\"scandic\",\n\"scanmag\",\n\"scanner\",\n\"scant\",\n\"scantle\",\n\"scantly\",\n\"scanty\",\n\"scap\",\n\"scape\",\n\"scapel\",\n\"scapha\",\n\"scapoid\",\n\"scapose\",\n\"scapple\",\n\"scapula\",\n\"scapus\",\n\"scar\",\n\"scarab\",\n\"scarce\",\n\"scarcen\",\n\"scare\",\n\"scarer\",\n\"scarf\",\n\"scarfed\",\n\"scarfer\",\n\"scarfy\",\n\"scarid\",\n\"scarify\",\n\"scarily\",\n\"scarlet\",\n\"scarman\",\n\"scarn\",\n\"scaroid\",\n\"scarp\",\n\"scarred\",\n\"scarrer\",\n\"scarry\",\n\"scart\",\n\"scarth\",\n\"scarus\",\n\"scarved\",\n\"scary\",\n\"scase\",\n\"scasely\",\n\"scat\",\n\"scatch\",\n\"scathe\",\n\"scatter\",\n\"scatty\",\n\"scatula\",\n\"scaul\",\n\"scaum\",\n\"scaup\",\n\"scauper\",\n\"scaur\",\n\"scaurie\",\n\"scaut\",\n\"scavage\",\n\"scavel\",\n\"scaw\",\n\"scawd\",\n\"scawl\",\n\"scazon\",\n\"sceat\",\n\"scena\",\n\"scenary\",\n\"scend\",\n\"scene\",\n\"scenery\",\n\"scenic\",\n\"scenist\",\n\"scenite\",\n\"scent\",\n\"scented\",\n\"scenter\",\n\"scepsis\",\n\"scepter\",\n\"sceptic\",\n\"sceptry\",\n\"scerne\",\n\"schanz\",\n\"schappe\",\n\"scharf\",\n\"schelly\",\n\"schema\",\n\"scheme\",\n\"schemer\",\n\"schemy\",\n\"schene\",\n\"schepel\",\n\"schepen\",\n\"scherm\",\n\"scherzi\",\n\"scherzo\",\n\"schesis\",\n\"schism\",\n\"schisma\",\n\"schist\",\n\"schloop\",\n\"schmelz\",\n\"scho\",\n\"schola\",\n\"scholae\",\n\"scholar\",\n\"scholia\",\n\"schone\",\n\"school\",\n\"schoon\",\n\"schorl\",\n\"schorly\",\n\"schout\",\n\"schtoff\",\n\"schuh\",\n\"schuhe\",\n\"schuit\",\n\"schule\",\n\"schuss\",\n\"schute\",\n\"schwa\",\n\"schwarz\",\n\"sciapod\",\n\"sciarid\",\n\"sciatic\",\n\"scibile\",\n\"science\",\n\"scient\",\n\"scincid\",\n\"scind\",\n\"sciniph\",\n\"scintle\",\n\"scion\",\n\"scious\",\n\"scirrhi\",\n\"scissel\",\n\"scissor\",\n\"sciurid\",\n\"sclaff\",\n\"sclate\",\n\"sclater\",\n\"sclaw\",\n\"scler\",\n\"sclera\",\n\"scleral\",\n\"sclere\",\n\"scliff\",\n\"sclim\",\n\"sclimb\",\n\"scoad\",\n\"scob\",\n\"scobby\",\n\"scobs\",\n\"scoff\",\n\"scoffer\",\n\"scog\",\n\"scoggan\",\n\"scogger\",\n\"scoggin\",\n\"scoke\",\n\"scolb\",\n\"scold\",\n\"scolder\",\n\"scolex\",\n\"scolia\",\n\"scoliid\",\n\"scolion\",\n\"scolite\",\n\"scollop\",\n\"scolog\",\n\"sconce\",\n\"sconcer\",\n\"scone\",\n\"scoon\",\n\"scoop\",\n\"scooped\",\n\"scooper\",\n\"scoot\",\n\"scooter\",\n\"scopa\",\n\"scopate\",\n\"scope\",\n\"scopet\",\n\"scopic\",\n\"scopine\",\n\"scopola\",\n\"scops\",\n\"scopula\",\n\"scorch\",\n\"score\",\n\"scored\",\n\"scorer\",\n\"scoria\",\n\"scoriac\",\n\"scoriae\",\n\"scorify\",\n\"scoring\",\n\"scorn\",\n\"scorned\",\n\"scorner\",\n\"scorny\",\n\"scorper\",\n\"scorse\",\n\"scot\",\n\"scotale\",\n\"scotch\",\n\"scote\",\n\"scoter\",\n\"scotia\",\n\"scotino\",\n\"scotoma\",\n\"scotomy\",\n\"scouch\",\n\"scouk\",\n\"scoup\",\n\"scour\",\n\"scoured\",\n\"scourer\",\n\"scourge\",\n\"scoury\",\n\"scouse\",\n\"scout\",\n\"scouter\",\n\"scouth\",\n\"scove\",\n\"scovel\",\n\"scovy\",\n\"scow\",\n\"scowder\",\n\"scowl\",\n\"scowler\",\n\"scowman\",\n\"scrab\",\n\"scrabe\",\n\"scrae\",\n\"scrag\",\n\"scraggy\",\n\"scraily\",\n\"scram\",\n\"scran\",\n\"scranch\",\n\"scrank\",\n\"scranky\",\n\"scranny\",\n\"scrap\",\n\"scrape\",\n\"scraped\",\n\"scraper\",\n\"scrapie\",\n\"scrappy\",\n\"scrapy\",\n\"scrat\",\n\"scratch\",\n\"scrath\",\n\"scrauch\",\n\"scraw\",\n\"scrawk\",\n\"scrawl\",\n\"scrawly\",\n\"scrawm\",\n\"scrawny\",\n\"scray\",\n\"scraze\",\n\"screak\",\n\"screaky\",\n\"scream\",\n\"screamy\",\n\"scree\",\n\"screech\",\n\"screed\",\n\"screek\",\n\"screel\",\n\"screen\",\n\"screeny\",\n\"screet\",\n\"screeve\",\n\"screich\",\n\"screigh\",\n\"screve\",\n\"screver\",\n\"screw\",\n\"screwed\",\n\"screwer\",\n\"screwy\",\n\"scribal\",\n\"scribe\",\n\"scriber\",\n\"scride\",\n\"scrieve\",\n\"scrike\",\n\"scrim\",\n\"scrime\",\n\"scrimer\",\n\"scrimp\",\n\"scrimpy\",\n\"scrin\",\n\"scrinch\",\n\"scrine\",\n\"scringe\",\n\"scrip\",\n\"scripee\",\n\"script\",\n\"scritch\",\n\"scrive\",\n\"scriven\",\n\"scriver\",\n\"scrob\",\n\"scrobe\",\n\"scrobis\",\n\"scrod\",\n\"scroff\",\n\"scrog\",\n\"scroggy\",\n\"scrolar\",\n\"scroll\",\n\"scrolly\",\n\"scroo\",\n\"scrooch\",\n\"scrooge\",\n\"scroop\",\n\"scrota\",\n\"scrotal\",\n\"scrotum\",\n\"scrouge\",\n\"scrout\",\n\"scrow\",\n\"scroyle\",\n\"scrub\",\n\"scrubby\",\n\"scruf\",\n\"scruff\",\n\"scruffy\",\n\"scruft\",\n\"scrum\",\n\"scrump\",\n\"scrunch\",\n\"scrunge\",\n\"scrunt\",\n\"scruple\",\n\"scrush\",\n\"scruto\",\n\"scruze\",\n\"scry\",\n\"scryer\",\n\"scud\",\n\"scudder\",\n\"scuddle\",\n\"scuddy\",\n\"scudi\",\n\"scudler\",\n\"scudo\",\n\"scuff\",\n\"scuffed\",\n\"scuffer\",\n\"scuffle\",\n\"scuffly\",\n\"scuffy\",\n\"scuft\",\n\"scufter\",\n\"scug\",\n\"sculch\",\n\"scull\",\n\"sculler\",\n\"scullog\",\n\"sculp\",\n\"sculper\",\n\"sculpin\",\n\"sculpt\",\n\"sculsh\",\n\"scum\",\n\"scumber\",\n\"scumble\",\n\"scummed\",\n\"scummer\",\n\"scummy\",\n\"scun\",\n\"scunder\",\n\"scunner\",\n\"scup\",\n\"scupful\",\n\"scupper\",\n\"scuppet\",\n\"scur\",\n\"scurdy\",\n\"scurf\",\n\"scurfer\",\n\"scurfy\",\n\"scurry\",\n\"scurvy\",\n\"scuse\",\n\"scut\",\n\"scuta\",\n\"scutage\",\n\"scutal\",\n\"scutate\",\n\"scutch\",\n\"scute\",\n\"scutel\",\n\"scutter\",\n\"scuttle\",\n\"scutty\",\n\"scutula\",\n\"scutum\",\n\"scybala\",\n\"scye\",\n\"scypha\",\n\"scyphae\",\n\"scyphi\",\n\"scyphoi\",\n\"scyphus\",\n\"scyt\",\n\"scytale\",\n\"scythe\",\n\"sdeath\",\n\"se\",\n\"sea\",\n\"seadog\",\n\"seafare\",\n\"seafolk\",\n\"seafowl\",\n\"seagirt\",\n\"seagoer\",\n\"seah\",\n\"seak\",\n\"seal\",\n\"sealant\",\n\"sealch\",\n\"sealed\",\n\"sealer\",\n\"sealery\",\n\"sealess\",\n\"sealet\",\n\"sealike\",\n\"sealine\",\n\"sealing\",\n\"seam\",\n\"seaman\",\n\"seamark\",\n\"seamed\",\n\"seamer\",\n\"seaming\",\n\"seamlet\",\n\"seamost\",\n\"seamrog\",\n\"seamy\",\n\"seance\",\n\"seaport\",\n\"sear\",\n\"searce\",\n\"searcer\",\n\"search\",\n\"seared\",\n\"searer\",\n\"searing\",\n\"seary\",\n\"seasick\",\n\"seaside\",\n\"season\",\n\"seat\",\n\"seatang\",\n\"seated\",\n\"seater\",\n\"seathe\",\n\"seating\",\n\"seatron\",\n\"seave\",\n\"seavy\",\n\"seawant\",\n\"seaward\",\n\"seaware\",\n\"seaway\",\n\"seaweed\",\n\"seawife\",\n\"seaworn\",\n\"seax\",\n\"sebacic\",\n\"sebait\",\n\"sebate\",\n\"sebific\",\n\"sebilla\",\n\"sebkha\",\n\"sebum\",\n\"sebundy\",\n\"sec\",\n\"secable\",\n\"secalin\",\n\"secancy\",\n\"secant\",\n\"secede\",\n\"seceder\",\n\"secern\",\n\"secesh\",\n\"sech\",\n\"seck\",\n\"seclude\",\n\"secluse\",\n\"secohm\",\n\"second\",\n\"seconde\",\n\"secos\",\n\"secpar\",\n\"secque\",\n\"secre\",\n\"secrecy\",\n\"secret\",\n\"secreta\",\n\"secrete\",\n\"secreto\",\n\"sect\",\n\"sectary\",\n\"sectile\",\n\"section\",\n\"sectism\",\n\"sectist\",\n\"sective\",\n\"sector\",\n\"secular\",\n\"secund\",\n\"secure\",\n\"securer\",\n\"sedan\",\n\"sedate\",\n\"sedent\",\n\"sedge\",\n\"sedged\",\n\"sedging\",\n\"sedgy\",\n\"sedile\",\n\"sedilia\",\n\"seduce\",\n\"seducee\",\n\"seducer\",\n\"seduct\",\n\"sedum\",\n\"see\",\n\"seeable\",\n\"seech\",\n\"seed\",\n\"seedage\",\n\"seedbed\",\n\"seedbox\",\n\"seeded\",\n\"seeder\",\n\"seedful\",\n\"seedily\",\n\"seedkin\",\n\"seedlet\",\n\"seedlip\",\n\"seedman\",\n\"seedy\",\n\"seege\",\n\"seeing\",\n\"seek\",\n\"seeker\",\n\"seeking\",\n\"seel\",\n\"seelful\",\n\"seely\",\n\"seem\",\n\"seemer\",\n\"seeming\",\n\"seemly\",\n\"seen\",\n\"seenie\",\n\"seep\",\n\"seepage\",\n\"seeped\",\n\"seepy\",\n\"seer\",\n\"seeress\",\n\"seerpaw\",\n\"seesaw\",\n\"seesee\",\n\"seethe\",\n\"seg\",\n\"seggar\",\n\"seggard\",\n\"segged\",\n\"seggrom\",\n\"segment\",\n\"sego\",\n\"segol\",\n\"seiche\",\n\"seidel\",\n\"seine\",\n\"seiner\",\n\"seise\",\n\"seism\",\n\"seismal\",\n\"seismic\",\n\"seit\",\n\"seity\",\n\"seize\",\n\"seizer\",\n\"seizin\",\n\"seizing\",\n\"seizor\",\n\"seizure\",\n\"sejant\",\n\"sejoin\",\n\"sejunct\",\n\"sekos\",\n\"selah\",\n\"selamin\",\n\"seldom\",\n\"seldor\",\n\"sele\",\n\"select\",\n\"selenic\",\n\"self\",\n\"selfdom\",\n\"selfful\",\n\"selfish\",\n\"selfism\",\n\"selfist\",\n\"selfly\",\n\"selion\",\n\"sell\",\n\"sella\",\n\"sellar\",\n\"sellate\",\n\"seller\",\n\"sellie\",\n\"selling\",\n\"sellout\",\n\"selly\",\n\"selsyn\",\n\"selt\",\n\"selva\",\n\"selvage\",\n\"semarum\",\n\"sematic\",\n\"semball\",\n\"semble\",\n\"seme\",\n\"semeed\",\n\"semeia\",\n\"semeion\",\n\"semen\",\n\"semence\",\n\"semese\",\n\"semi\",\n\"semiape\",\n\"semiarc\",\n\"semibay\",\n\"semic\",\n\"semicup\",\n\"semidry\",\n\"semiegg\",\n\"semifib\",\n\"semifit\",\n\"semify\",\n\"semigod\",\n\"semihot\",\n\"seminal\",\n\"seminar\",\n\"semiorb\",\n\"semiped\",\n\"semipro\",\n\"semiraw\",\n\"semis\",\n\"semita\",\n\"semitae\",\n\"semital\",\n\"semiurn\",\n\"semmet\",\n\"semmit\",\n\"semola\",\n\"semsem\",\n\"sen\",\n\"senaite\",\n\"senam\",\n\"senary\",\n\"senate\",\n\"senator\",\n\"sence\",\n\"sencion\",\n\"send\",\n\"sendal\",\n\"sendee\",\n\"sender\",\n\"sending\",\n\"senega\",\n\"senegin\",\n\"senesce\",\n\"senile\",\n\"senior\",\n\"senna\",\n\"sennet\",\n\"sennit\",\n\"sennite\",\n\"sensa\",\n\"sensal\",\n\"sensate\",\n\"sense\",\n\"sensed\",\n\"sensify\",\n\"sensile\",\n\"sension\",\n\"sensism\",\n\"sensist\",\n\"sensive\",\n\"sensize\",\n\"senso\",\n\"sensor\",\n\"sensory\",\n\"sensual\",\n\"sensum\",\n\"sensyne\",\n\"sent\",\n\"sentry\",\n\"sepad\",\n\"sepal\",\n\"sepaled\",\n\"sephen\",\n\"sepia\",\n\"sepian\",\n\"sepiary\",\n\"sepic\",\n\"sepioid\",\n\"sepion\",\n\"sepiost\",\n\"sepium\",\n\"sepone\",\n\"sepoy\",\n\"seppuku\",\n\"seps\",\n\"sepsine\",\n\"sepsis\",\n\"sept\",\n\"septa\",\n\"septal\",\n\"septan\",\n\"septane\",\n\"septate\",\n\"septave\",\n\"septet\",\n\"septic\",\n\"septier\",\n\"septile\",\n\"septime\",\n\"septoic\",\n\"septole\",\n\"septum\",\n\"septuor\",\n\"sequa\",\n\"sequel\",\n\"sequela\",\n\"sequent\",\n\"sequest\",\n\"sequin\",\n\"ser\",\n\"sera\",\n\"serab\",\n\"seragli\",\n\"serai\",\n\"serail\",\n\"seral\",\n\"serang\",\n\"serape\",\n\"seraph\",\n\"serau\",\n\"seraw\",\n\"sercial\",\n\"serdab\",\n\"sere\",\n\"sereh\",\n\"serene\",\n\"serf\",\n\"serfage\",\n\"serfdom\",\n\"serfish\",\n\"serfism\",\n\"serge\",\n\"serger\",\n\"serging\",\n\"serial\",\n\"seriary\",\n\"seriate\",\n\"sericea\",\n\"sericin\",\n\"seriema\",\n\"series\",\n\"serif\",\n\"serific\",\n\"serin\",\n\"serine\",\n\"seringa\",\n\"serio\",\n\"serious\",\n\"serment\",\n\"sermo\",\n\"sermon\",\n\"sero\",\n\"serolin\",\n\"seron\",\n\"seroon\",\n\"seroot\",\n\"seropus\",\n\"serosa\",\n\"serous\",\n\"serow\",\n\"serpent\",\n\"serphid\",\n\"serpigo\",\n\"serpula\",\n\"serra\",\n\"serrage\",\n\"serran\",\n\"serrana\",\n\"serrano\",\n\"serrate\",\n\"serried\",\n\"serry\",\n\"sert\",\n\"serta\",\n\"sertule\",\n\"sertum\",\n\"serum\",\n\"serumal\",\n\"serut\",\n\"servage\",\n\"serval\",\n\"servant\",\n\"serve\",\n\"server\",\n\"servery\",\n\"servet\",\n\"service\",\n\"servile\",\n\"serving\",\n\"servist\",\n\"servo\",\n\"sesame\",\n\"sesma\",\n\"sesqui\",\n\"sess\",\n\"sessile\",\n\"session\",\n\"sestet\",\n\"sesti\",\n\"sestiad\",\n\"sestina\",\n\"sestine\",\n\"sestole\",\n\"sestuor\",\n\"set\",\n\"seta\",\n\"setae\",\n\"setal\",\n\"setback\",\n\"setbolt\",\n\"setdown\",\n\"setfast\",\n\"seth\",\n\"sethead\",\n\"setier\",\n\"setline\",\n\"setness\",\n\"setoff\",\n\"seton\",\n\"setose\",\n\"setous\",\n\"setout\",\n\"setover\",\n\"setsman\",\n\"sett\",\n\"settee\",\n\"setter\",\n\"setting\",\n\"settle\",\n\"settled\",\n\"settler\",\n\"settlor\",\n\"setula\",\n\"setule\",\n\"setup\",\n\"setwall\",\n\"setwise\",\n\"setwork\",\n\"seugh\",\n\"seven\",\n\"sevener\",\n\"seventh\",\n\"seventy\",\n\"sever\",\n\"several\",\n\"severe\",\n\"severer\",\n\"severy\",\n\"sew\",\n\"sewable\",\n\"sewage\",\n\"sewan\",\n\"sewed\",\n\"sewen\",\n\"sewer\",\n\"sewered\",\n\"sewery\",\n\"sewing\",\n\"sewless\",\n\"sewn\",\n\"sex\",\n\"sexed\",\n\"sexern\",\n\"sexfid\",\n\"sexfoil\",\n\"sexhood\",\n\"sexifid\",\n\"sexiped\",\n\"sexless\",\n\"sexlike\",\n\"sexly\",\n\"sext\",\n\"sextain\",\n\"sextan\",\n\"sextans\",\n\"sextant\",\n\"sextar\",\n\"sextary\",\n\"sextern\",\n\"sextet\",\n\"sextic\",\n\"sextile\",\n\"sexto\",\n\"sextole\",\n\"sexton\",\n\"sextry\",\n\"sextula\",\n\"sexual\",\n\"sexuale\",\n\"sexuous\",\n\"sexy\",\n\"sey\",\n\"sfoot\",\n\"sh\",\n\"sha\",\n\"shab\",\n\"shabash\",\n\"shabbed\",\n\"shabble\",\n\"shabby\",\n\"shachle\",\n\"shachly\",\n\"shack\",\n\"shackle\",\n\"shackly\",\n\"shacky\",\n\"shad\",\n\"shade\",\n\"shaded\",\n\"shader\",\n\"shadily\",\n\"shadine\",\n\"shading\",\n\"shadkan\",\n\"shadoof\",\n\"shadow\",\n\"shadowy\",\n\"shady\",\n\"shaffle\",\n\"shaft\",\n\"shafted\",\n\"shafter\",\n\"shafty\",\n\"shag\",\n\"shagbag\",\n\"shagged\",\n\"shaggy\",\n\"shaglet\",\n\"shagrag\",\n\"shah\",\n\"shahdom\",\n\"shahi\",\n\"shahin\",\n\"shaikh\",\n\"shaitan\",\n\"shake\",\n\"shaken\",\n\"shaker\",\n\"shakers\",\n\"shakha\",\n\"shakily\",\n\"shaking\",\n\"shako\",\n\"shakti\",\n\"shaku\",\n\"shaky\",\n\"shale\",\n\"shall\",\n\"shallal\",\n\"shallon\",\n\"shallop\",\n\"shallot\",\n\"shallow\",\n\"shallu\",\n\"shalom\",\n\"shalt\",\n\"shalwar\",\n\"shaly\",\n\"sham\",\n\"shama\",\n\"shamal\",\n\"shamalo\",\n\"shaman\",\n\"shamba\",\n\"shamble\",\n\"shame\",\n\"shamed\",\n\"shamer\",\n\"shamir\",\n\"shammed\",\n\"shammer\",\n\"shammy\",\n\"shampoo\",\n\"shan\",\n\"shandry\",\n\"shandy\",\n\"shangan\",\n\"shank\",\n\"shanked\",\n\"shanker\",\n\"shanna\",\n\"shanny\",\n\"shansa\",\n\"shant\",\n\"shanty\",\n\"shap\",\n\"shape\",\n\"shaped\",\n\"shapely\",\n\"shapen\",\n\"shaper\",\n\"shaping\",\n\"shaps\",\n\"shapy\",\n\"shard\",\n\"sharded\",\n\"shardy\",\n\"share\",\n\"sharer\",\n\"shargar\",\n\"shark\",\n\"sharky\",\n\"sharn\",\n\"sharny\",\n\"sharp\",\n\"sharpen\",\n\"sharper\",\n\"sharpie\",\n\"sharply\",\n\"sharps\",\n\"sharpy\",\n\"sharrag\",\n\"sharry\",\n\"shaster\",\n\"shastra\",\n\"shastri\",\n\"shat\",\n\"shatan\",\n\"shatter\",\n\"shaugh\",\n\"shaul\",\n\"shaup\",\n\"shauri\",\n\"shauwe\",\n\"shave\",\n\"shaved\",\n\"shavee\",\n\"shaven\",\n\"shaver\",\n\"shavery\",\n\"shaving\",\n\"shaw\",\n\"shawl\",\n\"shawled\",\n\"shawm\",\n\"shawny\",\n\"shawy\",\n\"shay\",\n\"she\",\n\"shea\",\n\"sheaf\",\n\"sheafy\",\n\"sheal\",\n\"shear\",\n\"sheard\",\n\"shearer\",\n\"shears\",\n\"sheat\",\n\"sheath\",\n\"sheathe\",\n\"sheathy\",\n\"sheave\",\n\"sheaved\",\n\"shebang\",\n\"shebeen\",\n\"shed\",\n\"shedded\",\n\"shedder\",\n\"sheder\",\n\"shedman\",\n\"shee\",\n\"sheely\",\n\"sheen\",\n\"sheenly\",\n\"sheeny\",\n\"sheep\",\n\"sheepy\",\n\"sheer\",\n\"sheered\",\n\"sheerly\",\n\"sheet\",\n\"sheeted\",\n\"sheeter\",\n\"sheety\",\n\"sheik\",\n\"sheikly\",\n\"shekel\",\n\"shela\",\n\"sheld\",\n\"shelder\",\n\"shelf\",\n\"shelfy\",\n\"shell\",\n\"shellac\",\n\"shelled\",\n\"sheller\",\n\"shellum\",\n\"shelly\",\n\"shelta\",\n\"shelter\",\n\"shelty\",\n\"shelve\",\n\"shelver\",\n\"shelvy\",\n\"shend\",\n\"sheng\",\n\"sheolic\",\n\"sheppey\",\n\"sher\",\n\"sherbet\",\n\"sheriat\",\n\"sherif\",\n\"sherifa\",\n\"sheriff\",\n\"sherifi\",\n\"sherify\",\n\"sherry\",\n\"sheth\",\n\"sheugh\",\n\"sheva\",\n\"shevel\",\n\"shevri\",\n\"shewa\",\n\"shewel\",\n\"sheyle\",\n\"shi\",\n\"shibah\",\n\"shibar\",\n\"shice\",\n\"shicer\",\n\"shicker\",\n\"shide\",\n\"shied\",\n\"shiel\",\n\"shield\",\n\"shier\",\n\"shies\",\n\"shiest\",\n\"shift\",\n\"shifter\",\n\"shifty\",\n\"shigram\",\n\"shih\",\n\"shikar\",\n\"shikara\",\n\"shikari\",\n\"shikimi\",\n\"shikken\",\n\"shiko\",\n\"shikra\",\n\"shilf\",\n\"shilfa\",\n\"shill\",\n\"shilla\",\n\"shillet\",\n\"shilloo\",\n\"shilpit\",\n\"shim\",\n\"shimal\",\n\"shimmer\",\n\"shimmy\",\n\"shimose\",\n\"shimper\",\n\"shin\",\n\"shindig\",\n\"shindle\",\n\"shindy\",\n\"shine\",\n\"shiner\",\n\"shingle\",\n\"shingly\",\n\"shinily\",\n\"shining\",\n\"shinner\",\n\"shinny\",\n\"shinty\",\n\"shiny\",\n\"shinza\",\n\"ship\",\n\"shipboy\",\n\"shipful\",\n\"shiplap\",\n\"shiplet\",\n\"shipman\",\n\"shipped\",\n\"shipper\",\n\"shippo\",\n\"shippon\",\n\"shippy\",\n\"shipway\",\n\"shire\",\n\"shirk\",\n\"shirker\",\n\"shirky\",\n\"shirl\",\n\"shirpit\",\n\"shirr\",\n\"shirt\",\n\"shirty\",\n\"shish\",\n\"shisham\",\n\"shisn\",\n\"shita\",\n\"shither\",\n\"shittah\",\n\"shittim\",\n\"shiv\",\n\"shive\",\n\"shiver\",\n\"shivery\",\n\"shivey\",\n\"shivoo\",\n\"shivy\",\n\"sho\",\n\"shoad\",\n\"shoader\",\n\"shoal\",\n\"shoaler\",\n\"shoaly\",\n\"shoat\",\n\"shock\",\n\"shocker\",\n\"shod\",\n\"shodden\",\n\"shoddy\",\n\"shode\",\n\"shoder\",\n\"shoe\",\n\"shoeboy\",\n\"shoeing\",\n\"shoeman\",\n\"shoer\",\n\"shoful\",\n\"shog\",\n\"shogaol\",\n\"shoggie\",\n\"shoggle\",\n\"shoggly\",\n\"shogi\",\n\"shogun\",\n\"shohet\",\n\"shoji\",\n\"shola\",\n\"shole\",\n\"shone\",\n\"shoneen\",\n\"shoo\",\n\"shood\",\n\"shoofa\",\n\"shoofly\",\n\"shooi\",\n\"shook\",\n\"shool\",\n\"shooler\",\n\"shoop\",\n\"shoor\",\n\"shoot\",\n\"shootee\",\n\"shooter\",\n\"shop\",\n\"shopboy\",\n\"shopful\",\n\"shophar\",\n\"shoplet\",\n\"shopman\",\n\"shoppe\",\n\"shopper\",\n\"shoppy\",\n\"shoq\",\n\"shor\",\n\"shoran\",\n\"shore\",\n\"shored\",\n\"shorer\",\n\"shoring\",\n\"shorn\",\n\"short\",\n\"shorten\",\n\"shorter\",\n\"shortly\",\n\"shorts\",\n\"shot\",\n\"shote\",\n\"shotgun\",\n\"shotman\",\n\"shott\",\n\"shotted\",\n\"shotten\",\n\"shotter\",\n\"shotty\",\n\"shou\",\n\"should\",\n\"shout\",\n\"shouter\",\n\"shoval\",\n\"shove\",\n\"shovel\",\n\"shover\",\n\"show\",\n\"showdom\",\n\"shower\",\n\"showery\",\n\"showily\",\n\"showing\",\n\"showish\",\n\"showman\",\n\"shown\",\n\"showup\",\n\"showy\",\n\"shoya\",\n\"shrab\",\n\"shradh\",\n\"shraf\",\n\"shrag\",\n\"shram\",\n\"shrank\",\n\"shrap\",\n\"shrave\",\n\"shravey\",\n\"shred\",\n\"shreddy\",\n\"shree\",\n\"shreeve\",\n\"shrend\",\n\"shrew\",\n\"shrewd\",\n\"shrewdy\",\n\"shrewly\",\n\"shriek\",\n\"shrieky\",\n\"shrift\",\n\"shrike\",\n\"shrill\",\n\"shrilly\",\n\"shrimp\",\n\"shrimpi\",\n\"shrimpy\",\n\"shrinal\",\n\"shrine\",\n\"shrink\",\n\"shrinky\",\n\"shrip\",\n\"shrite\",\n\"shrive\",\n\"shrivel\",\n\"shriven\",\n\"shriver\",\n\"shroff\",\n\"shrog\",\n\"shroud\",\n\"shroudy\",\n\"shrove\",\n\"shrover\",\n\"shrub\",\n\"shrubby\",\n\"shruff\",\n\"shrug\",\n\"shrunk\",\n\"shrups\",\n\"shuba\",\n\"shuck\",\n\"shucker\",\n\"shucks\",\n\"shudder\",\n\"shuff\",\n\"shuffle\",\n\"shug\",\n\"shul\",\n\"shuler\",\n\"shumac\",\n\"shun\",\n\"shune\",\n\"shunner\",\n\"shunt\",\n\"shunter\",\n\"shure\",\n\"shurf\",\n\"shush\",\n\"shusher\",\n\"shut\",\n\"shutoff\",\n\"shutout\",\n\"shutten\",\n\"shutter\",\n\"shuttle\",\n\"shy\",\n\"shyer\",\n\"shyish\",\n\"shyly\",\n\"shyness\",\n\"shyster\",\n\"si\",\n\"siak\",\n\"sial\",\n\"sialic\",\n\"sialid\",\n\"sialoid\",\n\"siamang\",\n\"sib\",\n\"sibbed\",\n\"sibbens\",\n\"sibber\",\n\"sibby\",\n\"sibilus\",\n\"sibling\",\n\"sibness\",\n\"sibrede\",\n\"sibship\",\n\"sibyl\",\n\"sibylic\",\n\"sibylla\",\n\"sic\",\n\"sicca\",\n\"siccant\",\n\"siccate\",\n\"siccity\",\n\"sice\",\n\"sick\",\n\"sickbed\",\n\"sicken\",\n\"sicker\",\n\"sickish\",\n\"sickle\",\n\"sickled\",\n\"sickler\",\n\"sickly\",\n\"sicsac\",\n\"sicula\",\n\"sicular\",\n\"sidder\",\n\"siddur\",\n\"side\",\n\"sideage\",\n\"sidearm\",\n\"sidecar\",\n\"sided\",\n\"sider\",\n\"sideral\",\n\"siderin\",\n\"sides\",\n\"sideway\",\n\"sidhe\",\n\"sidi\",\n\"siding\",\n\"sidle\",\n\"sidler\",\n\"sidling\",\n\"sidth\",\n\"sidy\",\n\"sie\",\n\"siege\",\n\"sieger\",\n\"sienna\",\n\"sier\",\n\"siering\",\n\"sierra\",\n\"sierran\",\n\"siesta\",\n\"sieve\",\n\"siever\",\n\"sievy\",\n\"sifac\",\n\"sifaka\",\n\"sife\",\n\"siffle\",\n\"sifflet\",\n\"sifflot\",\n\"sift\",\n\"siftage\",\n\"sifted\",\n\"sifter\",\n\"sifting\",\n\"sig\",\n\"sigger\",\n\"sigh\",\n\"sigher\",\n\"sighful\",\n\"sighing\",\n\"sight\",\n\"sighted\",\n\"sighten\",\n\"sighter\",\n\"sightly\",\n\"sighty\",\n\"sigil\",\n\"sigla\",\n\"siglos\",\n\"sigma\",\n\"sigmate\",\n\"sigmoid\",\n\"sign\",\n\"signal\",\n\"signary\",\n\"signate\",\n\"signee\",\n\"signer\",\n\"signet\",\n\"signify\",\n\"signior\",\n\"signist\",\n\"signman\",\n\"signory\",\n\"signum\",\n\"sika\",\n\"sikar\",\n\"sikatch\",\n\"sike\",\n\"sikerly\",\n\"siket\",\n\"sikhara\",\n\"sikhra\",\n\"sil\",\n\"silage\",\n\"silane\",\n\"sile\",\n\"silen\",\n\"silence\",\n\"silency\",\n\"sileni\",\n\"silenic\",\n\"silent\",\n\"silenus\",\n\"silesia\",\n\"silex\",\n\"silica\",\n\"silicam\",\n\"silicic\",\n\"silicle\",\n\"silico\",\n\"silicon\",\n\"silicyl\",\n\"siliqua\",\n\"silique\",\n\"silk\",\n\"silked\",\n\"silken\",\n\"silker\",\n\"silkie\",\n\"silkily\",\n\"silkman\",\n\"silky\",\n\"sill\",\n\"sillar\",\n\"siller\",\n\"sillily\",\n\"sillock\",\n\"sillon\",\n\"silly\",\n\"silo\",\n\"siloist\",\n\"silphid\",\n\"silt\",\n\"siltage\",\n\"silting\",\n\"silty\",\n\"silurid\",\n\"silva\",\n\"silvan\",\n\"silver\",\n\"silvern\",\n\"silvery\",\n\"silvics\",\n\"silyl\",\n\"sima\",\n\"simal\",\n\"simar\",\n\"simball\",\n\"simbil\",\n\"simblin\",\n\"simblot\",\n\"sime\",\n\"simiad\",\n\"simial\",\n\"simian\",\n\"similar\",\n\"simile\",\n\"similor\",\n\"simioid\",\n\"simious\",\n\"simity\",\n\"simkin\",\n\"simlin\",\n\"simling\",\n\"simmer\",\n\"simmon\",\n\"simnel\",\n\"simony\",\n\"simool\",\n\"simoom\",\n\"simoon\",\n\"simous\",\n\"simp\",\n\"simpai\",\n\"simper\",\n\"simple\",\n\"simpler\",\n\"simplex\",\n\"simply\",\n\"simsim\",\n\"simson\",\n\"simular\",\n\"simuler\",\n\"sin\",\n\"sina\",\n\"sinaite\",\n\"sinal\",\n\"sinamay\",\n\"sinapic\",\n\"sinapis\",\n\"sinawa\",\n\"since\",\n\"sincere\",\n\"sind\",\n\"sinder\",\n\"sindle\",\n\"sindoc\",\n\"sindon\",\n\"sindry\",\n\"sine\",\n\"sinew\",\n\"sinewed\",\n\"sinewy\",\n\"sinful\",\n\"sing\",\n\"singe\",\n\"singed\",\n\"singer\",\n\"singey\",\n\"singh\",\n\"singing\",\n\"single\",\n\"singled\",\n\"singler\",\n\"singles\",\n\"singlet\",\n\"singly\",\n\"singult\",\n\"sinh\",\n\"sink\",\n\"sinkage\",\n\"sinker\",\n\"sinking\",\n\"sinky\",\n\"sinless\",\n\"sinlike\",\n\"sinnen\",\n\"sinner\",\n\"sinnet\",\n\"sinopia\",\n\"sinople\",\n\"sinsion\",\n\"sinsyne\",\n\"sinter\",\n\"sintoc\",\n\"sinuate\",\n\"sinuose\",\n\"sinuous\",\n\"sinus\",\n\"sinusal\",\n\"sinward\",\n\"siol\",\n\"sion\",\n\"sip\",\n\"sipage\",\n\"sipe\",\n\"siper\",\n\"siphoid\",\n\"siphon\",\n\"sipid\",\n\"siping\",\n\"sipling\",\n\"sipper\",\n\"sippet\",\n\"sippio\",\n\"sir\",\n\"sircar\",\n\"sirdar\",\n\"sire\",\n\"siren\",\n\"sirene\",\n\"sirenic\",\n\"sireny\",\n\"siress\",\n\"sirgang\",\n\"sirian\",\n\"siricid\",\n\"sirih\",\n\"siris\",\n\"sirkeer\",\n\"sirki\",\n\"sirky\",\n\"sirloin\",\n\"siroc\",\n\"sirocco\",\n\"sirpea\",\n\"sirple\",\n\"sirpoon\",\n\"sirrah\",\n\"sirree\",\n\"sirship\",\n\"sirup\",\n\"siruped\",\n\"siruper\",\n\"sirupy\",\n\"sis\",\n\"sisal\",\n\"sise\",\n\"sisel\",\n\"sish\",\n\"sisham\",\n\"sisi\",\n\"siskin\",\n\"siss\",\n\"sissify\",\n\"sissoo\",\n\"sissy\",\n\"sist\",\n\"sister\",\n\"sistern\",\n\"sistle\",\n\"sistrum\",\n\"sit\",\n\"sitao\",\n\"sitar\",\n\"sitch\",\n\"site\",\n\"sitfast\",\n\"sith\",\n\"sithe\",\n\"sithens\",\n\"sitient\",\n\"sitio\",\n\"sittee\",\n\"sitten\",\n\"sitter\",\n\"sittine\",\n\"sitting\",\n\"situal\",\n\"situate\",\n\"situla\",\n\"situlae\",\n\"situs\",\n\"siva\",\n\"siver\",\n\"sivvens\",\n\"siwash\",\n\"six\",\n\"sixain\",\n\"sixer\",\n\"sixfoil\",\n\"sixfold\",\n\"sixsome\",\n\"sixte\",\n\"sixteen\",\n\"sixth\",\n\"sixthet\",\n\"sixthly\",\n\"sixty\",\n\"sizable\",\n\"sizably\",\n\"sizal\",\n\"sizar\",\n\"size\",\n\"sized\",\n\"sizeman\",\n\"sizer\",\n\"sizes\",\n\"sizing\",\n\"sizy\",\n\"sizygia\",\n\"sizz\",\n\"sizzard\",\n\"sizzing\",\n\"sizzle\",\n\"sjambok\",\n\"skaddle\",\n\"skaff\",\n\"skaffie\",\n\"skag\",\n\"skair\",\n\"skal\",\n\"skance\",\n\"skart\",\n\"skasely\",\n\"skat\",\n\"skate\",\n\"skater\",\n\"skatiku\",\n\"skating\",\n\"skatist\",\n\"skatole\",\n\"skaw\",\n\"skean\",\n\"skedge\",\n\"skee\",\n\"skeed\",\n\"skeeg\",\n\"skeel\",\n\"skeely\",\n\"skeen\",\n\"skeer\",\n\"skeered\",\n\"skeery\",\n\"skeet\",\n\"skeeter\",\n\"skeezix\",\n\"skeg\",\n\"skegger\",\n\"skeif\",\n\"skeigh\",\n\"skeily\",\n\"skein\",\n\"skeiner\",\n\"skeipp\",\n\"skel\",\n\"skelder\",\n\"skelf\",\n\"skelic\",\n\"skell\",\n\"skellat\",\n\"skeller\",\n\"skellum\",\n\"skelly\",\n\"skelp\",\n\"skelper\",\n\"skelpin\",\n\"skelter\",\n\"skemmel\",\n\"skemp\",\n\"sken\",\n\"skene\",\n\"skeo\",\n\"skeough\",\n\"skep\",\n\"skepful\",\n\"skeptic\",\n\"sker\",\n\"skere\",\n\"skerret\",\n\"skerry\",\n\"sketch\",\n\"sketchy\",\n\"skete\",\n\"skevish\",\n\"skew\",\n\"skewed\",\n\"skewer\",\n\"skewl\",\n\"skewly\",\n\"skewy\",\n\"skey\",\n\"ski\",\n\"skiapod\",\n\"skibby\",\n\"skice\",\n\"skid\",\n\"skidded\",\n\"skidder\",\n\"skiddoo\",\n\"skiddy\",\n\"skidpan\",\n\"skidway\",\n\"skied\",\n\"skieppe\",\n\"skier\",\n\"skies\",\n\"skiff\",\n\"skift\",\n\"skiing\",\n\"skijore\",\n\"skil\",\n\"skilder\",\n\"skill\",\n\"skilled\",\n\"skillet\",\n\"skilly\",\n\"skilpot\",\n\"skilts\",\n\"skim\",\n\"skime\",\n\"skimmed\",\n\"skimmer\",\n\"skimp\",\n\"skimpy\",\n\"skin\",\n\"skinch\",\n\"skinful\",\n\"skink\",\n\"skinker\",\n\"skinkle\",\n\"skinned\",\n\"skinner\",\n\"skinny\",\n\"skip\",\n\"skipman\",\n\"skippel\",\n\"skipper\",\n\"skippet\",\n\"skipple\",\n\"skippy\",\n\"skirl\",\n\"skirp\",\n\"skirr\",\n\"skirreh\",\n\"skirret\",\n\"skirt\",\n\"skirted\",\n\"skirter\",\n\"skirty\",\n\"skit\",\n\"skite\",\n\"skiter\",\n\"skither\",\n\"skitter\",\n\"skittle\",\n\"skitty\",\n\"skiv\",\n\"skive\",\n\"skiver\",\n\"skiving\",\n\"sklate\",\n\"sklater\",\n\"sklent\",\n\"skoal\",\n\"skoo\",\n\"skookum\",\n\"skoptsy\",\n\"skout\",\n\"skraigh\",\n\"skrike\",\n\"skrupul\",\n\"skua\",\n\"skulk\",\n\"skulker\",\n\"skull\",\n\"skulled\",\n\"skully\",\n\"skulp\",\n\"skun\",\n\"skunk\",\n\"skunky\",\n\"skuse\",\n\"sky\",\n\"skybal\",\n\"skyey\",\n\"skyful\",\n\"skyish\",\n\"skylark\",\n\"skyless\",\n\"skylike\",\n\"skylook\",\n\"skyman\",\n\"skyphoi\",\n\"skyphos\",\n\"skyre\",\n\"skysail\",\n\"skyugle\",\n\"skyward\",\n\"skyway\",\n\"sla\",\n\"slab\",\n\"slabbed\",\n\"slabber\",\n\"slabby\",\n\"slabman\",\n\"slack\",\n\"slacked\",\n\"slacken\",\n\"slacker\",\n\"slackly\",\n\"slad\",\n\"sladang\",\n\"slade\",\n\"slae\",\n\"slag\",\n\"slagger\",\n\"slaggy\",\n\"slagman\",\n\"slain\",\n\"slainte\",\n\"slait\",\n\"slake\",\n\"slaker\",\n\"slaking\",\n\"slaky\",\n\"slam\",\n\"slamp\",\n\"slander\",\n\"slane\",\n\"slang\",\n\"slangy\",\n\"slank\",\n\"slant\",\n\"slantly\",\n\"slap\",\n\"slape\",\n\"slapper\",\n\"slare\",\n\"slart\",\n\"slarth\",\n\"slash\",\n\"slashed\",\n\"slasher\",\n\"slashy\",\n\"slat\",\n\"slatch\",\n\"slate\",\n\"slater\",\n\"slath\",\n\"slather\",\n\"slatify\",\n\"slating\",\n\"slatish\",\n\"slatted\",\n\"slatter\",\n\"slaty\",\n\"slaum\",\n\"slave\",\n\"slaved\",\n\"slaver\",\n\"slavery\",\n\"slavey\",\n\"slaving\",\n\"slavish\",\n\"slaw\",\n\"slay\",\n\"slayer\",\n\"slaying\",\n\"sleathy\",\n\"sleave\",\n\"sleaved\",\n\"sleazy\",\n\"sleck\",\n\"sled\",\n\"sledded\",\n\"sledder\",\n\"sledful\",\n\"sledge\",\n\"sledger\",\n\"slee\",\n\"sleech\",\n\"sleechy\",\n\"sleek\",\n\"sleeken\",\n\"sleeker\",\n\"sleekit\",\n\"sleekly\",\n\"sleeky\",\n\"sleep\",\n\"sleeper\",\n\"sleepry\",\n\"sleepy\",\n\"sleer\",\n\"sleet\",\n\"sleety\",\n\"sleeve\",\n\"sleeved\",\n\"sleever\",\n\"sleigh\",\n\"sleight\",\n\"slender\",\n\"slent\",\n\"slepez\",\n\"slept\",\n\"slete\",\n\"sleuth\",\n\"slew\",\n\"slewed\",\n\"slewer\",\n\"slewing\",\n\"sley\",\n\"sleyer\",\n\"slice\",\n\"sliced\",\n\"slicer\",\n\"slich\",\n\"slicht\",\n\"slicing\",\n\"slick\",\n\"slicken\",\n\"slicker\",\n\"slickly\",\n\"slid\",\n\"slidage\",\n\"slidden\",\n\"slidder\",\n\"slide\",\n\"slided\",\n\"slider\",\n\"sliding\",\n\"slifter\",\n\"slight\",\n\"slighty\",\n\"slim\",\n\"slime\",\n\"slimer\",\n\"slimily\",\n\"slimish\",\n\"slimly\",\n\"slimpsy\",\n\"slimsy\",\n\"slimy\",\n\"sline\",\n\"sling\",\n\"slinge\",\n\"slinger\",\n\"slink\",\n\"slinker\",\n\"slinky\",\n\"slip\",\n\"slipe\",\n\"slipman\",\n\"slipped\",\n\"slipper\",\n\"slippy\",\n\"slipway\",\n\"slirt\",\n\"slish\",\n\"slit\",\n\"slitch\",\n\"slite\",\n\"slither\",\n\"slithy\",\n\"slitted\",\n\"slitter\",\n\"slitty\",\n\"slive\",\n\"sliver\",\n\"slivery\",\n\"sliving\",\n\"sloan\",\n\"slob\",\n\"slobber\",\n\"slobby\",\n\"slock\",\n\"slocken\",\n\"slod\",\n\"slodder\",\n\"slodge\",\n\"slodger\",\n\"sloe\",\n\"slog\",\n\"slogan\",\n\"slogger\",\n\"sloka\",\n\"sloke\",\n\"slon\",\n\"slone\",\n\"slonk\",\n\"sloo\",\n\"sloom\",\n\"sloomy\",\n\"sloop\",\n\"sloosh\",\n\"slop\",\n\"slope\",\n\"sloped\",\n\"slopely\",\n\"sloper\",\n\"sloping\",\n\"slopped\",\n\"sloppy\",\n\"slops\",\n\"slopy\",\n\"slorp\",\n\"slosh\",\n\"slosher\",\n\"sloshy\",\n\"slot\",\n\"slote\",\n\"sloted\",\n\"sloth\",\n\"slotted\",\n\"slotter\",\n\"slouch\",\n\"slouchy\",\n\"slough\",\n\"sloughy\",\n\"slour\",\n\"sloush\",\n\"sloven\",\n\"slow\",\n\"slowish\",\n\"slowly\",\n\"slowrie\",\n\"slows\",\n\"sloyd\",\n\"slub\",\n\"slubber\",\n\"slubby\",\n\"slud\",\n\"sludder\",\n\"sludge\",\n\"sludged\",\n\"sludger\",\n\"sludgy\",\n\"slue\",\n\"sluer\",\n\"slug\",\n\"slugged\",\n\"slugger\",\n\"sluggy\",\n\"sluice\",\n\"sluicer\",\n\"sluicy\",\n\"sluig\",\n\"sluit\",\n\"slum\",\n\"slumber\",\n\"slumdom\",\n\"slumgum\",\n\"slummer\",\n\"slummy\",\n\"slump\",\n\"slumpy\",\n\"slung\",\n\"slunge\",\n\"slunk\",\n\"slunken\",\n\"slur\",\n\"slurbow\",\n\"slurp\",\n\"slurry\",\n\"slush\",\n\"slusher\",\n\"slushy\",\n\"slut\",\n\"slutch\",\n\"slutchy\",\n\"sluther\",\n\"slutter\",\n\"slutty\",\n\"sly\",\n\"slyish\",\n\"slyly\",\n\"slyness\",\n\"slype\",\n\"sma\",\n\"smack\",\n\"smackee\",\n\"smacker\",\n\"smaik\",\n\"small\",\n\"smallen\",\n\"smaller\",\n\"smalls\",\n\"smally\",\n\"smalm\",\n\"smalt\",\n\"smalter\",\n\"smalts\",\n\"smaragd\",\n\"smarm\",\n\"smarmy\",\n\"smart\",\n\"smarten\",\n\"smartly\",\n\"smarty\",\n\"smash\",\n\"smasher\",\n\"smashup\",\n\"smatter\",\n\"smaze\",\n\"smear\",\n\"smeared\",\n\"smearer\",\n\"smeary\",\n\"smectic\",\n\"smectis\",\n\"smeddum\",\n\"smee\",\n\"smeech\",\n\"smeek\",\n\"smeeky\",\n\"smeer\",\n\"smeeth\",\n\"smegma\",\n\"smell\",\n\"smelled\",\n\"smeller\",\n\"smelly\",\n\"smelt\",\n\"smelter\",\n\"smeth\",\n\"smethe\",\n\"smeuse\",\n\"smew\",\n\"smich\",\n\"smicker\",\n\"smicket\",\n\"smiddie\",\n\"smiddum\",\n\"smidge\",\n\"smidgen\",\n\"smilax\",\n\"smile\",\n\"smiler\",\n\"smilet\",\n\"smiling\",\n\"smily\",\n\"smirch\",\n\"smirchy\",\n\"smiris\",\n\"smirk\",\n\"smirker\",\n\"smirkle\",\n\"smirkly\",\n\"smirky\",\n\"smirtle\",\n\"smit\",\n\"smitch\",\n\"smite\",\n\"smiter\",\n\"smith\",\n\"smitham\",\n\"smither\",\n\"smithy\",\n\"smiting\",\n\"smitten\",\n\"smock\",\n\"smocker\",\n\"smog\",\n\"smoke\",\n\"smoked\",\n\"smoker\",\n\"smokery\",\n\"smokily\",\n\"smoking\",\n\"smokish\",\n\"smoky\",\n\"smolder\",\n\"smolt\",\n\"smooch\",\n\"smoochy\",\n\"smoodge\",\n\"smook\",\n\"smoot\",\n\"smooth\",\n\"smopple\",\n\"smore\",\n\"smote\",\n\"smother\",\n\"smotter\",\n\"smouch\",\n\"smous\",\n\"smouse\",\n\"smouser\",\n\"smout\",\n\"smriti\",\n\"smudge\",\n\"smudged\",\n\"smudger\",\n\"smudgy\",\n\"smug\",\n\"smuggle\",\n\"smugism\",\n\"smugly\",\n\"smuisty\",\n\"smur\",\n\"smurr\",\n\"smurry\",\n\"smuse\",\n\"smush\",\n\"smut\",\n\"smutch\",\n\"smutchy\",\n\"smutted\",\n\"smutter\",\n\"smutty\",\n\"smyth\",\n\"smytrie\",\n\"snab\",\n\"snabbie\",\n\"snabble\",\n\"snack\",\n\"snackle\",\n\"snaff\",\n\"snaffle\",\n\"snafu\",\n\"snag\",\n\"snagged\",\n\"snagger\",\n\"snaggy\",\n\"snagrel\",\n\"snail\",\n\"snails\",\n\"snaily\",\n\"snaith\",\n\"snake\",\n\"snaker\",\n\"snakery\",\n\"snakily\",\n\"snaking\",\n\"snakish\",\n\"snaky\",\n\"snap\",\n\"snapbag\",\n\"snape\",\n\"snaper\",\n\"snapped\",\n\"snapper\",\n\"snapps\",\n\"snappy\",\n\"snaps\",\n\"snapy\",\n\"snare\",\n\"snarer\",\n\"snark\",\n\"snarl\",\n\"snarler\",\n\"snarly\",\n\"snary\",\n\"snaste\",\n\"snatch\",\n\"snatchy\",\n\"snath\",\n\"snathe\",\n\"snavel\",\n\"snavvle\",\n\"snaw\",\n\"snead\",\n\"sneak\",\n\"sneaker\",\n\"sneaky\",\n\"sneap\",\n\"sneath\",\n\"sneathe\",\n\"sneb\",\n\"sneck\",\n\"snecker\",\n\"snecket\",\n\"sned\",\n\"snee\",\n\"sneer\",\n\"sneerer\",\n\"sneery\",\n\"sneesh\",\n\"sneest\",\n\"sneesty\",\n\"sneeze\",\n\"sneezer\",\n\"sneezy\",\n\"snell\",\n\"snelly\",\n\"snerp\",\n\"snew\",\n\"snib\",\n\"snibble\",\n\"snibel\",\n\"snicher\",\n\"snick\",\n\"snicker\",\n\"snicket\",\n\"snickey\",\n\"snickle\",\n\"sniddle\",\n\"snide\",\n\"sniff\",\n\"sniffer\",\n\"sniffle\",\n\"sniffly\",\n\"sniffy\",\n\"snift\",\n\"snifter\",\n\"snifty\",\n\"snig\",\n\"snigger\",\n\"sniggle\",\n\"snip\",\n\"snipe\",\n\"sniper\",\n\"sniping\",\n\"snipish\",\n\"snipper\",\n\"snippet\",\n\"snippy\",\n\"snipy\",\n\"snirl\",\n\"snirt\",\n\"snirtle\",\n\"snitch\",\n\"snite\",\n\"snithe\",\n\"snithy\",\n\"snittle\",\n\"snivel\",\n\"snively\",\n\"snivy\",\n\"snob\",\n\"snobber\",\n\"snobby\",\n\"snobdom\",\n\"snocher\",\n\"snock\",\n\"snocker\",\n\"snod\",\n\"snodly\",\n\"snoek\",\n\"snog\",\n\"snoga\",\n\"snoke\",\n\"snood\",\n\"snooded\",\n\"snook\",\n\"snooker\",\n\"snoop\",\n\"snooper\",\n\"snoopy\",\n\"snoose\",\n\"snoot\",\n\"snooty\",\n\"snoove\",\n\"snooze\",\n\"snoozer\",\n\"snoozle\",\n\"snoozy\",\n\"snop\",\n\"snore\",\n\"snorer\",\n\"snoring\",\n\"snork\",\n\"snorkel\",\n\"snorker\",\n\"snort\",\n\"snorter\",\n\"snortle\",\n\"snorty\",\n\"snot\",\n\"snotter\",\n\"snotty\",\n\"snouch\",\n\"snout\",\n\"snouted\",\n\"snouter\",\n\"snouty\",\n\"snow\",\n\"snowcap\",\n\"snowie\",\n\"snowily\",\n\"snowish\",\n\"snowk\",\n\"snowl\",\n\"snowy\",\n\"snozzle\",\n\"snub\",\n\"snubbed\",\n\"snubbee\",\n\"snubber\",\n\"snubby\",\n\"snuck\",\n\"snudge\",\n\"snuff\",\n\"snuffer\",\n\"snuffle\",\n\"snuffly\",\n\"snuffy\",\n\"snug\",\n\"snugger\",\n\"snuggle\",\n\"snugify\",\n\"snugly\",\n\"snum\",\n\"snup\",\n\"snupper\",\n\"snur\",\n\"snurl\",\n\"snurly\",\n\"snurp\",\n\"snurt\",\n\"snuzzle\",\n\"sny\",\n\"snying\",\n\"so\",\n\"soak\",\n\"soakage\",\n\"soaked\",\n\"soaken\",\n\"soaker\",\n\"soaking\",\n\"soakman\",\n\"soaky\",\n\"soally\",\n\"soam\",\n\"soap\",\n\"soapbox\",\n\"soaper\",\n\"soapery\",\n\"soapily\",\n\"soapsud\",\n\"soapy\",\n\"soar\",\n\"soarer\",\n\"soaring\",\n\"soary\",\n\"sob\",\n\"sobber\",\n\"sobbing\",\n\"sobby\",\n\"sobeit\",\n\"sober\",\n\"soberer\",\n\"soberly\",\n\"sobful\",\n\"soboles\",\n\"soc\",\n\"socage\",\n\"socager\",\n\"soccer\",\n\"soce\",\n\"socht\",\n\"social\",\n\"society\",\n\"socii\",\n\"socius\",\n\"sock\",\n\"socker\",\n\"socket\",\n\"sockeye\",\n\"socky\",\n\"socle\",\n\"socman\",\n\"soco\",\n\"sod\",\n\"soda\",\n\"sodaic\",\n\"sodded\",\n\"sodden\",\n\"sodding\",\n\"soddite\",\n\"soddy\",\n\"sodic\",\n\"sodio\",\n\"sodium\",\n\"sodless\",\n\"sodoku\",\n\"sodomic\",\n\"sodomy\",\n\"sodwork\",\n\"sody\",\n\"soe\",\n\"soekoe\",\n\"soever\",\n\"sofa\",\n\"sofane\",\n\"sofar\",\n\"soffit\",\n\"soft\",\n\"softa\",\n\"soften\",\n\"softish\",\n\"softly\",\n\"softner\",\n\"softy\",\n\"sog\",\n\"soger\",\n\"soget\",\n\"soggily\",\n\"sogging\",\n\"soggy\",\n\"soh\",\n\"soho\",\n\"soil\",\n\"soilage\",\n\"soiled\",\n\"soiling\",\n\"soilure\",\n\"soily\",\n\"soiree\",\n\"soja\",\n\"sojourn\",\n\"sok\",\n\"soka\",\n\"soke\",\n\"sokeman\",\n\"soken\",\n\"sol\",\n\"sola\",\n\"solace\",\n\"solacer\",\n\"solan\",\n\"solanal\",\n\"solanum\",\n\"solar\",\n\"solate\",\n\"solatia\",\n\"solay\",\n\"sold\",\n\"soldado\",\n\"soldan\",\n\"solder\",\n\"soldi\",\n\"soldier\",\n\"soldo\",\n\"sole\",\n\"solea\",\n\"soleas\",\n\"soleil\",\n\"solely\",\n\"solemn\",\n\"solen\",\n\"solent\",\n\"soler\",\n\"soles\",\n\"soleus\",\n\"soleyn\",\n\"soli\",\n\"solicit\",\n\"solid\",\n\"solidi\",\n\"solidly\",\n\"solidum\",\n\"solidus\",\n\"solio\",\n\"soliped\",\n\"solist\",\n\"sollar\",\n\"solo\",\n\"solod\",\n\"solodi\",\n\"soloist\",\n\"solon\",\n\"soloth\",\n\"soluble\",\n\"solubly\",\n\"solum\",\n\"solute\",\n\"solvate\",\n\"solve\",\n\"solvend\",\n\"solvent\",\n\"solver\",\n\"soma\",\n\"somal\",\n\"somata\",\n\"somatic\",\n\"somber\",\n\"sombre\",\n\"some\",\n\"someday\",\n\"somehow\",\n\"someone\",\n\"somers\",\n\"someway\",\n\"somewhy\",\n\"somital\",\n\"somite\",\n\"somitic\",\n\"somma\",\n\"somnial\",\n\"somnify\",\n\"somnus\",\n\"sompay\",\n\"sompne\",\n\"sompner\",\n\"son\",\n\"sonable\",\n\"sonance\",\n\"sonancy\",\n\"sonant\",\n\"sonar\",\n\"sonata\",\n\"sond\",\n\"sondeli\",\n\"soneri\",\n\"song\",\n\"songful\",\n\"songish\",\n\"songle\",\n\"songlet\",\n\"songman\",\n\"songy\",\n\"sonhood\",\n\"sonic\",\n\"soniou\",\n\"sonk\",\n\"sonless\",\n\"sonlike\",\n\"sonly\",\n\"sonnet\",\n\"sonny\",\n\"sonoric\",\n\"sons\",\n\"sonship\",\n\"sonsy\",\n\"sontag\",\n\"soodle\",\n\"soodly\",\n\"sook\",\n\"sooky\",\n\"sool\",\n\"sooloos\",\n\"soon\",\n\"sooner\",\n\"soonish\",\n\"soonly\",\n\"soorawn\",\n\"soord\",\n\"soorkee\",\n\"soot\",\n\"sooter\",\n\"sooth\",\n\"soothe\",\n\"soother\",\n\"sootily\",\n\"sooty\",\n\"sop\",\n\"sope\",\n\"soph\",\n\"sophia\",\n\"sophic\",\n\"sophism\",\n\"sophy\",\n\"sopite\",\n\"sopor\",\n\"sopper\",\n\"sopping\",\n\"soppy\",\n\"soprani\",\n\"soprano\",\n\"sora\",\n\"sorage\",\n\"soral\",\n\"sorb\",\n\"sorbate\",\n\"sorbent\",\n\"sorbic\",\n\"sorbile\",\n\"sorbin\",\n\"sorbite\",\n\"sorbose\",\n\"sorbus\",\n\"sorcer\",\n\"sorcery\",\n\"sorchin\",\n\"sorda\",\n\"sordes\",\n\"sordid\",\n\"sordine\",\n\"sordino\",\n\"sordor\",\n\"sore\",\n\"soredia\",\n\"soree\",\n\"sorehon\",\n\"sorely\",\n\"sorema\",\n\"sorgho\",\n\"sorghum\",\n\"sorgo\",\n\"sori\",\n\"soricid\",\n\"sorite\",\n\"sorites\",\n\"sorn\",\n\"sornare\",\n\"sornari\",\n\"sorner\",\n\"sorning\",\n\"soroban\",\n\"sororal\",\n\"sorose\",\n\"sorosis\",\n\"sorra\",\n\"sorrel\",\n\"sorrily\",\n\"sorroa\",\n\"sorrow\",\n\"sorrowy\",\n\"sorry\",\n\"sort\",\n\"sortal\",\n\"sorted\",\n\"sorter\",\n\"sortie\",\n\"sortly\",\n\"sorty\",\n\"sorus\",\n\"sorva\",\n\"sory\",\n\"sosh\",\n\"soshed\",\n\"soso\",\n\"sosoish\",\n\"soss\",\n\"sossle\",\n\"sot\",\n\"sotie\",\n\"sotnia\",\n\"sotnik\",\n\"sotol\",\n\"sots\",\n\"sottage\",\n\"sotted\",\n\"sotter\",\n\"sottish\",\n\"sou\",\n\"souari\",\n\"soubise\",\n\"soucar\",\n\"souchet\",\n\"souchy\",\n\"soud\",\n\"souffle\",\n\"sough\",\n\"sougher\",\n\"sought\",\n\"soul\",\n\"soulack\",\n\"souled\",\n\"soulful\",\n\"soulish\",\n\"souly\",\n\"soum\",\n\"sound\",\n\"sounder\",\n\"soundly\",\n\"soup\",\n\"soupcon\",\n\"souper\",\n\"souple\",\n\"soupy\",\n\"sour\",\n\"source\",\n\"soured\",\n\"souren\",\n\"sourer\",\n\"souring\",\n\"sourish\",\n\"sourly\",\n\"sourock\",\n\"soursop\",\n\"sourtop\",\n\"soury\",\n\"souse\",\n\"souser\",\n\"souslik\",\n\"soutane\",\n\"souter\",\n\"south\",\n\"souther\",\n\"sov\",\n\"soviet\",\n\"sovite\",\n\"sovkhoz\",\n\"sovran\",\n\"sow\",\n\"sowable\",\n\"sowan\",\n\"sowans\",\n\"sowar\",\n\"sowarry\",\n\"sowback\",\n\"sowbane\",\n\"sowel\",\n\"sowens\",\n\"sower\",\n\"sowfoot\",\n\"sowing\",\n\"sowins\",\n\"sowl\",\n\"sowle\",\n\"sowlike\",\n\"sowlth\",\n\"sown\",\n\"sowse\",\n\"sowt\",\n\"sowte\",\n\"soy\",\n\"soya\",\n\"soybean\",\n\"sozin\",\n\"sozolic\",\n\"sozzle\",\n\"sozzly\",\n\"spa\",\n\"space\",\n\"spaced\",\n\"spacer\",\n\"spacing\",\n\"spack\",\n\"spacy\",\n\"spad\",\n\"spade\",\n\"spaded\",\n\"spader\",\n\"spadger\",\n\"spading\",\n\"spadix\",\n\"spadone\",\n\"spae\",\n\"spaedom\",\n\"spaeman\",\n\"spaer\",\n\"spahi\",\n\"spaid\",\n\"spaik\",\n\"spairge\",\n\"spak\",\n\"spald\",\n\"spalder\",\n\"spale\",\n\"spall\",\n\"spaller\",\n\"spalt\",\n\"span\",\n\"spancel\",\n\"spandle\",\n\"spandy\",\n\"spane\",\n\"spanemy\",\n\"spang\",\n\"spangle\",\n\"spangly\",\n\"spaniel\",\n\"spaning\",\n\"spank\",\n\"spanker\",\n\"spanky\",\n\"spann\",\n\"spannel\",\n\"spanner\",\n\"spanule\",\n\"spar\",\n\"sparada\",\n\"sparch\",\n\"spare\",\n\"sparely\",\n\"sparer\",\n\"sparge\",\n\"sparger\",\n\"sparid\",\n\"sparing\",\n\"spark\",\n\"sparked\",\n\"sparker\",\n\"sparkle\",\n\"sparkly\",\n\"sparks\",\n\"sparky\",\n\"sparm\",\n\"sparoid\",\n\"sparred\",\n\"sparrer\",\n\"sparrow\",\n\"sparry\",\n\"sparse\",\n\"spart\",\n\"sparth\",\n\"spartle\",\n\"sparver\",\n\"spary\",\n\"spasm\",\n\"spasmed\",\n\"spasmic\",\n\"spastic\",\n\"spat\",\n\"spate\",\n\"spatha\",\n\"spathal\",\n\"spathe\",\n\"spathed\",\n\"spathic\",\n\"spatial\",\n\"spatted\",\n\"spatter\",\n\"spattle\",\n\"spatula\",\n\"spatule\",\n\"spave\",\n\"spaver\",\n\"spavie\",\n\"spavied\",\n\"spaviet\",\n\"spavin\",\n\"spawn\",\n\"spawner\",\n\"spawny\",\n\"spay\",\n\"spayad\",\n\"spayard\",\n\"spaying\",\n\"speak\",\n\"speaker\",\n\"speal\",\n\"spean\",\n\"spear\",\n\"spearer\",\n\"speary\",\n\"spec\",\n\"spece\",\n\"special\",\n\"specie\",\n\"species\",\n\"specify\",\n\"speck\",\n\"specked\",\n\"speckle\",\n\"speckly\",\n\"specks\",\n\"specky\",\n\"specs\",\n\"specter\",\n\"spectra\",\n\"spectry\",\n\"specula\",\n\"specus\",\n\"sped\",\n\"speech\",\n\"speed\",\n\"speeder\",\n\"speedy\",\n\"speel\",\n\"speen\",\n\"speer\",\n\"speiss\",\n\"spelder\",\n\"spelk\",\n\"spell\",\n\"speller\",\n\"spelt\",\n\"spelter\",\n\"speltz\",\n\"spelunk\",\n\"spence\",\n\"spencer\",\n\"spend\",\n\"spender\",\n\"spense\",\n\"spent\",\n\"speos\",\n\"sperate\",\n\"sperity\",\n\"sperket\",\n\"sperm\",\n\"sperma\",\n\"spermic\",\n\"spermy\",\n\"sperone\",\n\"spet\",\n\"spetch\",\n\"spew\",\n\"spewer\",\n\"spewing\",\n\"spewy\",\n\"spex\",\n\"sphacel\",\n\"sphecid\",\n\"spheges\",\n\"sphegid\",\n\"sphene\",\n\"sphenic\",\n\"spheral\",\n\"sphere\",\n\"spheric\",\n\"sphery\",\n\"sphinx\",\n\"spica\",\n\"spical\",\n\"spicant\",\n\"spicate\",\n\"spice\",\n\"spiced\",\n\"spicer\",\n\"spicery\",\n\"spicily\",\n\"spicing\",\n\"spick\",\n\"spicket\",\n\"spickle\",\n\"spicose\",\n\"spicous\",\n\"spicula\",\n\"spicule\",\n\"spicy\",\n\"spider\",\n\"spidery\",\n\"spidger\",\n\"spied\",\n\"spiegel\",\n\"spiel\",\n\"spieler\",\n\"spier\",\n\"spiff\",\n\"spiffed\",\n\"spiffy\",\n\"spig\",\n\"spignet\",\n\"spigot\",\n\"spike\",\n\"spiked\",\n\"spiker\",\n\"spikily\",\n\"spiking\",\n\"spiky\",\n\"spile\",\n\"spiler\",\n\"spiling\",\n\"spilite\",\n\"spill\",\n\"spiller\",\n\"spillet\",\n\"spilly\",\n\"spiloma\",\n\"spilt\",\n\"spilth\",\n\"spilus\",\n\"spin\",\n\"spina\",\n\"spinach\",\n\"spinae\",\n\"spinage\",\n\"spinal\",\n\"spinate\",\n\"spinder\",\n\"spindle\",\n\"spindly\",\n\"spine\",\n\"spined\",\n\"spinel\",\n\"spinet\",\n\"spingel\",\n\"spink\",\n\"spinner\",\n\"spinney\",\n\"spinoid\",\n\"spinose\",\n\"spinous\",\n\"spinule\",\n\"spiny\",\n\"spionid\",\n\"spiral\",\n\"spirale\",\n\"spiran\",\n\"spirant\",\n\"spirate\",\n\"spire\",\n\"spirea\",\n\"spired\",\n\"spireme\",\n\"spiring\",\n\"spirit\",\n\"spirity\",\n\"spirket\",\n\"spiro\",\n\"spiroid\",\n\"spirous\",\n\"spirt\",\n\"spiry\",\n\"spise\",\n\"spit\",\n\"spital\",\n\"spitbox\",\n\"spite\",\n\"spitful\",\n\"spitish\",\n\"spitted\",\n\"spitten\",\n\"spitter\",\n\"spittle\",\n\"spitz\",\n\"spiv\",\n\"spivery\",\n\"splash\",\n\"splashy\",\n\"splat\",\n\"splatch\",\n\"splay\",\n\"splayed\",\n\"splayer\",\n\"spleen\",\n\"spleeny\",\n\"spleet\",\n\"splenic\",\n\"splet\",\n\"splice\",\n\"splicer\",\n\"spline\",\n\"splint\",\n\"splinty\",\n\"split\",\n\"splodge\",\n\"splodgy\",\n\"splore\",\n\"splosh\",\n\"splotch\",\n\"splunge\",\n\"splurge\",\n\"splurgy\",\n\"splurt\",\n\"spoach\",\n\"spode\",\n\"spodium\",\n\"spoffle\",\n\"spoffy\",\n\"spogel\",\n\"spoil\",\n\"spoiled\",\n\"spoiler\",\n\"spoilt\",\n\"spoke\",\n\"spoken\",\n\"spoky\",\n\"spole\",\n\"spolia\",\n\"spolium\",\n\"spondee\",\n\"spondyl\",\n\"spong\",\n\"sponge\",\n\"sponged\",\n\"sponger\",\n\"spongin\",\n\"spongy\",\n\"sponsal\",\n\"sponson\",\n\"sponsor\",\n\"spoof\",\n\"spoofer\",\n\"spook\",\n\"spooky\",\n\"spool\",\n\"spooler\",\n\"spoom\",\n\"spoon\",\n\"spooner\",\n\"spoony\",\n\"spoor\",\n\"spoorer\",\n\"spoot\",\n\"spor\",\n\"sporal\",\n\"spore\",\n\"spored\",\n\"sporid\",\n\"sporoid\",\n\"sporont\",\n\"sporous\",\n\"sporran\",\n\"sport\",\n\"sporter\",\n\"sportly\",\n\"sports\",\n\"sporty\",\n\"sporule\",\n\"sposh\",\n\"sposhy\",\n\"spot\",\n\"spotted\",\n\"spotter\",\n\"spottle\",\n\"spotty\",\n\"spousal\",\n\"spouse\",\n\"spousy\",\n\"spout\",\n\"spouter\",\n\"spouty\",\n\"sprack\",\n\"sprad\",\n\"sprag\",\n\"spraich\",\n\"sprain\",\n\"spraint\",\n\"sprang\",\n\"sprank\",\n\"sprat\",\n\"spratty\",\n\"sprawl\",\n\"sprawly\",\n\"spray\",\n\"sprayer\",\n\"sprayey\",\n\"spread\",\n\"spready\",\n\"spreath\",\n\"spree\",\n\"spreeuw\",\n\"spreng\",\n\"sprent\",\n\"spret\",\n\"sprew\",\n\"sprewl\",\n\"spried\",\n\"sprier\",\n\"spriest\",\n\"sprig\",\n\"spriggy\",\n\"spring\",\n\"springe\",\n\"springy\",\n\"sprink\",\n\"sprint\",\n\"sprit\",\n\"sprite\",\n\"spritty\",\n\"sproat\",\n\"sprod\",\n\"sprogue\",\n\"sproil\",\n\"sprong\",\n\"sprose\",\n\"sprout\",\n\"sprowsy\",\n\"spruce\",\n\"sprue\",\n\"spruer\",\n\"sprug\",\n\"spruit\",\n\"sprung\",\n\"sprunny\",\n\"sprunt\",\n\"spry\",\n\"spryly\",\n\"spud\",\n\"spudder\",\n\"spuddle\",\n\"spuddy\",\n\"spuffle\",\n\"spug\",\n\"spuke\",\n\"spume\",\n\"spumone\",\n\"spumose\",\n\"spumous\",\n\"spumy\",\n\"spun\",\n\"spung\",\n\"spunk\",\n\"spunkie\",\n\"spunky\",\n\"spunny\",\n\"spur\",\n\"spurge\",\n\"spuriae\",\n\"spurl\",\n\"spurlet\",\n\"spurn\",\n\"spurner\",\n\"spurred\",\n\"spurrer\",\n\"spurry\",\n\"spurt\",\n\"spurter\",\n\"spurtle\",\n\"spurway\",\n\"sput\",\n\"sputa\",\n\"sputter\",\n\"sputum\",\n\"spy\",\n\"spyboat\",\n\"spydom\",\n\"spyer\",\n\"spyhole\",\n\"spyism\",\n\"spyship\",\n\"squab\",\n\"squabby\",\n\"squacco\",\n\"squad\",\n\"squaddy\",\n\"squail\",\n\"squalid\",\n\"squall\",\n\"squally\",\n\"squalm\",\n\"squalor\",\n\"squam\",\n\"squama\",\n\"squamae\",\n\"squame\",\n\"square\",\n\"squared\",\n\"squarer\",\n\"squark\",\n\"squary\",\n\"squash\",\n\"squashy\",\n\"squat\",\n\"squatly\",\n\"squatty\",\n\"squaw\",\n\"squawk\",\n\"squawky\",\n\"squdge\",\n\"squdgy\",\n\"squeak\",\n\"squeaky\",\n\"squeal\",\n\"squeald\",\n\"squeam\",\n\"squeamy\",\n\"squeege\",\n\"squeeze\",\n\"squeezy\",\n\"squelch\",\n\"squench\",\n\"squib\",\n\"squid\",\n\"squidge\",\n\"squidgy\",\n\"squiffy\",\n\"squilla\",\n\"squin\",\n\"squinch\",\n\"squinny\",\n\"squinsy\",\n\"squint\",\n\"squinty\",\n\"squire\",\n\"squiret\",\n\"squirk\",\n\"squirm\",\n\"squirmy\",\n\"squirr\",\n\"squirt\",\n\"squirty\",\n\"squish\",\n\"squishy\",\n\"squit\",\n\"squitch\",\n\"squoze\",\n\"squush\",\n\"squushy\",\n\"sraddha\",\n\"sramana\",\n\"sri\",\n\"sruti\",\n\"ssu\",\n\"st\",\n\"staab\",\n\"stab\",\n\"stabber\",\n\"stabile\",\n\"stable\",\n\"stabler\",\n\"stably\",\n\"staboy\",\n\"stacher\",\n\"stachys\",\n\"stack\",\n\"stacker\",\n\"stacte\",\n\"stadda\",\n\"staddle\",\n\"stade\",\n\"stadia\",\n\"stadic\",\n\"stadion\",\n\"stadium\",\n\"staff\",\n\"staffed\",\n\"staffer\",\n\"stag\",\n\"stage\",\n\"staged\",\n\"stager\",\n\"stagery\",\n\"stagese\",\n\"stagger\",\n\"staggie\",\n\"staggy\",\n\"stagily\",\n\"staging\",\n\"stagnum\",\n\"stagy\",\n\"staia\",\n\"staid\",\n\"staidly\",\n\"stain\",\n\"stainer\",\n\"staio\",\n\"stair\",\n\"staired\",\n\"stairy\",\n\"staith\",\n\"staiver\",\n\"stake\",\n\"staker\",\n\"stale\",\n\"stalely\",\n\"staling\",\n\"stalk\",\n\"stalked\",\n\"stalker\",\n\"stalko\",\n\"stalky\",\n\"stall\",\n\"stallar\",\n\"staller\",\n\"stam\",\n\"stambha\",\n\"stamen\",\n\"stamin\",\n\"stamina\",\n\"stammel\",\n\"stammer\",\n\"stamnos\",\n\"stamp\",\n\"stampee\",\n\"stamper\",\n\"stample\",\n\"stance\",\n\"stanch\",\n\"stand\",\n\"standee\",\n\"standel\",\n\"stander\",\n\"stane\",\n\"stang\",\n\"stanine\",\n\"stanjen\",\n\"stank\",\n\"stankie\",\n\"stannel\",\n\"stanner\",\n\"stannic\",\n\"stanno\",\n\"stannum\",\n\"stannyl\",\n\"stanza\",\n\"stanze\",\n\"stap\",\n\"stapes\",\n\"staple\",\n\"stapled\",\n\"stapler\",\n\"star\",\n\"starch\",\n\"starchy\",\n\"stardom\",\n\"stare\",\n\"staree\",\n\"starer\",\n\"starets\",\n\"starful\",\n\"staring\",\n\"stark\",\n\"starken\",\n\"starkly\",\n\"starky\",\n\"starlet\",\n\"starlit\",\n\"starn\",\n\"starnel\",\n\"starnie\",\n\"starost\",\n\"starred\",\n\"starry\",\n\"start\",\n\"starter\",\n\"startle\",\n\"startly\",\n\"startor\",\n\"starty\",\n\"starve\",\n\"starved\",\n\"starver\",\n\"starvy\",\n\"stary\",\n\"stases\",\n\"stash\",\n\"stashie\",\n\"stasis\",\n\"statal\",\n\"statant\",\n\"state\",\n\"stated\",\n\"stately\",\n\"stater\",\n\"static\",\n\"statics\",\n\"station\",\n\"statism\",\n\"statist\",\n\"stative\",\n\"stator\",\n\"statue\",\n\"statued\",\n\"stature\",\n\"status\",\n\"statute\",\n\"stauk\",\n\"staumer\",\n\"staun\",\n\"staunch\",\n\"staup\",\n\"stauter\",\n\"stave\",\n\"staver\",\n\"stavers\",\n\"staving\",\n\"staw\",\n\"stawn\",\n\"staxis\",\n\"stay\",\n\"stayed\",\n\"stayer\",\n\"staynil\",\n\"stays\",\n\"stchi\",\n\"stead\",\n\"steady\",\n\"steak\",\n\"steal\",\n\"stealed\",\n\"stealer\",\n\"stealth\",\n\"stealy\",\n\"steam\",\n\"steamer\",\n\"steamy\",\n\"stean\",\n\"stearic\",\n\"stearin\",\n\"stearyl\",\n\"steatin\",\n\"stech\",\n\"steddle\",\n\"steed\",\n\"steek\",\n\"steel\",\n\"steeler\",\n\"steely\",\n\"steen\",\n\"steenth\",\n\"steep\",\n\"steepen\",\n\"steeper\",\n\"steeple\",\n\"steeply\",\n\"steepy\",\n\"steer\",\n\"steerer\",\n\"steeve\",\n\"steever\",\n\"steg\",\n\"steid\",\n\"steigh\",\n\"stein\",\n\"stekan\",\n\"stela\",\n\"stelae\",\n\"stelai\",\n\"stelar\",\n\"stele\",\n\"stell\",\n\"stella\",\n\"stellar\",\n\"stem\",\n\"stema\",\n\"stemlet\",\n\"stemma\",\n\"stemmed\",\n\"stemmer\",\n\"stemmy\",\n\"stemple\",\n\"stemson\",\n\"sten\",\n\"stenar\",\n\"stench\",\n\"stenchy\",\n\"stencil\",\n\"stend\",\n\"steng\",\n\"stengah\",\n\"stenion\",\n\"steno\",\n\"stenog\",\n\"stent\",\n\"stenter\",\n\"stenton\",\n\"step\",\n\"steppe\",\n\"stepped\",\n\"stepper\",\n\"stepson\",\n\"stept\",\n\"stepway\",\n\"stere\",\n\"stereo\",\n\"steri\",\n\"steric\",\n\"sterics\",\n\"steride\",\n\"sterile\",\n\"sterin\",\n\"sterk\",\n\"sterlet\",\n\"stern\",\n\"sterna\",\n\"sternad\",\n\"sternal\",\n\"sterned\",\n\"sternly\",\n\"sternum\",\n\"stero\",\n\"steroid\",\n\"sterol\",\n\"stert\",\n\"stertor\",\n\"sterve\",\n\"stet\",\n\"stetch\",\n\"stevel\",\n\"steven\",\n\"stevia\",\n\"stew\",\n\"steward\",\n\"stewed\",\n\"stewpan\",\n\"stewpot\",\n\"stewy\",\n\"stey\",\n\"sthenia\",\n\"sthenic\",\n\"stib\",\n\"stibial\",\n\"stibic\",\n\"stibine\",\n\"stibium\",\n\"stich\",\n\"stichic\",\n\"stichid\",\n\"stick\",\n\"sticked\",\n\"sticker\",\n\"stickit\",\n\"stickle\",\n\"stickly\",\n\"sticks\",\n\"stickum\",\n\"sticky\",\n\"stid\",\n\"stiddy\",\n\"stife\",\n\"stiff\",\n\"stiffen\",\n\"stiffly\",\n\"stifle\",\n\"stifler\",\n\"stigma\",\n\"stigmai\",\n\"stigmal\",\n\"stigme\",\n\"stile\",\n\"stilet\",\n\"still\",\n\"stiller\",\n\"stilly\",\n\"stilt\",\n\"stilted\",\n\"stilter\",\n\"stilty\",\n\"stim\",\n\"stime\",\n\"stimuli\",\n\"stimy\",\n\"stine\",\n\"sting\",\n\"stinge\",\n\"stinger\",\n\"stingo\",\n\"stingy\",\n\"stink\",\n\"stinker\",\n\"stint\",\n\"stinted\",\n\"stinter\",\n\"stinty\",\n\"stion\",\n\"stionic\",\n\"stipe\",\n\"stiped\",\n\"stipel\",\n\"stipend\",\n\"stipes\",\n\"stippen\",\n\"stipple\",\n\"stipply\",\n\"stipula\",\n\"stipule\",\n\"stir\",\n\"stirk\",\n\"stirp\",\n\"stirps\",\n\"stirra\",\n\"stirrer\",\n\"stirrup\",\n\"stitch\",\n\"stite\",\n\"stith\",\n\"stithy\",\n\"stive\",\n\"stiver\",\n\"stivy\",\n\"stoa\",\n\"stoach\",\n\"stoat\",\n\"stoater\",\n\"stob\",\n\"stocah\",\n\"stock\",\n\"stocker\",\n\"stocks\",\n\"stocky\",\n\"stod\",\n\"stodge\",\n\"stodger\",\n\"stodgy\",\n\"stoep\",\n\"stof\",\n\"stoff\",\n\"stog\",\n\"stoga\",\n\"stogie\",\n\"stogy\",\n\"stoic\",\n\"stoical\",\n\"stoke\",\n\"stoker\",\n\"stola\",\n\"stolae\",\n\"stole\",\n\"stoled\",\n\"stolen\",\n\"stolid\",\n\"stolist\",\n\"stollen\",\n\"stolon\",\n\"stoma\",\n\"stomach\",\n\"stomata\",\n\"stomate\",\n\"stomium\",\n\"stomp\",\n\"stomper\",\n\"stond\",\n\"stone\",\n\"stoned\",\n\"stonen\",\n\"stoner\",\n\"stong\",\n\"stonied\",\n\"stonify\",\n\"stonily\",\n\"stoning\",\n\"stonish\",\n\"stonker\",\n\"stony\",\n\"stood\",\n\"stooded\",\n\"stooden\",\n\"stoof\",\n\"stooge\",\n\"stook\",\n\"stooker\",\n\"stookie\",\n\"stool\",\n\"stoon\",\n\"stoond\",\n\"stoop\",\n\"stooper\",\n\"stoory\",\n\"stoot\",\n\"stop\",\n\"stopa\",\n\"stope\",\n\"stoper\",\n\"stopgap\",\n\"stoping\",\n\"stopped\",\n\"stopper\",\n\"stoppit\",\n\"stopple\",\n\"storage\",\n\"storax\",\n\"store\",\n\"storeen\",\n\"storer\",\n\"storge\",\n\"storied\",\n\"storier\",\n\"storify\",\n\"stork\",\n\"storken\",\n\"storm\",\n\"stormer\",\n\"stormy\",\n\"story\",\n\"stosh\",\n\"stoss\",\n\"stot\",\n\"stotter\",\n\"stoun\",\n\"stound\",\n\"stoup\",\n\"stour\",\n\"stoury\",\n\"stoush\",\n\"stout\",\n\"stouten\",\n\"stouth\",\n\"stoutly\",\n\"stouty\",\n\"stove\",\n\"stoven\",\n\"stover\",\n\"stow\",\n\"stowage\",\n\"stowce\",\n\"stower\",\n\"stowing\",\n\"stra\",\n\"strack\",\n\"stract\",\n\"strad\",\n\"strade\",\n\"stradl\",\n\"stradld\",\n\"strae\",\n\"strafe\",\n\"strafer\",\n\"strag\",\n\"straik\",\n\"strain\",\n\"straint\",\n\"strait\",\n\"strake\",\n\"straked\",\n\"straky\",\n\"stram\",\n\"stramp\",\n\"strand\",\n\"strang\",\n\"strange\",\n\"strany\",\n\"strap\",\n\"strass\",\n\"strata\",\n\"stratal\",\n\"strath\",\n\"strati\",\n\"stratic\",\n\"stratum\",\n\"stratus\",\n\"strave\",\n\"straw\",\n\"strawen\",\n\"strawer\",\n\"strawy\",\n\"stray\",\n\"strayer\",\n\"stre\",\n\"streak\",\n\"streaky\",\n\"stream\",\n\"streamy\",\n\"streck\",\n\"stree\",\n\"streek\",\n\"streel\",\n\"streen\",\n\"streep\",\n\"street\",\n\"streets\",\n\"streite\",\n\"streke\",\n\"stremma\",\n\"streng\",\n\"strent\",\n\"strenth\",\n\"strepen\",\n\"strepor\",\n\"stress\",\n\"stret\",\n\"stretch\",\n\"strette\",\n\"stretti\",\n\"stretto\",\n\"strew\",\n\"strewer\",\n\"strewn\",\n\"strey\",\n\"streyne\",\n\"stria\",\n\"striae\",\n\"strial\",\n\"striate\",\n\"strich\",\n\"striche\",\n\"strick\",\n\"strict\",\n\"strid\",\n\"stride\",\n\"strider\",\n\"stridor\",\n\"strife\",\n\"strig\",\n\"striga\",\n\"strigae\",\n\"strigal\",\n\"stright\",\n\"strigil\",\n\"strike\",\n\"striker\",\n\"strind\",\n\"string\",\n\"stringy\",\n\"striola\",\n\"strip\",\n\"stripe\",\n\"striped\",\n\"striper\",\n\"stript\",\n\"stripy\",\n\"strit\",\n\"strive\",\n\"strived\",\n\"striven\",\n\"striver\",\n\"strix\",\n\"stroam\",\n\"strobic\",\n\"strode\",\n\"stroil\",\n\"stroke\",\n\"stroker\",\n\"stroky\",\n\"strold\",\n\"stroll\",\n\"strolld\",\n\"strom\",\n\"stroma\",\n\"stromal\",\n\"stromb\",\n\"strome\",\n\"strone\",\n\"strong\",\n\"strook\",\n\"stroot\",\n\"strop\",\n\"strophe\",\n\"stroth\",\n\"stroud\",\n\"stroup\",\n\"strove\",\n\"strow\",\n\"strowd\",\n\"strown\",\n\"stroy\",\n\"stroyer\",\n\"strub\",\n\"struck\",\n\"strudel\",\n\"strue\",\n\"strum\",\n\"struma\",\n\"strumae\",\n\"strung\",\n\"strunt\",\n\"strut\",\n\"struth\",\n\"struv\",\n\"strych\",\n\"stub\",\n\"stubb\",\n\"stubbed\",\n\"stubber\",\n\"stubble\",\n\"stubbly\",\n\"stubboy\",\n\"stubby\",\n\"stuber\",\n\"stuboy\",\n\"stucco\",\n\"stuck\",\n\"stud\",\n\"studder\",\n\"studdie\",\n\"studdle\",\n\"stude\",\n\"student\",\n\"studia\",\n\"studied\",\n\"studier\",\n\"studio\",\n\"studium\",\n\"study\",\n\"stue\",\n\"stuff\",\n\"stuffed\",\n\"stuffer\",\n\"stuffy\",\n\"stug\",\n\"stuggy\",\n\"stuiver\",\n\"stull\",\n\"stuller\",\n\"stulm\",\n\"stum\",\n\"stumble\",\n\"stumbly\",\n\"stumer\",\n\"stummer\",\n\"stummy\",\n\"stump\",\n\"stumper\",\n\"stumpy\",\n\"stun\",\n\"stung\",\n\"stunk\",\n\"stunner\",\n\"stunsle\",\n\"stunt\",\n\"stunted\",\n\"stunter\",\n\"stunty\",\n\"stupa\",\n\"stupe\",\n\"stupefy\",\n\"stupend\",\n\"stupent\",\n\"stupex\",\n\"stupid\",\n\"stupor\",\n\"stupose\",\n\"stupp\",\n\"stuprum\",\n\"sturdy\",\n\"sturine\",\n\"sturk\",\n\"sturt\",\n\"sturtan\",\n\"sturtin\",\n\"stuss\",\n\"stut\",\n\"stutter\",\n\"sty\",\n\"styan\",\n\"styca\",\n\"styful\",\n\"stylar\",\n\"stylate\",\n\"style\",\n\"styler\",\n\"stylet\",\n\"styline\",\n\"styling\",\n\"stylish\",\n\"stylist\",\n\"stylite\",\n\"stylize\",\n\"stylo\",\n\"styloid\",\n\"stylops\",\n\"stylus\",\n\"stymie\",\n\"stypsis\",\n\"styptic\",\n\"styrax\",\n\"styrene\",\n\"styrol\",\n\"styrone\",\n\"styryl\",\n\"stythe\",\n\"styward\",\n\"suable\",\n\"suably\",\n\"suade\",\n\"suaharo\",\n\"suant\",\n\"suantly\",\n\"suasion\",\n\"suasive\",\n\"suasory\",\n\"suave\",\n\"suavely\",\n\"suavify\",\n\"suavity\",\n\"sub\",\n\"subacid\",\n\"subact\",\n\"subage\",\n\"subah\",\n\"subaid\",\n\"subanal\",\n\"subarch\",\n\"subarea\",\n\"subatom\",\n\"subaud\",\n\"subband\",\n\"subbank\",\n\"subbase\",\n\"subbass\",\n\"subbeau\",\n\"subbias\",\n\"subbing\",\n\"subcase\",\n\"subcash\",\n\"subcast\",\n\"subcell\",\n\"subcity\",\n\"subclan\",\n\"subcool\",\n\"subdate\",\n\"subdean\",\n\"subdeb\",\n\"subdial\",\n\"subdie\",\n\"subdual\",\n\"subduce\",\n\"subduct\",\n\"subdue\",\n\"subdued\",\n\"subduer\",\n\"subecho\",\n\"subedit\",\n\"suber\",\n\"suberic\",\n\"suberin\",\n\"subface\",\n\"subfeu\",\n\"subfief\",\n\"subfix\",\n\"subform\",\n\"subfusc\",\n\"subfusk\",\n\"subgape\",\n\"subgens\",\n\"subget\",\n\"subgit\",\n\"subgod\",\n\"subgrin\",\n\"subgyre\",\n\"subhall\",\n\"subhead\",\n\"subherd\",\n\"subhero\",\n\"subicle\",\n\"subidar\",\n\"subidea\",\n\"subitem\",\n\"subjack\",\n\"subject\",\n\"subjee\",\n\"subjoin\",\n\"subking\",\n\"sublate\",\n\"sublet\",\n\"sublid\",\n\"sublime\",\n\"sublong\",\n\"sublot\",\n\"submaid\",\n\"submain\",\n\"subman\",\n\"submind\",\n\"submiss\",\n\"submit\",\n\"subnect\",\n\"subness\",\n\"subnex\",\n\"subnote\",\n\"subnude\",\n\"suboral\",\n\"suborn\",\n\"suboval\",\n\"subpart\",\n\"subpass\",\n\"subpial\",\n\"subpimp\",\n\"subplat\",\n\"subplot\",\n\"subplow\",\n\"subpool\",\n\"subport\",\n\"subrace\",\n\"subrent\",\n\"subroot\",\n\"subrule\",\n\"subsale\",\n\"subsalt\",\n\"subsea\",\n\"subsect\",\n\"subsept\",\n\"subset\",\n\"subside\",\n\"subsidy\",\n\"subsill\",\n\"subsist\",\n\"subsoil\",\n\"subsult\",\n\"subsume\",\n\"subtack\",\n\"subtend\",\n\"subtext\",\n\"subtile\",\n\"subtill\",\n\"subtle\",\n\"subtly\",\n\"subtone\",\n\"subtype\",\n\"subunit\",\n\"suburb\",\n\"subvein\",\n\"subvene\",\n\"subvert\",\n\"subvola\",\n\"subway\",\n\"subwink\",\n\"subzone\",\n\"succade\",\n\"succeed\",\n\"succent\",\n\"success\",\n\"succi\",\n\"succin\",\n\"succise\",\n\"succor\",\n\"succory\",\n\"succous\",\n\"succub\",\n\"succuba\",\n\"succube\",\n\"succula\",\n\"succumb\",\n\"succuss\",\n\"such\",\n\"suck\",\n\"suckage\",\n\"sucken\",\n\"sucker\",\n\"sucking\",\n\"suckle\",\n\"suckler\",\n\"suclat\",\n\"sucrate\",\n\"sucre\",\n\"sucrose\",\n\"suction\",\n\"sucuri\",\n\"sucuriu\",\n\"sud\",\n\"sudamen\",\n\"sudary\",\n\"sudate\",\n\"sudd\",\n\"sudden\",\n\"sudder\",\n\"suddle\",\n\"suddy\",\n\"sudoral\",\n\"sudoric\",\n\"suds\",\n\"sudsman\",\n\"sudsy\",\n\"sue\",\n\"suede\",\n\"suer\",\n\"suet\",\n\"suety\",\n\"suff\",\n\"suffect\",\n\"suffer\",\n\"suffete\",\n\"suffice\",\n\"suffix\",\n\"sufflue\",\n\"suffuse\",\n\"sugamo\",\n\"sugan\",\n\"sugar\",\n\"sugared\",\n\"sugarer\",\n\"sugary\",\n\"sugent\",\n\"suggest\",\n\"sugh\",\n\"sugi\",\n\"suguaro\",\n\"suhuaro\",\n\"suicide\",\n\"suid\",\n\"suidian\",\n\"suiform\",\n\"suimate\",\n\"suine\",\n\"suing\",\n\"suingly\",\n\"suint\",\n\"suist\",\n\"suit\",\n\"suite\",\n\"suiting\",\n\"suitor\",\n\"suity\",\n\"suji\",\n\"sulcal\",\n\"sulcar\",\n\"sulcate\",\n\"sulcus\",\n\"suld\",\n\"sulea\",\n\"sulfa\",\n\"sulfato\",\n\"sulfion\",\n\"sulfury\",\n\"sulk\",\n\"sulka\",\n\"sulker\",\n\"sulkily\",\n\"sulky\",\n\"sull\",\n\"sulla\",\n\"sullage\",\n\"sullen\",\n\"sullow\",\n\"sully\",\n\"sulpha\",\n\"sulpho\",\n\"sulphur\",\n\"sultam\",\n\"sultan\",\n\"sultana\",\n\"sultane\",\n\"sultone\",\n\"sultry\",\n\"sulung\",\n\"sum\",\n\"sumac\",\n\"sumatra\",\n\"sumbul\",\n\"sumless\",\n\"summage\",\n\"summand\",\n\"summar\",\n\"summary\",\n\"summate\",\n\"summed\",\n\"summer\",\n\"summery\",\n\"summist\",\n\"summit\",\n\"summity\",\n\"summon\",\n\"summons\",\n\"summula\",\n\"summut\",\n\"sumner\",\n\"sump\",\n\"sumpage\",\n\"sumper\",\n\"sumph\",\n\"sumphy\",\n\"sumpit\",\n\"sumple\",\n\"sumpman\",\n\"sumpter\",\n\"sun\",\n\"sunbeam\",\n\"sunbird\",\n\"sunbow\",\n\"sunburn\",\n\"suncup\",\n\"sundae\",\n\"sundang\",\n\"sundari\",\n\"sundek\",\n\"sunder\",\n\"sundew\",\n\"sundial\",\n\"sundik\",\n\"sundog\",\n\"sundown\",\n\"sundra\",\n\"sundri\",\n\"sundry\",\n\"sune\",\n\"sunfall\",\n\"sunfast\",\n\"sunfish\",\n\"sung\",\n\"sungha\",\n\"sunglo\",\n\"sunglow\",\n\"sunk\",\n\"sunken\",\n\"sunket\",\n\"sunlamp\",\n\"sunland\",\n\"sunless\",\n\"sunlet\",\n\"sunlike\",\n\"sunlit\",\n\"sunn\",\n\"sunnily\",\n\"sunnud\",\n\"sunny\",\n\"sunray\",\n\"sunrise\",\n\"sunroom\",\n\"sunset\",\n\"sunsmit\",\n\"sunspot\",\n\"sunt\",\n\"sunup\",\n\"sunward\",\n\"sunway\",\n\"sunways\",\n\"sunweed\",\n\"sunwise\",\n\"sunyie\",\n\"sup\",\n\"supa\",\n\"supari\",\n\"supawn\",\n\"supe\",\n\"super\",\n\"superb\",\n\"supine\",\n\"supper\",\n\"supping\",\n\"supple\",\n\"supply\",\n\"support\",\n\"suppose\",\n\"suppost\",\n\"supreme\",\n\"sur\",\n\"sura\",\n\"surah\",\n\"surahi\",\n\"sural\",\n\"suranal\",\n\"surat\",\n\"surbase\",\n\"surbate\",\n\"surbed\",\n\"surcoat\",\n\"surcrue\",\n\"surculi\",\n\"surd\",\n\"surdent\",\n\"surdity\",\n\"sure\",\n\"surely\",\n\"sures\",\n\"surette\",\n\"surety\",\n\"surf\",\n\"surface\",\n\"surfacy\",\n\"surfeit\",\n\"surfer\",\n\"surfle\",\n\"surfman\",\n\"surfuse\",\n\"surfy\",\n\"surge\",\n\"surgent\",\n\"surgeon\",\n\"surgery\",\n\"surging\",\n\"surgy\",\n\"suriga\",\n\"surlily\",\n\"surly\",\n\"surma\",\n\"surmark\",\n\"surmise\",\n\"surname\",\n\"surnap\",\n\"surnay\",\n\"surpass\",\n\"surplus\",\n\"surra\",\n\"surrey\",\n\"surtax\",\n\"surtout\",\n\"survey\",\n\"survive\",\n\"suscept\",\n\"susi\",\n\"suslik\",\n\"suspect\",\n\"suspend\",\n\"suspire\",\n\"sustain\",\n\"susu\",\n\"susurr\",\n\"suther\",\n\"sutile\",\n\"sutler\",\n\"sutlery\",\n\"sutor\",\n\"sutra\",\n\"suttee\",\n\"sutten\",\n\"suttin\",\n\"suttle\",\n\"sutural\",\n\"suture\",\n\"suum\",\n\"suwarro\",\n\"suwe\",\n\"suz\",\n\"svelte\",\n\"swa\",\n\"swab\",\n\"swabber\",\n\"swabble\",\n\"swack\",\n\"swacken\",\n\"swad\",\n\"swaddle\",\n\"swaddy\",\n\"swag\",\n\"swage\",\n\"swager\",\n\"swagger\",\n\"swaggie\",\n\"swaggy\",\n\"swagman\",\n\"swain\",\n\"swaird\",\n\"swale\",\n\"swaler\",\n\"swaling\",\n\"swallet\",\n\"swallo\",\n\"swallow\",\n\"swam\",\n\"swami\",\n\"swamp\",\n\"swamper\",\n\"swampy\",\n\"swan\",\n\"swang\",\n\"swangy\",\n\"swank\",\n\"swanker\",\n\"swanky\",\n\"swanner\",\n\"swanny\",\n\"swap\",\n\"swape\",\n\"swapper\",\n\"swaraj\",\n\"swarbie\",\n\"sward\",\n\"swardy\",\n\"sware\",\n\"swarf\",\n\"swarfer\",\n\"swarm\",\n\"swarmer\",\n\"swarmy\",\n\"swarry\",\n\"swart\",\n\"swarth\",\n\"swarthy\",\n\"swartly\",\n\"swarty\",\n\"swarve\",\n\"swash\",\n\"swasher\",\n\"swashy\",\n\"swat\",\n\"swatch\",\n\"swath\",\n\"swathe\",\n\"swather\",\n\"swathy\",\n\"swatter\",\n\"swattle\",\n\"swaver\",\n\"sway\",\n\"swayed\",\n\"swayer\",\n\"swayful\",\n\"swaying\",\n\"sweal\",\n\"swear\",\n\"swearer\",\n\"sweat\",\n\"sweated\",\n\"sweater\",\n\"sweath\",\n\"sweaty\",\n\"swedge\",\n\"sweeny\",\n\"sweep\",\n\"sweeper\",\n\"sweepy\",\n\"sweer\",\n\"sweered\",\n\"sweet\",\n\"sweeten\",\n\"sweetie\",\n\"sweetly\",\n\"sweety\",\n\"swego\",\n\"swell\",\n\"swelled\",\n\"sweller\",\n\"swelly\",\n\"swelp\",\n\"swelt\",\n\"swelter\",\n\"swelth\",\n\"sweltry\",\n\"swelty\",\n\"swep\",\n\"swept\",\n\"swerd\",\n\"swerve\",\n\"swerver\",\n\"swick\",\n\"swidge\",\n\"swift\",\n\"swiften\",\n\"swifter\",\n\"swifty\",\n\"swig\",\n\"swigger\",\n\"swiggle\",\n\"swile\",\n\"swill\",\n\"swiller\",\n\"swim\",\n\"swimmer\",\n\"swimmy\",\n\"swimy\",\n\"swindle\",\n\"swine\",\n\"swinely\",\n\"swinery\",\n\"swiney\",\n\"swing\",\n\"swinge\",\n\"swinger\",\n\"swingle\",\n\"swingy\",\n\"swinish\",\n\"swink\",\n\"swinney\",\n\"swipe\",\n\"swiper\",\n\"swipes\",\n\"swiple\",\n\"swipper\",\n\"swipy\",\n\"swird\",\n\"swire\",\n\"swirl\",\n\"swirly\",\n\"swish\",\n\"swisher\",\n\"swishy\",\n\"swiss\",\n\"switch\",\n\"switchy\",\n\"swith\",\n\"swithe\",\n\"swithen\",\n\"swither\",\n\"swivel\",\n\"swivet\",\n\"swiz\",\n\"swizzle\",\n\"swob\",\n\"swollen\",\n\"swom\",\n\"swonken\",\n\"swoon\",\n\"swooned\",\n\"swoony\",\n\"swoop\",\n\"swooper\",\n\"swoosh\",\n\"sword\",\n\"swore\",\n\"sworn\",\n\"swosh\",\n\"swot\",\n\"swotter\",\n\"swounds\",\n\"swow\",\n\"swum\",\n\"swung\",\n\"swungen\",\n\"swure\",\n\"syagush\",\n\"sybotic\",\n\"syce\",\n\"sycee\",\n\"sycock\",\n\"sycoma\",\n\"syconid\",\n\"syconus\",\n\"sycosis\",\n\"sye\",\n\"syenite\",\n\"sylid\",\n\"syllab\",\n\"syllabe\",\n\"syllabi\",\n\"sylloge\",\n\"sylph\",\n\"sylphic\",\n\"sylphid\",\n\"sylphy\",\n\"sylva\",\n\"sylvae\",\n\"sylvage\",\n\"sylvan\",\n\"sylvate\",\n\"sylvic\",\n\"sylvine\",\n\"sylvite\",\n\"symbion\",\n\"symbiot\",\n\"symbol\",\n\"sympode\",\n\"symptom\",\n\"synacme\",\n\"synacmy\",\n\"synange\",\n\"synapse\",\n\"synapte\",\n\"synaxar\",\n\"synaxis\",\n\"sync\",\n\"syncarp\",\n\"synch\",\n\"synchro\",\n\"syncope\",\n\"syndic\",\n\"syndoc\",\n\"syne\",\n\"synema\",\n\"synergy\",\n\"synesis\",\n\"syngamy\",\n\"synod\",\n\"synodal\",\n\"synoecy\",\n\"synonym\",\n\"synopsy\",\n\"synovia\",\n\"syntan\",\n\"syntax\",\n\"synthol\",\n\"syntomy\",\n\"syntone\",\n\"syntony\",\n\"syntype\",\n\"synusia\",\n\"sypher\",\n\"syre\",\n\"syringa\",\n\"syringe\",\n\"syrinx\",\n\"syrma\",\n\"syrphid\",\n\"syrt\",\n\"syrtic\",\n\"syrup\",\n\"syruped\",\n\"syruper\",\n\"syrupy\",\n\"syssel\",\n\"system\",\n\"systole\",\n\"systyle\",\n\"syzygy\",\n\"t\",\n\"ta\",\n\"taa\",\n\"taar\",\n\"tab\",\n\"tabacin\",\n\"tabacum\",\n\"tabanid\",\n\"tabard\",\n\"tabaret\",\n\"tabaxir\",\n\"tabber\",\n\"tabby\",\n\"tabefy\",\n\"tabella\",\n\"taberna\",\n\"tabes\",\n\"tabet\",\n\"tabetic\",\n\"tabic\",\n\"tabid\",\n\"tabidly\",\n\"tabific\",\n\"tabinet\",\n\"tabla\",\n\"table\",\n\"tableau\",\n\"tabled\",\n\"tabler\",\n\"tables\",\n\"tablet\",\n\"tabling\",\n\"tabloid\",\n\"tabog\",\n\"taboo\",\n\"taboot\",\n\"tabor\",\n\"taborer\",\n\"taboret\",\n\"taborin\",\n\"tabour\",\n\"tabret\",\n\"tabu\",\n\"tabula\",\n\"tabular\",\n\"tabule\",\n\"tabut\",\n\"taccada\",\n\"tach\",\n\"tache\",\n\"tachiol\",\n\"tacit\",\n\"tacitly\",\n\"tack\",\n\"tacker\",\n\"tacket\",\n\"tackety\",\n\"tackey\",\n\"tacking\",\n\"tackle\",\n\"tackled\",\n\"tackler\",\n\"tacky\",\n\"tacnode\",\n\"tacso\",\n\"tact\",\n\"tactful\",\n\"tactic\",\n\"tactics\",\n\"tactile\",\n\"taction\",\n\"tactite\",\n\"tactive\",\n\"tactor\",\n\"tactual\",\n\"tactus\",\n\"tad\",\n\"tade\",\n\"tadpole\",\n\"tae\",\n\"tael\",\n\"taen\",\n\"taenia\",\n\"taenial\",\n\"taenian\",\n\"taenite\",\n\"taennin\",\n\"taffeta\",\n\"taffety\",\n\"taffle\",\n\"taffy\",\n\"tafia\",\n\"taft\",\n\"tafwiz\",\n\"tag\",\n\"tagetol\",\n\"tagged\",\n\"tagger\",\n\"taggle\",\n\"taggy\",\n\"taglet\",\n\"taglike\",\n\"taglock\",\n\"tagrag\",\n\"tagsore\",\n\"tagtail\",\n\"tagua\",\n\"taguan\",\n\"tagwerk\",\n\"taha\",\n\"taheen\",\n\"tahil\",\n\"tahin\",\n\"tahr\",\n\"tahsil\",\n\"tahua\",\n\"tai\",\n\"taiaha\",\n\"taich\",\n\"taiga\",\n\"taigle\",\n\"taihoa\",\n\"tail\",\n\"tailage\",\n\"tailed\",\n\"tailer\",\n\"tailet\",\n\"tailge\",\n\"tailing\",\n\"taille\",\n\"taillie\",\n\"tailor\",\n\"tailory\",\n\"tailpin\",\n\"taily\",\n\"tailzee\",\n\"tailzie\",\n\"taimen\",\n\"tain\",\n\"taint\",\n\"taintor\",\n\"taipan\",\n\"taipo\",\n\"tairge\",\n\"tairger\",\n\"tairn\",\n\"taisch\",\n\"taise\",\n\"taissle\",\n\"tait\",\n\"taiver\",\n\"taivers\",\n\"taivert\",\n\"taj\",\n\"takable\",\n\"takar\",\n\"take\",\n\"takeful\",\n\"taken\",\n\"taker\",\n\"takin\",\n\"taking\",\n\"takings\",\n\"takosis\",\n\"takt\",\n\"taky\",\n\"takyr\",\n\"tal\",\n\"tala\",\n\"talabon\",\n\"talahib\",\n\"talaje\",\n\"talak\",\n\"talao\",\n\"talar\",\n\"talari\",\n\"talaria\",\n\"talaric\",\n\"talayot\",\n\"talbot\",\n\"talc\",\n\"talcer\",\n\"talcky\",\n\"talcoid\",\n\"talcose\",\n\"talcous\",\n\"talcum\",\n\"tald\",\n\"tale\",\n\"taled\",\n\"taleful\",\n\"talent\",\n\"taler\",\n\"tales\",\n\"tali\",\n\"taliage\",\n\"taliera\",\n\"talion\",\n\"talipat\",\n\"taliped\",\n\"talipes\",\n\"talipot\",\n\"talis\",\n\"talisay\",\n\"talite\",\n\"talitol\",\n\"talk\",\n\"talker\",\n\"talkful\",\n\"talkie\",\n\"talking\",\n\"talky\",\n\"tall\",\n\"tallage\",\n\"tallboy\",\n\"taller\",\n\"tallero\",\n\"talles\",\n\"tallet\",\n\"talliar\",\n\"tallier\",\n\"tallis\",\n\"tallish\",\n\"tallit\",\n\"tallith\",\n\"talloel\",\n\"tallote\",\n\"tallow\",\n\"tallowy\",\n\"tally\",\n\"tallyho\",\n\"talma\",\n\"talon\",\n\"taloned\",\n\"talonic\",\n\"talonid\",\n\"talose\",\n\"talpid\",\n\"talpify\",\n\"talpine\",\n\"talpoid\",\n\"talthib\",\n\"taluk\",\n\"taluka\",\n\"talus\",\n\"taluto\",\n\"talwar\",\n\"talwood\",\n\"tam\",\n\"tamable\",\n\"tamably\",\n\"tamale\",\n\"tamandu\",\n\"tamanu\",\n\"tamara\",\n\"tamarao\",\n\"tamarin\",\n\"tamas\",\n\"tamasha\",\n\"tambac\",\n\"tamber\",\n\"tambo\",\n\"tamboo\",\n\"tambor\",\n\"tambour\",\n\"tame\",\n\"tamein\",\n\"tamely\",\n\"tamer\",\n\"tamis\",\n\"tamise\",\n\"tamlung\",\n\"tammie\",\n\"tammock\",\n\"tammy\",\n\"tamp\",\n\"tampala\",\n\"tampan\",\n\"tampang\",\n\"tamper\",\n\"tampin\",\n\"tamping\",\n\"tampion\",\n\"tampon\",\n\"tampoon\",\n\"tan\",\n\"tana\",\n\"tanach\",\n\"tanager\",\n\"tanaist\",\n\"tanak\",\n\"tanan\",\n\"tanbark\",\n\"tanbur\",\n\"tancel\",\n\"tandan\",\n\"tandem\",\n\"tandle\",\n\"tandour\",\n\"tane\",\n\"tang\",\n\"tanga\",\n\"tanged\",\n\"tangelo\",\n\"tangent\",\n\"tanger\",\n\"tangham\",\n\"tanghan\",\n\"tanghin\",\n\"tangi\",\n\"tangie\",\n\"tangka\",\n\"tanglad\",\n\"tangle\",\n\"tangler\",\n\"tangly\",\n\"tango\",\n\"tangram\",\n\"tangs\",\n\"tangue\",\n\"tangum\",\n\"tangun\",\n\"tangy\",\n\"tanh\",\n\"tanha\",\n\"tania\",\n\"tanica\",\n\"tanier\",\n\"tanist\",\n\"tanjib\",\n\"tanjong\",\n\"tank\",\n\"tanka\",\n\"tankage\",\n\"tankah\",\n\"tankard\",\n\"tanked\",\n\"tanker\",\n\"tankert\",\n\"tankful\",\n\"tankle\",\n\"tankman\",\n\"tanling\",\n\"tannage\",\n\"tannaic\",\n\"tannaim\",\n\"tannase\",\n\"tannate\",\n\"tanned\",\n\"tanner\",\n\"tannery\",\n\"tannic\",\n\"tannide\",\n\"tannin\",\n\"tanning\",\n\"tannoid\",\n\"tannyl\",\n\"tanoa\",\n\"tanquam\",\n\"tanquen\",\n\"tanrec\",\n\"tansy\",\n\"tantara\",\n\"tanti\",\n\"tantivy\",\n\"tantle\",\n\"tantra\",\n\"tantric\",\n\"tantrik\",\n\"tantrum\",\n\"tantum\",\n\"tanwood\",\n\"tanyard\",\n\"tanzeb\",\n\"tanzib\",\n\"tanzy\",\n\"tao\",\n\"taotai\",\n\"taoyin\",\n\"tap\",\n\"tapa\",\n\"tapalo\",\n\"tapas\",\n\"tapasvi\",\n\"tape\",\n\"tapeman\",\n\"tapen\",\n\"taper\",\n\"tapered\",\n\"taperer\",\n\"taperly\",\n\"tapet\",\n\"tapetal\",\n\"tapete\",\n\"tapeti\",\n\"tapetum\",\n\"taphole\",\n\"tapia\",\n\"tapioca\",\n\"tapir\",\n\"tapis\",\n\"tapism\",\n\"tapist\",\n\"taplash\",\n\"taplet\",\n\"tapmost\",\n\"tapnet\",\n\"tapoa\",\n\"tapoun\",\n\"tappa\",\n\"tappall\",\n\"tappaul\",\n\"tappen\",\n\"tapper\",\n\"tappet\",\n\"tapping\",\n\"tappoon\",\n\"taproom\",\n\"taproot\",\n\"taps\",\n\"tapster\",\n\"tapu\",\n\"tapul\",\n\"taqua\",\n\"tar\",\n\"tara\",\n\"taraf\",\n\"tarage\",\n\"tarairi\",\n\"tarand\",\n\"taraph\",\n\"tarapin\",\n\"tarata\",\n\"taratah\",\n\"tarau\",\n\"tarbet\",\n\"tarboy\",\n\"tarbush\",\n\"tardily\",\n\"tardive\",\n\"tardle\",\n\"tardy\",\n\"tare\",\n\"tarea\",\n\"tarefa\",\n\"tarente\",\n\"tarfa\",\n\"targe\",\n\"targer\",\n\"target\",\n\"tarhood\",\n\"tari\",\n\"tarie\",\n\"tariff\",\n\"tarin\",\n\"tariric\",\n\"tarish\",\n\"tarkhan\",\n\"tarlike\",\n\"tarmac\",\n\"tarman\",\n\"tarn\",\n\"tarnal\",\n\"tarnish\",\n\"taro\",\n\"taroc\",\n\"tarocco\",\n\"tarok\",\n\"tarot\",\n\"tarp\",\n\"tarpan\",\n\"tarpon\",\n\"tarpot\",\n\"tarpum\",\n\"tarr\",\n\"tarrack\",\n\"tarras\",\n\"tarrass\",\n\"tarred\",\n\"tarrer\",\n\"tarri\",\n\"tarrie\",\n\"tarrier\",\n\"tarrify\",\n\"tarrily\",\n\"tarrish\",\n\"tarrock\",\n\"tarrow\",\n\"tarry\",\n\"tars\",\n\"tarsal\",\n\"tarsale\",\n\"tarse\",\n\"tarsi\",\n\"tarsia\",\n\"tarsier\",\n\"tarsome\",\n\"tarsus\",\n\"tart\",\n\"tartago\",\n\"tartan\",\n\"tartana\",\n\"tartane\",\n\"tartar\",\n\"tarten\",\n\"tartish\",\n\"tartle\",\n\"tartlet\",\n\"tartly\",\n\"tartro\",\n\"tartryl\",\n\"tarve\",\n\"tarweed\",\n\"tarwood\",\n\"taryard\",\n\"tasajo\",\n\"tascal\",\n\"tasco\",\n\"tash\",\n\"tashie\",\n\"tashlik\",\n\"tashrif\",\n\"task\",\n\"taskage\",\n\"tasker\",\n\"taskit\",\n\"taslet\",\n\"tass\",\n\"tassago\",\n\"tassah\",\n\"tassal\",\n\"tassard\",\n\"tasse\",\n\"tassel\",\n\"tassely\",\n\"tasser\",\n\"tasset\",\n\"tassie\",\n\"tassoo\",\n\"taste\",\n\"tasted\",\n\"tasten\",\n\"taster\",\n\"tastily\",\n\"tasting\",\n\"tasty\",\n\"tasu\",\n\"tat\",\n\"tataupa\",\n\"tatbeb\",\n\"tatchy\",\n\"tate\",\n\"tater\",\n\"tath\",\n\"tatie\",\n\"tatinek\",\n\"tatler\",\n\"tatou\",\n\"tatouay\",\n\"tatsman\",\n\"tatta\",\n\"tatter\",\n\"tattery\",\n\"tatther\",\n\"tattied\",\n\"tatting\",\n\"tattle\",\n\"tattler\",\n\"tattoo\",\n\"tattva\",\n\"tatty\",\n\"tatu\",\n\"tau\",\n\"taught\",\n\"taula\",\n\"taum\",\n\"taun\",\n\"taunt\",\n\"taunter\",\n\"taupe\",\n\"taupo\",\n\"taupou\",\n\"taur\",\n\"taurean\",\n\"taurian\",\n\"tauric\",\n\"taurine\",\n\"taurite\",\n\"tauryl\",\n\"taut\",\n\"tautaug\",\n\"tauted\",\n\"tauten\",\n\"tautit\",\n\"tautly\",\n\"tautog\",\n\"tav\",\n\"tave\",\n\"tavell\",\n\"taver\",\n\"tavern\",\n\"tavers\",\n\"tavert\",\n\"tavola\",\n\"taw\",\n\"tawa\",\n\"tawdry\",\n\"tawer\",\n\"tawery\",\n\"tawie\",\n\"tawite\",\n\"tawkee\",\n\"tawkin\",\n\"tawn\",\n\"tawney\",\n\"tawnily\",\n\"tawnle\",\n\"tawny\",\n\"tawpi\",\n\"tawpie\",\n\"taws\",\n\"tawse\",\n\"tawtie\",\n\"tax\",\n\"taxable\",\n\"taxably\",\n\"taxator\",\n\"taxed\",\n\"taxeme\",\n\"taxemic\",\n\"taxer\",\n\"taxi\",\n\"taxibus\",\n\"taxicab\",\n\"taximan\",\n\"taxine\",\n\"taxing\",\n\"taxis\",\n\"taxite\",\n\"taxitic\",\n\"taxless\",\n\"taxman\",\n\"taxon\",\n\"taxor\",\n\"taxpaid\",\n\"taxwax\",\n\"taxy\",\n\"tay\",\n\"tayer\",\n\"tayir\",\n\"tayra\",\n\"taysaam\",\n\"tazia\",\n\"tch\",\n\"tchai\",\n\"tcharik\",\n\"tchast\",\n\"tche\",\n\"tchick\",\n\"tchu\",\n\"tck\",\n\"te\",\n\"tea\",\n\"teabox\",\n\"teaboy\",\n\"teacake\",\n\"teacart\",\n\"teach\",\n\"teache\",\n\"teacher\",\n\"teachy\",\n\"teacup\",\n\"tead\",\n\"teadish\",\n\"teaer\",\n\"teaey\",\n\"teagle\",\n\"teaish\",\n\"teaism\",\n\"teak\",\n\"teal\",\n\"tealery\",\n\"tealess\",\n\"team\",\n\"teaman\",\n\"teameo\",\n\"teamer\",\n\"teaming\",\n\"teamman\",\n\"tean\",\n\"teanal\",\n\"teap\",\n\"teapot\",\n\"teapoy\",\n\"tear\",\n\"tearage\",\n\"tearcat\",\n\"tearer\",\n\"tearful\",\n\"tearing\",\n\"tearlet\",\n\"tearoom\",\n\"tearpit\",\n\"teart\",\n\"teary\",\n\"tease\",\n\"teasel\",\n\"teaser\",\n\"teashop\",\n\"teasing\",\n\"teasler\",\n\"teasy\",\n\"teat\",\n\"teated\",\n\"teathe\",\n\"teather\",\n\"teatime\",\n\"teatman\",\n\"teaty\",\n\"teave\",\n\"teaware\",\n\"teaze\",\n\"teazer\",\n\"tebbet\",\n\"tec\",\n\"teca\",\n\"tecali\",\n\"tech\",\n\"techily\",\n\"technic\",\n\"techous\",\n\"techy\",\n\"teck\",\n\"tecomin\",\n\"tecon\",\n\"tectal\",\n\"tectum\",\n\"tecum\",\n\"tecuma\",\n\"ted\",\n\"tedder\",\n\"tedge\",\n\"tedious\",\n\"tedium\",\n\"tee\",\n\"teedle\",\n\"teel\",\n\"teem\",\n\"teemer\",\n\"teemful\",\n\"teeming\",\n\"teems\",\n\"teen\",\n\"teenage\",\n\"teenet\",\n\"teens\",\n\"teensy\",\n\"teenty\",\n\"teeny\",\n\"teer\",\n\"teerer\",\n\"teest\",\n\"teet\",\n\"teetan\",\n\"teeter\",\n\"teeth\",\n\"teethe\",\n\"teethy\",\n\"teeting\",\n\"teety\",\n\"teevee\",\n\"teff\",\n\"teg\",\n\"tegmen\",\n\"tegmina\",\n\"tegua\",\n\"tegula\",\n\"tegular\",\n\"tegumen\",\n\"tehseel\",\n\"tehsil\",\n\"teicher\",\n\"teil\",\n\"teind\",\n\"teinder\",\n\"teioid\",\n\"tejon\",\n\"teju\",\n\"tekiah\",\n\"tekke\",\n\"tekken\",\n\"tektite\",\n\"tekya\",\n\"telamon\",\n\"telang\",\n\"telar\",\n\"telary\",\n\"tele\",\n\"teledu\",\n\"telega\",\n\"teleost\",\n\"teleran\",\n\"telergy\",\n\"telesia\",\n\"telesis\",\n\"teleuto\",\n\"televox\",\n\"telfer\",\n\"telford\",\n\"teli\",\n\"telial\",\n\"telic\",\n\"telical\",\n\"telium\",\n\"tell\",\n\"tellach\",\n\"tellee\",\n\"teller\",\n\"telling\",\n\"tellt\",\n\"telome\",\n\"telomic\",\n\"telpath\",\n\"telpher\",\n\"telson\",\n\"telt\",\n\"telurgy\",\n\"telyn\",\n\"temacha\",\n\"teman\",\n\"tembe\",\n\"temblor\",\n\"temenos\",\n\"temiak\",\n\"temin\",\n\"temp\",\n\"temper\",\n\"tempera\",\n\"tempery\",\n\"tempest\",\n\"tempi\",\n\"templar\",\n\"temple\",\n\"templed\",\n\"templet\",\n\"tempo\",\n\"tempora\",\n\"tempre\",\n\"tempt\",\n\"tempter\",\n\"temse\",\n\"temser\",\n\"ten\",\n\"tenable\",\n\"tenably\",\n\"tenace\",\n\"tenai\",\n\"tenancy\",\n\"tenant\",\n\"tench\",\n\"tend\",\n\"tendant\",\n\"tendent\",\n\"tender\",\n\"tending\",\n\"tendon\",\n\"tendour\",\n\"tendril\",\n\"tendron\",\n\"tenebra\",\n\"tenent\",\n\"teneral\",\n\"tenet\",\n\"tenfold\",\n\"teng\",\n\"tengere\",\n\"tengu\",\n\"tenible\",\n\"tenio\",\n\"tenline\",\n\"tenne\",\n\"tenner\",\n\"tennis\",\n\"tennisy\",\n\"tenon\",\n\"tenoner\",\n\"tenor\",\n\"tenpin\",\n\"tenrec\",\n\"tense\",\n\"tensely\",\n\"tensify\",\n\"tensile\",\n\"tension\",\n\"tensity\",\n\"tensive\",\n\"tenson\",\n\"tensor\",\n\"tent\",\n\"tentage\",\n\"tented\",\n\"tenter\",\n\"tentful\",\n\"tenth\",\n\"tenthly\",\n\"tentigo\",\n\"tention\",\n\"tentlet\",\n\"tenture\",\n\"tenty\",\n\"tenuate\",\n\"tenues\",\n\"tenuis\",\n\"tenuity\",\n\"tenuous\",\n\"tenure\",\n\"teopan\",\n\"tepache\",\n\"tepal\",\n\"tepee\",\n\"tepefy\",\n\"tepid\",\n\"tepidly\",\n\"tepor\",\n\"tequila\",\n\"tera\",\n\"terap\",\n\"teras\",\n\"terbia\",\n\"terbic\",\n\"terbium\",\n\"tercel\",\n\"tercer\",\n\"tercet\",\n\"tercia\",\n\"tercine\",\n\"tercio\",\n\"terebic\",\n\"terebra\",\n\"teredo\",\n\"terek\",\n\"terete\",\n\"tereu\",\n\"terfez\",\n\"tergal\",\n\"tergant\",\n\"tergite\",\n\"tergum\",\n\"term\",\n\"terma\",\n\"termage\",\n\"termen\",\n\"termer\",\n\"termin\",\n\"termine\",\n\"termini\",\n\"termino\",\n\"termite\",\n\"termly\",\n\"termon\",\n\"termor\",\n\"tern\",\n\"terna\",\n\"ternal\",\n\"ternar\",\n\"ternary\",\n\"ternate\",\n\"terne\",\n\"ternery\",\n\"ternion\",\n\"ternize\",\n\"ternlet\",\n\"terp\",\n\"terpane\",\n\"terpene\",\n\"terpin\",\n\"terpine\",\n\"terrace\",\n\"terrage\",\n\"terrain\",\n\"terral\",\n\"terrane\",\n\"terrar\",\n\"terrene\",\n\"terret\",\n\"terrier\",\n\"terrify\",\n\"terrine\",\n\"terron\",\n\"terror\",\n\"terry\",\n\"terse\",\n\"tersely\",\n\"tersion\",\n\"tertia\",\n\"tertial\",\n\"tertian\",\n\"tertius\",\n\"terton\",\n\"tervee\",\n\"terzina\",\n\"terzo\",\n\"tesack\",\n\"teskere\",\n\"tessara\",\n\"tessel\",\n\"tessera\",\n\"test\",\n\"testa\",\n\"testacy\",\n\"testar\",\n\"testata\",\n\"testate\",\n\"teste\",\n\"tested\",\n\"testee\",\n\"tester\",\n\"testes\",\n\"testify\",\n\"testily\",\n\"testing\",\n\"testis\",\n\"teston\",\n\"testone\",\n\"testoon\",\n\"testor\",\n\"testril\",\n\"testudo\",\n\"testy\",\n\"tetanic\",\n\"tetanus\",\n\"tetany\",\n\"tetard\",\n\"tetch\",\n\"tetchy\",\n\"tete\",\n\"tetel\",\n\"teth\",\n\"tether\",\n\"tethery\",\n\"tetra\",\n\"tetract\",\n\"tetrad\",\n\"tetrane\",\n\"tetrazo\",\n\"tetric\",\n\"tetrode\",\n\"tetrole\",\n\"tetrose\",\n\"tetryl\",\n\"tetter\",\n\"tettery\",\n\"tettix\",\n\"teucrin\",\n\"teufit\",\n\"teuk\",\n\"teviss\",\n\"tew\",\n\"tewel\",\n\"tewer\",\n\"tewit\",\n\"tewly\",\n\"tewsome\",\n\"text\",\n\"textile\",\n\"textlet\",\n\"textman\",\n\"textual\",\n\"texture\",\n\"tez\",\n\"tezkere\",\n\"th\",\n\"tha\",\n\"thack\",\n\"thacker\",\n\"thakur\",\n\"thalami\",\n\"thaler\",\n\"thalli\",\n\"thallic\",\n\"thallus\",\n\"thameng\",\n\"than\",\n\"thana\",\n\"thanage\",\n\"thanan\",\n\"thane\",\n\"thank\",\n\"thankee\",\n\"thanker\",\n\"thanks\",\n\"thapes\",\n\"thapsia\",\n\"thar\",\n\"tharf\",\n\"tharm\",\n\"that\",\n\"thatch\",\n\"thatchy\",\n\"thatn\",\n\"thats\",\n\"thaught\",\n\"thave\",\n\"thaw\",\n\"thawer\",\n\"thawn\",\n\"thawy\",\n\"the\",\n\"theah\",\n\"theasum\",\n\"theat\",\n\"theater\",\n\"theatry\",\n\"theave\",\n\"theb\",\n\"theca\",\n\"thecae\",\n\"thecal\",\n\"thecate\",\n\"thecia\",\n\"thecium\",\n\"thecla\",\n\"theclan\",\n\"thecoid\",\n\"thee\",\n\"theek\",\n\"theeker\",\n\"theelin\",\n\"theelol\",\n\"theer\",\n\"theet\",\n\"theezan\",\n\"theft\",\n\"thegn\",\n\"thegnly\",\n\"theine\",\n\"their\",\n\"theirn\",\n\"theirs\",\n\"theism\",\n\"theist\",\n\"thelium\",\n\"them\",\n\"thema\",\n\"themata\",\n\"theme\",\n\"themer\",\n\"themis\",\n\"themsel\",\n\"then\",\n\"thenal\",\n\"thenar\",\n\"thence\",\n\"theody\",\n\"theorbo\",\n\"theorem\",\n\"theoria\",\n\"theoric\",\n\"theorum\",\n\"theory\",\n\"theow\",\n\"therapy\",\n\"there\",\n\"thereas\",\n\"thereat\",\n\"thereby\",\n\"therein\",\n\"thereof\",\n\"thereon\",\n\"theres\",\n\"therese\",\n\"thereto\",\n\"thereup\",\n\"theriac\",\n\"therial\",\n\"therm\",\n\"thermae\",\n\"thermal\",\n\"thermic\",\n\"thermit\",\n\"thermo\",\n\"thermos\",\n\"theroid\",\n\"these\",\n\"theses\",\n\"thesial\",\n\"thesis\",\n\"theta\",\n\"thetch\",\n\"thetic\",\n\"thetics\",\n\"thetin\",\n\"thetine\",\n\"theurgy\",\n\"thew\",\n\"thewed\",\n\"thewy\",\n\"they\",\n\"theyll\",\n\"theyre\",\n\"thiamin\",\n\"thiasi\",\n\"thiasoi\",\n\"thiasos\",\n\"thiasus\",\n\"thick\",\n\"thicken\",\n\"thicket\",\n\"thickly\",\n\"thief\",\n\"thienyl\",\n\"thieve\",\n\"thiever\",\n\"thig\",\n\"thigger\",\n\"thigh\",\n\"thighed\",\n\"thight\",\n\"thilk\",\n\"thill\",\n\"thiller\",\n\"thilly\",\n\"thimber\",\n\"thimble\",\n\"thin\",\n\"thine\",\n\"thing\",\n\"thingal\",\n\"thingly\",\n\"thingum\",\n\"thingy\",\n\"think\",\n\"thinker\",\n\"thinly\",\n\"thinner\",\n\"thio\",\n\"thiol\",\n\"thiolic\",\n\"thionic\",\n\"thionyl\",\n\"thir\",\n\"third\",\n\"thirdly\",\n\"thirl\",\n\"thirst\",\n\"thirsty\",\n\"thirt\",\n\"thirty\",\n\"this\",\n\"thishow\",\n\"thisn\",\n\"thissen\",\n\"thistle\",\n\"thistly\",\n\"thither\",\n\"thiuram\",\n\"thivel\",\n\"thixle\",\n\"tho\",\n\"thob\",\n\"thocht\",\n\"thof\",\n\"thoft\",\n\"thoke\",\n\"thokish\",\n\"thole\",\n\"tholi\",\n\"tholoi\",\n\"tholos\",\n\"tholus\",\n\"thon\",\n\"thonder\",\n\"thone\",\n\"thong\",\n\"thonged\",\n\"thongy\",\n\"thoo\",\n\"thooid\",\n\"thoom\",\n\"thoral\",\n\"thorax\",\n\"thore\",\n\"thoria\",\n\"thoric\",\n\"thorina\",\n\"thorite\",\n\"thorium\",\n\"thorn\",\n\"thorned\",\n\"thornen\",\n\"thorny\",\n\"thoro\",\n\"thoron\",\n\"thorp\",\n\"thort\",\n\"thorter\",\n\"those\",\n\"thou\",\n\"though\",\n\"thought\",\n\"thouse\",\n\"thow\",\n\"thowel\",\n\"thowt\",\n\"thrack\",\n\"thraep\",\n\"thrail\",\n\"thrain\",\n\"thrall\",\n\"thram\",\n\"thrang\",\n\"thrap\",\n\"thrash\",\n\"thrast\",\n\"thrave\",\n\"thraver\",\n\"thraw\",\n\"thrawn\",\n\"thread\",\n\"thready\",\n\"threap\",\n\"threat\",\n\"three\",\n\"threne\",\n\"threnos\",\n\"threose\",\n\"thresh\",\n\"threw\",\n\"thrice\",\n\"thrift\",\n\"thrifty\",\n\"thrill\",\n\"thrilly\",\n\"thrimp\",\n\"thring\",\n\"thrip\",\n\"thripel\",\n\"thrips\",\n\"thrive\",\n\"thriven\",\n\"thriver\",\n\"thro\",\n\"throat\",\n\"throaty\",\n\"throb\",\n\"throck\",\n\"throddy\",\n\"throe\",\n\"thronal\",\n\"throne\",\n\"throng\",\n\"throu\",\n\"throuch\",\n\"through\",\n\"throve\",\n\"throw\",\n\"thrower\",\n\"thrown\",\n\"thrum\",\n\"thrummy\",\n\"thrush\",\n\"thrushy\",\n\"thrust\",\n\"thrutch\",\n\"thruv\",\n\"thrymsa\",\n\"thud\",\n\"thug\",\n\"thugdom\",\n\"thuggee\",\n\"thujene\",\n\"thujin\",\n\"thujone\",\n\"thujyl\",\n\"thulia\",\n\"thulir\",\n\"thulite\",\n\"thulium\",\n\"thulr\",\n\"thuluth\",\n\"thumb\",\n\"thumbed\",\n\"thumber\",\n\"thumble\",\n\"thumby\",\n\"thump\",\n\"thumper\",\n\"thunder\",\n\"thung\",\n\"thunge\",\n\"thuoc\",\n\"thurify\",\n\"thurl\",\n\"thurm\",\n\"thurmus\",\n\"thurse\",\n\"thurt\",\n\"thus\",\n\"thusly\",\n\"thutter\",\n\"thwack\",\n\"thwaite\",\n\"thwart\",\n\"thwite\",\n\"thy\",\n\"thyine\",\n\"thymate\",\n\"thyme\",\n\"thymele\",\n\"thymene\",\n\"thymic\",\n\"thymine\",\n\"thymol\",\n\"thymoma\",\n\"thymus\",\n\"thymy\",\n\"thymyl\",\n\"thynnid\",\n\"thyroid\",\n\"thyrse\",\n\"thyrsus\",\n\"thysel\",\n\"thyself\",\n\"thysen\",\n\"ti\",\n\"tiang\",\n\"tiao\",\n\"tiar\",\n\"tiara\",\n\"tib\",\n\"tibby\",\n\"tibet\",\n\"tibey\",\n\"tibia\",\n\"tibiad\",\n\"tibiae\",\n\"tibial\",\n\"tibiale\",\n\"tiburon\",\n\"tic\",\n\"tical\",\n\"ticca\",\n\"tice\",\n\"ticer\",\n\"tick\",\n\"ticked\",\n\"ticken\",\n\"ticker\",\n\"ticket\",\n\"tickey\",\n\"tickie\",\n\"ticking\",\n\"tickle\",\n\"tickled\",\n\"tickler\",\n\"tickly\",\n\"tickney\",\n\"ticky\",\n\"ticul\",\n\"tid\",\n\"tidal\",\n\"tidally\",\n\"tidbit\",\n\"tiddle\",\n\"tiddler\",\n\"tiddley\",\n\"tiddy\",\n\"tide\",\n\"tided\",\n\"tideful\",\n\"tidely\",\n\"tideway\",\n\"tidily\",\n\"tiding\",\n\"tidings\",\n\"tidley\",\n\"tidy\",\n\"tidyism\",\n\"tie\",\n\"tieback\",\n\"tied\",\n\"tien\",\n\"tiepin\",\n\"tier\",\n\"tierce\",\n\"tierced\",\n\"tiered\",\n\"tierer\",\n\"tietick\",\n\"tiewig\",\n\"tiff\",\n\"tiffany\",\n\"tiffie\",\n\"tiffin\",\n\"tiffish\",\n\"tiffle\",\n\"tiffy\",\n\"tift\",\n\"tifter\",\n\"tig\",\n\"tige\",\n\"tigella\",\n\"tigelle\",\n\"tiger\",\n\"tigerly\",\n\"tigery\",\n\"tigger\",\n\"tight\",\n\"tighten\",\n\"tightly\",\n\"tights\",\n\"tiglic\",\n\"tignum\",\n\"tigress\",\n\"tigrine\",\n\"tigroid\",\n\"tigtag\",\n\"tikka\",\n\"tikker\",\n\"tiklin\",\n\"tikor\",\n\"tikur\",\n\"til\",\n\"tilaite\",\n\"tilaka\",\n\"tilbury\",\n\"tilde\",\n\"tile\",\n\"tiled\",\n\"tiler\",\n\"tilery\",\n\"tilikum\",\n\"tiling\",\n\"till\",\n\"tillage\",\n\"tiller\",\n\"tilley\",\n\"tillite\",\n\"tillot\",\n\"tilly\",\n\"tilmus\",\n\"tilpah\",\n\"tilt\",\n\"tilter\",\n\"tilth\",\n\"tilting\",\n\"tiltup\",\n\"tilty\",\n\"tilyer\",\n\"timable\",\n\"timar\",\n\"timarau\",\n\"timawa\",\n\"timbal\",\n\"timbale\",\n\"timbang\",\n\"timbe\",\n\"timber\",\n\"timbern\",\n\"timbery\",\n\"timbo\",\n\"timbre\",\n\"timbrel\",\n\"time\",\n\"timed\",\n\"timeful\",\n\"timely\",\n\"timeous\",\n\"timer\",\n\"times\",\n\"timid\",\n\"timidly\",\n\"timing\",\n\"timish\",\n\"timist\",\n\"timon\",\n\"timor\",\n\"timothy\",\n\"timpani\",\n\"timpano\",\n\"tin\",\n\"tinamou\",\n\"tincal\",\n\"tinchel\",\n\"tinclad\",\n\"tinct\",\n\"tind\",\n\"tindal\",\n\"tindalo\",\n\"tinder\",\n\"tindery\",\n\"tine\",\n\"tinea\",\n\"tineal\",\n\"tinean\",\n\"tined\",\n\"tineid\",\n\"tineine\",\n\"tineman\",\n\"tineoid\",\n\"tinety\",\n\"tinful\",\n\"ting\",\n\"tinge\",\n\"tinged\",\n\"tinger\",\n\"tingi\",\n\"tingid\",\n\"tingle\",\n\"tingler\",\n\"tingly\",\n\"tinguy\",\n\"tinhorn\",\n\"tinily\",\n\"tining\",\n\"tink\",\n\"tinker\",\n\"tinkle\",\n\"tinkler\",\n\"tinkly\",\n\"tinlet\",\n\"tinlike\",\n\"tinman\",\n\"tinned\",\n\"tinner\",\n\"tinnery\",\n\"tinnet\",\n\"tinnily\",\n\"tinning\",\n\"tinnock\",\n\"tinny\",\n\"tinosa\",\n\"tinsel\",\n\"tinsman\",\n\"tint\",\n\"tinta\",\n\"tintage\",\n\"tinted\",\n\"tinter\",\n\"tintie\",\n\"tinting\",\n\"tintist\",\n\"tinty\",\n\"tintype\",\n\"tinwald\",\n\"tinware\",\n\"tinwork\",\n\"tiny\",\n\"tip\",\n\"tipburn\",\n\"tipcart\",\n\"tipcat\",\n\"tipe\",\n\"tipful\",\n\"tiphead\",\n\"tipiti\",\n\"tiple\",\n\"tipless\",\n\"tiplet\",\n\"tipman\",\n\"tipmost\",\n\"tiponi\",\n\"tipped\",\n\"tippee\",\n\"tipper\",\n\"tippet\",\n\"tipping\",\n\"tipple\",\n\"tippler\",\n\"tipply\",\n\"tippy\",\n\"tipsify\",\n\"tipsily\",\n\"tipster\",\n\"tipsy\",\n\"tiptail\",\n\"tiptilt\",\n\"tiptoe\",\n\"tiptop\",\n\"tipulid\",\n\"tipup\",\n\"tirade\",\n\"tiralee\",\n\"tire\",\n\"tired\",\n\"tiredly\",\n\"tiredom\",\n\"tireman\",\n\"tirer\",\n\"tiriba\",\n\"tiring\",\n\"tirl\",\n\"tirma\",\n\"tirr\",\n\"tirret\",\n\"tirrlie\",\n\"tirve\",\n\"tirwit\",\n\"tisane\",\n\"tisar\",\n\"tissual\",\n\"tissue\",\n\"tissued\",\n\"tissuey\",\n\"tiswin\",\n\"tit\",\n\"titania\",\n\"titanic\",\n\"titano\",\n\"titanyl\",\n\"titar\",\n\"titbit\",\n\"tite\",\n\"titer\",\n\"titfish\",\n\"tithal\",\n\"tithe\",\n\"tither\",\n\"tithing\",\n\"titi\",\n\"titian\",\n\"titien\",\n\"titlark\",\n\"title\",\n\"titled\",\n\"titler\",\n\"titlike\",\n\"titling\",\n\"titlist\",\n\"titmal\",\n\"titman\",\n\"titoki\",\n\"titrate\",\n\"titre\",\n\"titter\",\n\"tittery\",\n\"tittie\",\n\"tittle\",\n\"tittler\",\n\"tittup\",\n\"tittupy\",\n\"titty\",\n\"titular\",\n\"titule\",\n\"titulus\",\n\"tiver\",\n\"tivoli\",\n\"tivy\",\n\"tiza\",\n\"tizeur\",\n\"tizzy\",\n\"tji\",\n\"tjosite\",\n\"tlaco\",\n\"tmema\",\n\"tmesis\",\n\"to\",\n\"toa\",\n\"toad\",\n\"toadeat\",\n\"toader\",\n\"toadery\",\n\"toadess\",\n\"toadier\",\n\"toadish\",\n\"toadlet\",\n\"toady\",\n\"toast\",\n\"toastee\",\n\"toaster\",\n\"toasty\",\n\"toat\",\n\"toatoa\",\n\"tobacco\",\n\"tobe\",\n\"tobine\",\n\"tobira\",\n\"toby\",\n\"tobyman\",\n\"toccata\",\n\"tocher\",\n\"tock\",\n\"toco\",\n\"tocome\",\n\"tocsin\",\n\"tocusso\",\n\"tod\",\n\"today\",\n\"todder\",\n\"toddick\",\n\"toddite\",\n\"toddle\",\n\"toddler\",\n\"toddy\",\n\"tode\",\n\"tody\",\n\"toe\",\n\"toecap\",\n\"toed\",\n\"toeless\",\n\"toelike\",\n\"toenail\",\n\"toetoe\",\n\"toff\",\n\"toffee\",\n\"toffing\",\n\"toffish\",\n\"toffy\",\n\"toft\",\n\"tofter\",\n\"toftman\",\n\"tofu\",\n\"tog\",\n\"toga\",\n\"togaed\",\n\"togata\",\n\"togate\",\n\"togated\",\n\"toggel\",\n\"toggery\",\n\"toggle\",\n\"toggler\",\n\"togless\",\n\"togs\",\n\"togt\",\n\"togue\",\n\"toher\",\n\"toheroa\",\n\"toho\",\n\"tohunga\",\n\"toi\",\n\"toil\",\n\"toiled\",\n\"toiler\",\n\"toilet\",\n\"toilful\",\n\"toiling\",\n\"toise\",\n\"toit\",\n\"toitish\",\n\"toity\",\n\"tokay\",\n\"toke\",\n\"token\",\n\"tokened\",\n\"toko\",\n\"tokopat\",\n\"tol\",\n\"tolan\",\n\"tolane\",\n\"told\",\n\"toldo\",\n\"tole\",\n\"tolite\",\n\"toll\",\n\"tollage\",\n\"toller\",\n\"tollery\",\n\"tolling\",\n\"tollman\",\n\"tolly\",\n\"tolsey\",\n\"tolt\",\n\"tolter\",\n\"tolu\",\n\"toluate\",\n\"toluene\",\n\"toluic\",\n\"toluide\",\n\"toluido\",\n\"toluol\",\n\"toluyl\",\n\"tolyl\",\n\"toman\",\n\"tomato\",\n\"tomb\",\n\"tombac\",\n\"tombal\",\n\"tombe\",\n\"tombic\",\n\"tomblet\",\n\"tombola\",\n\"tombolo\",\n\"tomboy\",\n\"tomcat\",\n\"tomcod\",\n\"tome\",\n\"tomeful\",\n\"tomelet\",\n\"toment\",\n\"tomfool\",\n\"tomial\",\n\"tomin\",\n\"tomish\",\n\"tomium\",\n\"tomjohn\",\n\"tomkin\",\n\"tommy\",\n\"tomnoup\",\n\"tomorn\",\n\"tomosis\",\n\"tompon\",\n\"tomtate\",\n\"tomtit\",\n\"ton\",\n\"tonal\",\n\"tonally\",\n\"tonant\",\n\"tondino\",\n\"tone\",\n\"toned\",\n\"toneme\",\n\"toner\",\n\"tonetic\",\n\"tong\",\n\"tonga\",\n\"tonger\",\n\"tongman\",\n\"tongs\",\n\"tongue\",\n\"tongued\",\n\"tonguer\",\n\"tonguey\",\n\"tonic\",\n\"tonify\",\n\"tonight\",\n\"tonish\",\n\"tonite\",\n\"tonjon\",\n\"tonk\",\n\"tonkin\",\n\"tonlet\",\n\"tonnage\",\n\"tonneau\",\n\"tonner\",\n\"tonnish\",\n\"tonous\",\n\"tonsil\",\n\"tonsor\",\n\"tonsure\",\n\"tontine\",\n\"tonus\",\n\"tony\",\n\"too\",\n\"toodle\",\n\"took\",\n\"tooken\",\n\"tool\",\n\"toolbox\",\n\"tooler\",\n\"tooling\",\n\"toolman\",\n\"toom\",\n\"toomly\",\n\"toon\",\n\"toop\",\n\"toorie\",\n\"toorock\",\n\"tooroo\",\n\"toosh\",\n\"toot\",\n\"tooter\",\n\"tooth\",\n\"toothed\",\n\"toother\",\n\"toothy\",\n\"tootle\",\n\"tootler\",\n\"tootsy\",\n\"toozle\",\n\"toozoo\",\n\"top\",\n\"toparch\",\n\"topass\",\n\"topaz\",\n\"topazy\",\n\"topcap\",\n\"topcast\",\n\"topcoat\",\n\"tope\",\n\"topee\",\n\"topeng\",\n\"topepo\",\n\"toper\",\n\"topfull\",\n\"toph\",\n\"tophus\",\n\"topi\",\n\"topia\",\n\"topiary\",\n\"topic\",\n\"topical\",\n\"topknot\",\n\"topless\",\n\"toplike\",\n\"topline\",\n\"topman\",\n\"topmast\",\n\"topmost\",\n\"topo\",\n\"toponym\",\n\"topped\",\n\"topper\",\n\"topping\",\n\"topple\",\n\"toppler\",\n\"topply\",\n\"toppy\",\n\"toprail\",\n\"toprope\",\n\"tops\",\n\"topsail\",\n\"topside\",\n\"topsl\",\n\"topsman\",\n\"topsoil\",\n\"toptail\",\n\"topwise\",\n\"toque\",\n\"tor\",\n\"tora\",\n\"torah\",\n\"toral\",\n\"toran\",\n\"torc\",\n\"torcel\",\n\"torch\",\n\"torcher\",\n\"torchon\",\n\"tore\",\n\"tored\",\n\"torero\",\n\"torfel\",\n\"torgoch\",\n\"toric\",\n\"torii\",\n\"torma\",\n\"tormen\",\n\"torment\",\n\"tormina\",\n\"torn\",\n\"tornade\",\n\"tornado\",\n\"tornal\",\n\"tornese\",\n\"torney\",\n\"tornote\",\n\"tornus\",\n\"toro\",\n\"toroid\",\n\"torose\",\n\"torous\",\n\"torpedo\",\n\"torpent\",\n\"torpid\",\n\"torpify\",\n\"torpor\",\n\"torque\",\n\"torqued\",\n\"torques\",\n\"torrefy\",\n\"torrent\",\n\"torrid\",\n\"torsade\",\n\"torse\",\n\"torsel\",\n\"torsile\",\n\"torsion\",\n\"torsive\",\n\"torsk\",\n\"torso\",\n\"tort\",\n\"torta\",\n\"torteau\",\n\"tortile\",\n\"tortive\",\n\"tortula\",\n\"torture\",\n\"toru\",\n\"torula\",\n\"torulin\",\n\"torulus\",\n\"torus\",\n\"torve\",\n\"torvid\",\n\"torvity\",\n\"torvous\",\n\"tory\",\n\"tosh\",\n\"tosher\",\n\"toshery\",\n\"toshly\",\n\"toshy\",\n\"tosily\",\n\"toss\",\n\"tosser\",\n\"tossily\",\n\"tossing\",\n\"tosspot\",\n\"tossup\",\n\"tossy\",\n\"tost\",\n\"toston\",\n\"tosy\",\n\"tot\",\n\"total\",\n\"totally\",\n\"totara\",\n\"totchka\",\n\"tote\",\n\"totem\",\n\"totemic\",\n\"totemy\",\n\"toter\",\n\"tother\",\n\"totient\",\n\"toto\",\n\"totora\",\n\"totquot\",\n\"totter\",\n\"tottery\",\n\"totting\",\n\"tottle\",\n\"totty\",\n\"totuava\",\n\"totum\",\n\"toty\",\n\"totyman\",\n\"tou\",\n\"toucan\",\n\"touch\",\n\"touched\",\n\"toucher\",\n\"touchy\",\n\"toug\",\n\"tough\",\n\"toughen\",\n\"toughly\",\n\"tought\",\n\"tould\",\n\"toumnah\",\n\"toup\",\n\"toupee\",\n\"toupeed\",\n\"toupet\",\n\"tour\",\n\"touraco\",\n\"tourer\",\n\"touring\",\n\"tourism\",\n\"tourist\",\n\"tourize\",\n\"tourn\",\n\"tournay\",\n\"tournee\",\n\"tourney\",\n\"tourte\",\n\"tousche\",\n\"touse\",\n\"touser\",\n\"tousle\",\n\"tously\",\n\"tousy\",\n\"tout\",\n\"touter\",\n\"tovar\",\n\"tow\",\n\"towable\",\n\"towage\",\n\"towai\",\n\"towan\",\n\"toward\",\n\"towards\",\n\"towboat\",\n\"towcock\",\n\"towd\",\n\"towel\",\n\"towelry\",\n\"tower\",\n\"towered\",\n\"towery\",\n\"towght\",\n\"towhead\",\n\"towhee\",\n\"towing\",\n\"towkay\",\n\"towlike\",\n\"towline\",\n\"towmast\",\n\"town\",\n\"towned\",\n\"townee\",\n\"towner\",\n\"townet\",\n\"townful\",\n\"townify\",\n\"townish\",\n\"townist\",\n\"townlet\",\n\"townly\",\n\"townman\",\n\"towny\",\n\"towpath\",\n\"towrope\",\n\"towser\",\n\"towy\",\n\"tox\",\n\"toxa\",\n\"toxamin\",\n\"toxcatl\",\n\"toxemia\",\n\"toxemic\",\n\"toxic\",\n\"toxical\",\n\"toxicum\",\n\"toxifer\",\n\"toxin\",\n\"toxity\",\n\"toxoid\",\n\"toxon\",\n\"toxone\",\n\"toxosis\",\n\"toxotae\",\n\"toy\",\n\"toydom\",\n\"toyer\",\n\"toyful\",\n\"toying\",\n\"toyish\",\n\"toyland\",\n\"toyless\",\n\"toylike\",\n\"toyman\",\n\"toyon\",\n\"toyshop\",\n\"toysome\",\n\"toytown\",\n\"toywort\",\n\"toze\",\n\"tozee\",\n\"tozer\",\n\"tra\",\n\"trabal\",\n\"trabant\",\n\"trabea\",\n\"trabeae\",\n\"trabuch\",\n\"trace\",\n\"tracer\",\n\"tracery\",\n\"trachea\",\n\"trachle\",\n\"tracing\",\n\"track\",\n\"tracked\",\n\"tracker\",\n\"tract\",\n\"tractor\",\n\"tradal\",\n\"trade\",\n\"trader\",\n\"trading\",\n\"tradite\",\n\"traduce\",\n\"trady\",\n\"traffic\",\n\"trag\",\n\"tragal\",\n\"tragedy\",\n\"tragi\",\n\"tragic\",\n\"tragus\",\n\"trah\",\n\"traheen\",\n\"traik\",\n\"trail\",\n\"trailer\",\n\"traily\",\n\"train\",\n\"trained\",\n\"trainee\",\n\"trainer\",\n\"trainy\",\n\"traipse\",\n\"trait\",\n\"traitor\",\n\"traject\",\n\"trajet\",\n\"tralira\",\n\"tram\",\n\"trama\",\n\"tramal\",\n\"tramcar\",\n\"trame\",\n\"tramful\",\n\"tramman\",\n\"trammel\",\n\"trammer\",\n\"trammon\",\n\"tramp\",\n\"tramper\",\n\"trample\",\n\"trampot\",\n\"tramway\",\n\"trance\",\n\"tranced\",\n\"traneen\",\n\"trank\",\n\"tranka\",\n\"tranker\",\n\"trankum\",\n\"tranky\",\n\"transit\",\n\"transom\",\n\"trant\",\n\"tranter\",\n\"trap\",\n\"trapes\",\n\"trapeze\",\n\"trapped\",\n\"trapper\",\n\"trappy\",\n\"traps\",\n\"trash\",\n\"traship\",\n\"trashy\",\n\"trass\",\n\"trasy\",\n\"trauma\",\n\"travail\",\n\"travale\",\n\"trave\",\n\"travel\",\n\"travis\",\n\"travois\",\n\"travoy\",\n\"trawl\",\n\"trawler\",\n\"tray\",\n\"trayful\",\n\"treacle\",\n\"treacly\",\n\"tread\",\n\"treader\",\n\"treadle\",\n\"treason\",\n\"treat\",\n\"treatee\",\n\"treater\",\n\"treator\",\n\"treaty\",\n\"treble\",\n\"trebly\",\n\"treddle\",\n\"tree\",\n\"treed\",\n\"treeful\",\n\"treeify\",\n\"treelet\",\n\"treeman\",\n\"treen\",\n\"treetop\",\n\"treey\",\n\"tref\",\n\"trefle\",\n\"trefoil\",\n\"tregerg\",\n\"tregohm\",\n\"trehala\",\n\"trek\",\n\"trekker\",\n\"trellis\",\n\"tremble\",\n\"trembly\",\n\"tremie\",\n\"tremolo\",\n\"tremor\",\n\"trenail\",\n\"trench\",\n\"trend\",\n\"trendle\",\n\"trental\",\n\"trepan\",\n\"trepang\",\n\"trepid\",\n\"tress\",\n\"tressed\",\n\"tresson\",\n\"tressy\",\n\"trest\",\n\"trestle\",\n\"tret\",\n\"trevet\",\n\"trews\",\n\"trey\",\n\"tri\",\n\"triable\",\n\"triace\",\n\"triacid\",\n\"triact\",\n\"triad\",\n\"triadic\",\n\"triaene\",\n\"triage\",\n\"trial\",\n\"triamid\",\n\"triarch\",\n\"triarii\",\n\"triatic\",\n\"triaxon\",\n\"triazin\",\n\"triazo\",\n\"tribade\",\n\"tribady\",\n\"tribal\",\n\"tribase\",\n\"tribble\",\n\"tribe\",\n\"triblet\",\n\"tribrac\",\n\"tribual\",\n\"tribuna\",\n\"tribune\",\n\"tribute\",\n\"trica\",\n\"tricae\",\n\"tricar\",\n\"trice\",\n\"triceps\",\n\"trichi\",\n\"trichia\",\n\"trichy\",\n\"trick\",\n\"tricker\",\n\"trickle\",\n\"trickly\",\n\"tricksy\",\n\"tricky\",\n\"triclad\",\n\"tricorn\",\n\"tricot\",\n\"trident\",\n\"triduan\",\n\"triduum\",\n\"tried\",\n\"triedly\",\n\"triene\",\n\"triens\",\n\"trier\",\n\"trifa\",\n\"trifid\",\n\"trifle\",\n\"trifler\",\n\"triflet\",\n\"trifoil\",\n\"trifold\",\n\"trifoly\",\n\"triform\",\n\"trig\",\n\"trigamy\",\n\"trigger\",\n\"triglid\",\n\"triglot\",\n\"trigly\",\n\"trigon\",\n\"trigone\",\n\"trigram\",\n\"trigyn\",\n\"trikaya\",\n\"trike\",\n\"triker\",\n\"triketo\",\n\"trikir\",\n\"trilabe\",\n\"trilby\",\n\"trilit\",\n\"trilite\",\n\"trilith\",\n\"trill\",\n\"trillet\",\n\"trilli\",\n\"trillo\",\n\"trilobe\",\n\"trilogy\",\n\"trim\",\n\"trimer\",\n\"trimly\",\n\"trimmer\",\n\"trin\",\n\"trinal\",\n\"trinary\",\n\"trindle\",\n\"trine\",\n\"trinely\",\n\"tringle\",\n\"trinity\",\n\"trink\",\n\"trinket\",\n\"trinkle\",\n\"trinode\",\n\"trinol\",\n\"trintle\",\n\"trio\",\n\"triobol\",\n\"triode\",\n\"triodia\",\n\"triole\",\n\"triolet\",\n\"trionym\",\n\"trior\",\n\"triose\",\n\"trip\",\n\"tripal\",\n\"tripara\",\n\"tripart\",\n\"tripe\",\n\"tripel\",\n\"tripery\",\n\"triple\",\n\"triplet\",\n\"triplex\",\n\"triplum\",\n\"triply\",\n\"tripod\",\n\"tripody\",\n\"tripoli\",\n\"tripos\",\n\"tripper\",\n\"trippet\",\n\"tripple\",\n\"tripsis\",\n\"tripy\",\n\"trireme\",\n\"trisalt\",\n\"trisazo\",\n\"trisect\",\n\"triseme\",\n\"trishna\",\n\"trismic\",\n\"trismus\",\n\"trisome\",\n\"trisomy\",\n\"trist\",\n\"trisul\",\n\"trisula\",\n\"tritaph\",\n\"trite\",\n\"tritely\",\n\"tritish\",\n\"tritium\",\n\"tritolo\",\n\"triton\",\n\"tritone\",\n\"tritor\",\n\"trityl\",\n\"triumph\",\n\"triunal\",\n\"triune\",\n\"triurid\",\n\"trivant\",\n\"trivet\",\n\"trivia\",\n\"trivial\",\n\"trivium\",\n\"trivvet\",\n\"trizoic\",\n\"trizone\",\n\"troat\",\n\"troca\",\n\"trocar\",\n\"trochal\",\n\"troche\",\n\"trochee\",\n\"trochi\",\n\"trochid\",\n\"trochus\",\n\"trock\",\n\"troco\",\n\"trod\",\n\"trodden\",\n\"trode\",\n\"troft\",\n\"trog\",\n\"trogger\",\n\"troggin\",\n\"trogon\",\n\"trogs\",\n\"trogue\",\n\"troika\",\n\"troke\",\n\"troker\",\n\"troll\",\n\"troller\",\n\"trolley\",\n\"trollol\",\n\"trollop\",\n\"trolly\",\n\"tromba\",\n\"trombe\",\n\"trommel\",\n\"tromp\",\n\"trompe\",\n\"trompil\",\n\"tromple\",\n\"tron\",\n\"trona\",\n\"tronage\",\n\"tronc\",\n\"trone\",\n\"troner\",\n\"troolie\",\n\"troop\",\n\"trooper\",\n\"troot\",\n\"tropal\",\n\"tropary\",\n\"tropate\",\n\"trope\",\n\"tropeic\",\n\"troper\",\n\"trophal\",\n\"trophi\",\n\"trophic\",\n\"trophy\",\n\"tropic\",\n\"tropine\",\n\"tropism\",\n\"tropist\",\n\"tropoyl\",\n\"tropyl\",\n\"trot\",\n\"troth\",\n\"trotlet\",\n\"trotol\",\n\"trotter\",\n\"trottie\",\n\"trotty\",\n\"trotyl\",\n\"trouble\",\n\"troubly\",\n\"trough\",\n\"troughy\",\n\"trounce\",\n\"troupe\",\n\"trouper\",\n\"trouse\",\n\"trouser\",\n\"trout\",\n\"trouter\",\n\"trouty\",\n\"trove\",\n\"trover\",\n\"trow\",\n\"trowel\",\n\"trowing\",\n\"trowman\",\n\"trowth\",\n\"troy\",\n\"truancy\",\n\"truant\",\n\"trub\",\n\"trubu\",\n\"truce\",\n\"trucial\",\n\"truck\",\n\"trucker\",\n\"truckle\",\n\"trucks\",\n\"truddo\",\n\"trudge\",\n\"trudgen\",\n\"trudger\",\n\"true\",\n\"truer\",\n\"truff\",\n\"truffle\",\n\"trug\",\n\"truish\",\n\"truism\",\n\"trull\",\n\"truller\",\n\"trullo\",\n\"truly\",\n\"trummel\",\n\"trump\",\n\"trumper\",\n\"trumpet\",\n\"trumph\",\n\"trumpie\",\n\"trun\",\n\"truncal\",\n\"trunch\",\n\"trundle\",\n\"trunk\",\n\"trunked\",\n\"trunnel\",\n\"trush\",\n\"trusion\",\n\"truss\",\n\"trussed\",\n\"trusser\",\n\"trust\",\n\"trustee\",\n\"trusten\",\n\"truster\",\n\"trustle\",\n\"trusty\",\n\"truth\",\n\"truthy\",\n\"truvat\",\n\"try\",\n\"trygon\",\n\"trying\",\n\"tryma\",\n\"tryout\",\n\"tryp\",\n\"trypa\",\n\"trypan\",\n\"trypsin\",\n\"tryptic\",\n\"trysail\",\n\"tryst\",\n\"tryster\",\n\"tryt\",\n\"tsadik\",\n\"tsamba\",\n\"tsantsa\",\n\"tsar\",\n\"tsardom\",\n\"tsarina\",\n\"tsatlee\",\n\"tsere\",\n\"tsetse\",\n\"tsia\",\n\"tsine\",\n\"tst\",\n\"tsuba\",\n\"tsubo\",\n\"tsun\",\n\"tsunami\",\n\"tsungtu\",\n\"tu\",\n\"tua\",\n\"tuan\",\n\"tuarn\",\n\"tuart\",\n\"tuatara\",\n\"tuatera\",\n\"tuath\",\n\"tub\",\n\"tuba\",\n\"tubae\",\n\"tubage\",\n\"tubal\",\n\"tubar\",\n\"tubate\",\n\"tubba\",\n\"tubbal\",\n\"tubbeck\",\n\"tubber\",\n\"tubbie\",\n\"tubbing\",\n\"tubbish\",\n\"tubboe\",\n\"tubby\",\n\"tube\",\n\"tubeful\",\n\"tubelet\",\n\"tubeman\",\n\"tuber\",\n\"tuberin\",\n\"tubfish\",\n\"tubful\",\n\"tubicen\",\n\"tubifer\",\n\"tubig\",\n\"tubik\",\n\"tubing\",\n\"tublet\",\n\"tublike\",\n\"tubman\",\n\"tubular\",\n\"tubule\",\n\"tubulet\",\n\"tubuli\",\n\"tubulus\",\n\"tuchit\",\n\"tuchun\",\n\"tuck\",\n\"tucker\",\n\"tucket\",\n\"tucking\",\n\"tuckner\",\n\"tucktoo\",\n\"tucky\",\n\"tucum\",\n\"tucuma\",\n\"tucuman\",\n\"tudel\",\n\"tue\",\n\"tueiron\",\n\"tufa\",\n\"tufan\",\n\"tuff\",\n\"tuffet\",\n\"tuffing\",\n\"tuft\",\n\"tufted\",\n\"tufter\",\n\"tuftily\",\n\"tufting\",\n\"tuftlet\",\n\"tufty\",\n\"tug\",\n\"tugboat\",\n\"tugger\",\n\"tuggery\",\n\"tugging\",\n\"tughra\",\n\"tugless\",\n\"tuglike\",\n\"tugman\",\n\"tugrik\",\n\"tugui\",\n\"tui\",\n\"tuik\",\n\"tuille\",\n\"tuilyie\",\n\"tuism\",\n\"tuition\",\n\"tuitive\",\n\"tuke\",\n\"tukra\",\n\"tula\",\n\"tulare\",\n\"tulasi\",\n\"tulchan\",\n\"tulchin\",\n\"tule\",\n\"tuliac\",\n\"tulip\",\n\"tulipy\",\n\"tulisan\",\n\"tulle\",\n\"tulsi\",\n\"tulwar\",\n\"tum\",\n\"tumasha\",\n\"tumbak\",\n\"tumble\",\n\"tumbled\",\n\"tumbler\",\n\"tumbly\",\n\"tumbrel\",\n\"tume\",\n\"tumefy\",\n\"tumid\",\n\"tumidly\",\n\"tummals\",\n\"tummel\",\n\"tummer\",\n\"tummock\",\n\"tummy\",\n\"tumor\",\n\"tumored\",\n\"tump\",\n\"tumtum\",\n\"tumular\",\n\"tumuli\",\n\"tumult\",\n\"tumulus\",\n\"tun\",\n\"tuna\",\n\"tunable\",\n\"tunably\",\n\"tunca\",\n\"tund\",\n\"tunder\",\n\"tundish\",\n\"tundra\",\n\"tundun\",\n\"tune\",\n\"tuned\",\n\"tuneful\",\n\"tuner\",\n\"tunful\",\n\"tung\",\n\"tungate\",\n\"tungo\",\n\"tunhoof\",\n\"tunic\",\n\"tunicin\",\n\"tunicle\",\n\"tuning\",\n\"tunish\",\n\"tunist\",\n\"tunk\",\n\"tunket\",\n\"tunlike\",\n\"tunmoot\",\n\"tunna\",\n\"tunnel\",\n\"tunner\",\n\"tunnery\",\n\"tunnor\",\n\"tunny\",\n\"tuno\",\n\"tunu\",\n\"tuny\",\n\"tup\",\n\"tupara\",\n\"tupek\",\n\"tupelo\",\n\"tupik\",\n\"tupman\",\n\"tupuna\",\n\"tuque\",\n\"tur\",\n\"turacin\",\n\"turb\",\n\"turban\",\n\"turbary\",\n\"turbeh\",\n\"turbid\",\n\"turbine\",\n\"turbit\",\n\"turbith\",\n\"turbo\",\n\"turbot\",\n\"turco\",\n\"turd\",\n\"turdine\",\n\"turdoid\",\n\"tureen\",\n\"turf\",\n\"turfage\",\n\"turfdom\",\n\"turfed\",\n\"turfen\",\n\"turfing\",\n\"turfite\",\n\"turfman\",\n\"turfy\",\n\"turgent\",\n\"turgid\",\n\"turgite\",\n\"turgoid\",\n\"turgor\",\n\"turgy\",\n\"turio\",\n\"turion\",\n\"turjite\",\n\"turk\",\n\"turken\",\n\"turkey\",\n\"turkis\",\n\"turkle\",\n\"turm\",\n\"turma\",\n\"turment\",\n\"turmit\",\n\"turmoil\",\n\"turn\",\n\"turncap\",\n\"turndun\",\n\"turned\",\n\"turnel\",\n\"turner\",\n\"turnery\",\n\"turney\",\n\"turning\",\n\"turnip\",\n\"turnipy\",\n\"turnix\",\n\"turnkey\",\n\"turnoff\",\n\"turnout\",\n\"turnpin\",\n\"turnrow\",\n\"turns\",\n\"turnup\",\n\"turp\",\n\"turpeth\",\n\"turpid\",\n\"turps\",\n\"turr\",\n\"turret\",\n\"turse\",\n\"tursio\",\n\"turtle\",\n\"turtler\",\n\"turtlet\",\n\"turtosa\",\n\"tururi\",\n\"turus\",\n\"turwar\",\n\"tusche\",\n\"tush\",\n\"tushed\",\n\"tusher\",\n\"tushery\",\n\"tusk\",\n\"tuskar\",\n\"tusked\",\n\"tusker\",\n\"tuskish\",\n\"tusky\",\n\"tussah\",\n\"tussal\",\n\"tusser\",\n\"tussis\",\n\"tussive\",\n\"tussle\",\n\"tussock\",\n\"tussore\",\n\"tussur\",\n\"tut\",\n\"tutania\",\n\"tutball\",\n\"tute\",\n\"tutee\",\n\"tutela\",\n\"tutelar\",\n\"tutenag\",\n\"tuth\",\n\"tutin\",\n\"tutly\",\n\"tutman\",\n\"tutor\",\n\"tutorer\",\n\"tutorly\",\n\"tutory\",\n\"tutoyer\",\n\"tutress\",\n\"tutrice\",\n\"tutrix\",\n\"tuts\",\n\"tutsan\",\n\"tutster\",\n\"tutti\",\n\"tutty\",\n\"tutu\",\n\"tutulus\",\n\"tutwork\",\n\"tuwi\",\n\"tux\",\n\"tuxedo\",\n\"tuyere\",\n\"tuza\",\n\"tuzzle\",\n\"twa\",\n\"twaddle\",\n\"twaddly\",\n\"twaddy\",\n\"twae\",\n\"twagger\",\n\"twain\",\n\"twaite\",\n\"twal\",\n\"twale\",\n\"twalt\",\n\"twang\",\n\"twanger\",\n\"twangle\",\n\"twangy\",\n\"twank\",\n\"twanker\",\n\"twankle\",\n\"twanky\",\n\"twant\",\n\"twarly\",\n\"twas\",\n\"twasome\",\n\"twat\",\n\"twattle\",\n\"tway\",\n\"twazzy\",\n\"tweag\",\n\"tweak\",\n\"tweaker\",\n\"tweaky\",\n\"twee\",\n\"tweed\",\n\"tweeded\",\n\"tweedle\",\n\"tweedy\",\n\"tweeg\",\n\"tweel\",\n\"tween\",\n\"tweeny\",\n\"tweesh\",\n\"tweesht\",\n\"tweest\",\n\"tweet\",\n\"tweeter\",\n\"tweeze\",\n\"tweezer\",\n\"tweil\",\n\"twelfth\",\n\"twelve\",\n\"twenty\",\n\"twere\",\n\"twerp\",\n\"twibil\",\n\"twice\",\n\"twicer\",\n\"twicet\",\n\"twick\",\n\"twiddle\",\n\"twiddly\",\n\"twifoil\",\n\"twifold\",\n\"twig\",\n\"twigful\",\n\"twigged\",\n\"twiggen\",\n\"twigger\",\n\"twiggy\",\n\"twiglet\",\n\"twilit\",\n\"twill\",\n\"twilled\",\n\"twiller\",\n\"twilly\",\n\"twilt\",\n\"twin\",\n\"twindle\",\n\"twine\",\n\"twiner\",\n\"twinge\",\n\"twingle\",\n\"twinism\",\n\"twink\",\n\"twinkle\",\n\"twinkly\",\n\"twinly\",\n\"twinned\",\n\"twinner\",\n\"twinter\",\n\"twiny\",\n\"twire\",\n\"twirk\",\n\"twirl\",\n\"twirler\",\n\"twirly\",\n\"twiscar\",\n\"twisel\",\n\"twist\",\n\"twisted\",\n\"twister\",\n\"twistle\",\n\"twisty\",\n\"twit\",\n\"twitch\",\n\"twitchy\",\n\"twite\",\n\"twitten\",\n\"twitter\",\n\"twitty\",\n\"twixt\",\n\"twizzle\",\n\"two\",\n\"twofold\",\n\"twoling\",\n\"twoness\",\n\"twosome\",\n\"tychism\",\n\"tychite\",\n\"tycoon\",\n\"tyddyn\",\n\"tydie\",\n\"tye\",\n\"tyee\",\n\"tyg\",\n\"tying\",\n\"tyke\",\n\"tyken\",\n\"tykhana\",\n\"tyking\",\n\"tylarus\",\n\"tylion\",\n\"tyloma\",\n\"tylopod\",\n\"tylose\",\n\"tylosis\",\n\"tylote\",\n\"tylotic\",\n\"tylotus\",\n\"tylus\",\n\"tymp\",\n\"tympan\",\n\"tympana\",\n\"tympani\",\n\"tympany\",\n\"tynd\",\n\"typal\",\n\"type\",\n\"typer\",\n\"typeset\",\n\"typhia\",\n\"typhic\",\n\"typhlon\",\n\"typhoid\",\n\"typhoon\",\n\"typhose\",\n\"typhous\",\n\"typhus\",\n\"typic\",\n\"typica\",\n\"typical\",\n\"typicon\",\n\"typicum\",\n\"typify\",\n\"typist\",\n\"typo\",\n\"typobar\",\n\"typonym\",\n\"typp\",\n\"typy\",\n\"tyranny\",\n\"tyrant\",\n\"tyre\",\n\"tyro\",\n\"tyroma\",\n\"tyrone\",\n\"tyronic\",\n\"tyrosyl\",\n\"tyste\",\n\"tyt\",\n\"tzolkin\",\n\"tzontle\",\n\"u\",\n\"uang\",\n\"uayeb\",\n\"uberant\",\n\"uberous\",\n\"uberty\",\n\"ubi\",\n\"ubiety\",\n\"ubiquit\",\n\"ubussu\",\n\"uckia\",\n\"udal\",\n\"udaler\",\n\"udaller\",\n\"udalman\",\n\"udasi\",\n\"udder\",\n\"uddered\",\n\"udell\",\n\"udo\",\n\"ug\",\n\"ugh\",\n\"uglify\",\n\"uglily\",\n\"ugly\",\n\"ugsome\",\n\"uhlan\",\n\"uhllo\",\n\"uhtsong\",\n\"uily\",\n\"uinal\",\n\"uintjie\",\n\"uitspan\",\n\"uji\",\n\"ukase\",\n\"uke\",\n\"ukiyoye\",\n\"ukulele\",\n\"ula\",\n\"ulcer\",\n\"ulcered\",\n\"ulcery\",\n\"ule\",\n\"ulema\",\n\"uletic\",\n\"ulex\",\n\"ulexine\",\n\"ulexite\",\n\"ulitis\",\n\"ull\",\n\"ulla\",\n\"ullage\",\n\"ullaged\",\n\"uller\",\n\"ulling\",\n\"ulluco\",\n\"ulmic\",\n\"ulmin\",\n\"ulminic\",\n\"ulmo\",\n\"ulmous\",\n\"ulna\",\n\"ulnad\",\n\"ulnae\",\n\"ulnar\",\n\"ulnare\",\n\"ulnaria\",\n\"uloid\",\n\"uloncus\",\n\"ulster\",\n\"ultima\",\n\"ultimo\",\n\"ultimum\",\n\"ultra\",\n\"ulu\",\n\"ulua\",\n\"uluhi\",\n\"ululant\",\n\"ululate\",\n\"ululu\",\n\"um\",\n\"umbel\",\n\"umbeled\",\n\"umbella\",\n\"umber\",\n\"umbilic\",\n\"umble\",\n\"umbo\",\n\"umbonal\",\n\"umbone\",\n\"umbones\",\n\"umbonic\",\n\"umbra\",\n\"umbrae\",\n\"umbrage\",\n\"umbral\",\n\"umbrel\",\n\"umbril\",\n\"umbrine\",\n\"umbrose\",\n\"umbrous\",\n\"ume\",\n\"umiak\",\n\"umiri\",\n\"umlaut\",\n\"ump\",\n\"umph\",\n\"umpire\",\n\"umpirer\",\n\"umpteen\",\n\"umpty\",\n\"umu\",\n\"un\",\n\"unable\",\n\"unably\",\n\"unact\",\n\"unacted\",\n\"unacute\",\n\"unadapt\",\n\"unadd\",\n\"unadded\",\n\"unadopt\",\n\"unadorn\",\n\"unadult\",\n\"unafire\",\n\"unaflow\",\n\"unaged\",\n\"unagile\",\n\"unaging\",\n\"unaided\",\n\"unaimed\",\n\"unaired\",\n\"unakin\",\n\"unakite\",\n\"unal\",\n\"unalarm\",\n\"unalert\",\n\"unalike\",\n\"unalist\",\n\"unalive\",\n\"unallow\",\n\"unalone\",\n\"unaloud\",\n\"unamend\",\n\"unamiss\",\n\"unamo\",\n\"unample\",\n\"unamply\",\n\"unangry\",\n\"unannex\",\n\"unapart\",\n\"unapt\",\n\"unaptly\",\n\"unarch\",\n\"unark\",\n\"unarm\",\n\"unarmed\",\n\"unarray\",\n\"unarted\",\n\"unary\",\n\"unasked\",\n\"unau\",\n\"unavian\",\n\"unawake\",\n\"unaware\",\n\"unaway\",\n\"unawed\",\n\"unawful\",\n\"unawned\",\n\"unaxled\",\n\"unbag\",\n\"unbain\",\n\"unbait\",\n\"unbaked\",\n\"unbale\",\n\"unbank\",\n\"unbar\",\n\"unbarb\",\n\"unbare\",\n\"unbark\",\n\"unbase\",\n\"unbased\",\n\"unbaste\",\n\"unbated\",\n\"unbay\",\n\"unbe\",\n\"unbear\",\n\"unbeard\",\n\"unbeast\",\n\"unbed\",\n\"unbefit\",\n\"unbeget\",\n\"unbegot\",\n\"unbegun\",\n\"unbeing\",\n\"unbell\",\n\"unbelt\",\n\"unbench\",\n\"unbend\",\n\"unbent\",\n\"unberth\",\n\"unbeset\",\n\"unbesot\",\n\"unbet\",\n\"unbias\",\n\"unbid\",\n\"unbind\",\n\"unbit\",\n\"unbitt\",\n\"unblade\",\n\"unbled\",\n\"unblent\",\n\"unbless\",\n\"unblest\",\n\"unblind\",\n\"unbliss\",\n\"unblock\",\n\"unbloom\",\n\"unblown\",\n\"unblued\",\n\"unblush\",\n\"unboat\",\n\"unbody\",\n\"unbog\",\n\"unboggy\",\n\"unbokel\",\n\"unbold\",\n\"unbolt\",\n\"unbone\",\n\"unboned\",\n\"unbonny\",\n\"unboot\",\n\"unbored\",\n\"unborn\",\n\"unborne\",\n\"unbosom\",\n\"unbound\",\n\"unbow\",\n\"unbowed\",\n\"unbowel\",\n\"unbox\",\n\"unboxed\",\n\"unboy\",\n\"unbrace\",\n\"unbraid\",\n\"unbran\",\n\"unbrand\",\n\"unbrave\",\n\"unbraze\",\n\"unbred\",\n\"unbrent\",\n\"unbrick\",\n\"unbrief\",\n\"unbroad\",\n\"unbroke\",\n\"unbrown\",\n\"unbrute\",\n\"unbud\",\n\"unbuild\",\n\"unbuilt\",\n\"unbulky\",\n\"unbung\",\n\"unburly\",\n\"unburn\",\n\"unburnt\",\n\"unburst\",\n\"unbury\",\n\"unbush\",\n\"unbusk\",\n\"unbusy\",\n\"unbuxom\",\n\"unca\",\n\"uncage\",\n\"uncaged\",\n\"uncake\",\n\"uncalk\",\n\"uncall\",\n\"uncalm\",\n\"uncaned\",\n\"uncanny\",\n\"uncap\",\n\"uncart\",\n\"uncase\",\n\"uncased\",\n\"uncask\",\n\"uncast\",\n\"uncaste\",\n\"uncate\",\n\"uncave\",\n\"unceded\",\n\"unchain\",\n\"unchair\",\n\"uncharm\",\n\"unchary\",\n\"uncheat\",\n\"uncheck\",\n\"unchid\",\n\"unchild\",\n\"unchurn\",\n\"unci\",\n\"uncia\",\n\"uncial\",\n\"uncinal\",\n\"uncinch\",\n\"uncinct\",\n\"uncini\",\n\"uncinus\",\n\"uncite\",\n\"uncited\",\n\"uncity\",\n\"uncivic\",\n\"uncivil\",\n\"unclad\",\n\"unclamp\",\n\"unclasp\",\n\"unclay\",\n\"uncle\",\n\"unclead\",\n\"unclean\",\n\"unclear\",\n\"uncleft\",\n\"unclew\",\n\"unclick\",\n\"unclify\",\n\"unclimb\",\n\"uncling\",\n\"unclip\",\n\"uncloak\",\n\"unclog\",\n\"unclose\",\n\"uncloud\",\n\"unclout\",\n\"unclub\",\n\"unco\",\n\"uncoach\",\n\"uncoat\",\n\"uncock\",\n\"uncoded\",\n\"uncoif\",\n\"uncoil\",\n\"uncoin\",\n\"uncoked\",\n\"uncolt\",\n\"uncoly\",\n\"uncome\",\n\"uncomfy\",\n\"uncomic\",\n\"uncoop\",\n\"uncope\",\n\"uncord\",\n\"uncore\",\n\"uncored\",\n\"uncork\",\n\"uncost\",\n\"uncouch\",\n\"uncous\",\n\"uncouth\",\n\"uncover\",\n\"uncowed\",\n\"uncowl\",\n\"uncoy\",\n\"uncram\",\n\"uncramp\",\n\"uncream\",\n\"uncrest\",\n\"uncrib\",\n\"uncried\",\n\"uncrime\",\n\"uncrisp\",\n\"uncrook\",\n\"uncropt\",\n\"uncross\",\n\"uncrown\",\n\"uncrude\",\n\"uncruel\",\n\"unction\",\n\"uncubic\",\n\"uncular\",\n\"uncurb\",\n\"uncurd\",\n\"uncured\",\n\"uncurl\",\n\"uncurse\",\n\"uncurst\",\n\"uncus\",\n\"uncut\",\n\"uncuth\",\n\"undaily\",\n\"undam\",\n\"undamn\",\n\"undared\",\n\"undark\",\n\"undate\",\n\"undated\",\n\"undaub\",\n\"undazed\",\n\"unde\",\n\"undead\",\n\"undeaf\",\n\"undealt\",\n\"undean\",\n\"undear\",\n\"undeck\",\n\"undecyl\",\n\"undeep\",\n\"undeft\",\n\"undeify\",\n\"undelve\",\n\"unden\",\n\"under\",\n\"underdo\",\n\"underer\",\n\"undergo\",\n\"underly\",\n\"undern\",\n\"undevil\",\n\"undewed\",\n\"undewy\",\n\"undid\",\n\"undies\",\n\"undig\",\n\"undight\",\n\"undiked\",\n\"undim\",\n\"undine\",\n\"undined\",\n\"undirk\",\n\"undo\",\n\"undock\",\n\"undoer\",\n\"undog\",\n\"undoing\",\n\"undomed\",\n\"undon\",\n\"undone\",\n\"undoped\",\n\"undose\",\n\"undosed\",\n\"undowny\",\n\"undrab\",\n\"undrag\",\n\"undrape\",\n\"undraw\",\n\"undrawn\",\n\"undress\",\n\"undried\",\n\"undrunk\",\n\"undry\",\n\"undub\",\n\"unducal\",\n\"undue\",\n\"undug\",\n\"unduke\",\n\"undular\",\n\"undull\",\n\"unduly\",\n\"unduped\",\n\"undust\",\n\"unduty\",\n\"undwelt\",\n\"undy\",\n\"undye\",\n\"undyed\",\n\"undying\",\n\"uneager\",\n\"unearly\",\n\"unearth\",\n\"unease\",\n\"uneasy\",\n\"uneaten\",\n\"uneath\",\n\"unebbed\",\n\"unedge\",\n\"unedged\",\n\"unelect\",\n\"unempt\",\n\"unempty\",\n\"unended\",\n\"unepic\",\n\"unequal\",\n\"unerect\",\n\"unethic\",\n\"uneven\",\n\"unevil\",\n\"unexact\",\n\"uneye\",\n\"uneyed\",\n\"unface\",\n\"unfaced\",\n\"unfact\",\n\"unfaded\",\n\"unfain\",\n\"unfaint\",\n\"unfair\",\n\"unfaith\",\n\"unfaked\",\n\"unfalse\",\n\"unfamed\",\n\"unfancy\",\n\"unfar\",\n\"unfast\",\n\"unfeary\",\n\"unfed\",\n\"unfeed\",\n\"unfele\",\n\"unfelon\",\n\"unfelt\",\n\"unfence\",\n\"unfeted\",\n\"unfeued\",\n\"unfew\",\n\"unfiber\",\n\"unfiend\",\n\"unfiery\",\n\"unfight\",\n\"unfile\",\n\"unfiled\",\n\"unfill\",\n\"unfilm\",\n\"unfine\",\n\"unfined\",\n\"unfired\",\n\"unfirm\",\n\"unfit\",\n\"unfitly\",\n\"unfitty\",\n\"unfix\",\n\"unfixed\",\n\"unflag\",\n\"unflaky\",\n\"unflank\",\n\"unflat\",\n\"unflead\",\n\"unflesh\",\n\"unflock\",\n\"unfloor\",\n\"unflown\",\n\"unfluid\",\n\"unflush\",\n\"unfoggy\",\n\"unfold\",\n\"unfond\",\n\"unfool\",\n\"unfork\",\n\"unform\",\n\"unfoul\",\n\"unfound\",\n\"unfoxy\",\n\"unfrail\",\n\"unframe\",\n\"unfrank\",\n\"unfree\",\n\"unfreed\",\n\"unfret\",\n\"unfried\",\n\"unfrill\",\n\"unfrizz\",\n\"unfrock\",\n\"unfrost\",\n\"unfroze\",\n\"unfull\",\n\"unfully\",\n\"unfumed\",\n\"unfunny\",\n\"unfur\",\n\"unfurl\",\n\"unfused\",\n\"unfussy\",\n\"ungag\",\n\"ungaged\",\n\"ungain\",\n\"ungaite\",\n\"ungaro\",\n\"ungaudy\",\n\"ungear\",\n\"ungelt\",\n\"unget\",\n\"ungiant\",\n\"ungiddy\",\n\"ungild\",\n\"ungill\",\n\"ungilt\",\n\"ungird\",\n\"ungirt\",\n\"ungirth\",\n\"ungive\",\n\"ungiven\",\n\"ungka\",\n\"unglad\",\n\"unglaze\",\n\"unglee\",\n\"unglobe\",\n\"ungloom\",\n\"unglory\",\n\"ungloss\",\n\"unglove\",\n\"unglue\",\n\"unglued\",\n\"ungnaw\",\n\"ungnawn\",\n\"ungod\",\n\"ungodly\",\n\"ungold\",\n\"ungone\",\n\"ungood\",\n\"ungored\",\n\"ungorge\",\n\"ungot\",\n\"ungouty\",\n\"ungown\",\n\"ungrace\",\n\"ungraft\",\n\"ungrain\",\n\"ungrand\",\n\"ungrasp\",\n\"ungrave\",\n\"ungreat\",\n\"ungreen\",\n\"ungrip\",\n\"ungripe\",\n\"ungross\",\n\"ungrow\",\n\"ungrown\",\n\"ungruff\",\n\"ungual\",\n\"unguard\",\n\"ungueal\",\n\"unguent\",\n\"ungues\",\n\"unguis\",\n\"ungula\",\n\"ungulae\",\n\"ungular\",\n\"unguled\",\n\"ungull\",\n\"ungulp\",\n\"ungum\",\n\"unguyed\",\n\"ungyve\",\n\"ungyved\",\n\"unhabit\",\n\"unhad\",\n\"unhaft\",\n\"unhair\",\n\"unhairy\",\n\"unhand\",\n\"unhandy\",\n\"unhang\",\n\"unhap\",\n\"unhappy\",\n\"unhard\",\n\"unhardy\",\n\"unharsh\",\n\"unhasp\",\n\"unhaste\",\n\"unhasty\",\n\"unhat\",\n\"unhate\",\n\"unhated\",\n\"unhaunt\",\n\"unhave\",\n\"unhayed\",\n\"unhazed\",\n\"unhead\",\n\"unheady\",\n\"unheal\",\n\"unheard\",\n\"unheart\",\n\"unheavy\",\n\"unhedge\",\n\"unheed\",\n\"unheedy\",\n\"unheld\",\n\"unhele\",\n\"unheler\",\n\"unhelm\",\n\"unherd\",\n\"unhero\",\n\"unhewed\",\n\"unhewn\",\n\"unhex\",\n\"unhid\",\n\"unhide\",\n\"unhigh\",\n\"unhinge\",\n\"unhired\",\n\"unhit\",\n\"unhitch\",\n\"unhive\",\n\"unhoard\",\n\"unhoary\",\n\"unhoed\",\n\"unhoist\",\n\"unhold\",\n\"unholy\",\n\"unhome\",\n\"unhoned\",\n\"unhood\",\n\"unhook\",\n\"unhoop\",\n\"unhoped\",\n\"unhorny\",\n\"unhorse\",\n\"unhose\",\n\"unhosed\",\n\"unhot\",\n\"unhouse\",\n\"unhull\",\n\"unhuman\",\n\"unhumid\",\n\"unhung\",\n\"unhurt\",\n\"unhusk\",\n\"uniat\",\n\"uniate\",\n\"uniaxal\",\n\"unible\",\n\"unice\",\n\"uniced\",\n\"unicell\",\n\"unicism\",\n\"unicist\",\n\"unicity\",\n\"unicorn\",\n\"unicum\",\n\"unideal\",\n\"unidle\",\n\"unidly\",\n\"unie\",\n\"uniface\",\n\"unific\",\n\"unified\",\n\"unifier\",\n\"uniflow\",\n\"uniform\",\n\"unify\",\n\"unilobe\",\n\"unimped\",\n\"uninked\",\n\"uninn\",\n\"unio\",\n\"unioid\",\n\"union\",\n\"unioned\",\n\"unionic\",\n\"unionid\",\n\"unioval\",\n\"unipara\",\n\"uniped\",\n\"unipod\",\n\"unique\",\n\"unireme\",\n\"unisoil\",\n\"unison\",\n\"unit\",\n\"unitage\",\n\"unital\",\n\"unitary\",\n\"unite\",\n\"united\",\n\"uniter\",\n\"uniting\",\n\"unition\",\n\"unitism\",\n\"unitive\",\n\"unitize\",\n\"unitude\",\n\"unity\",\n\"univied\",\n\"unjaded\",\n\"unjam\",\n\"unjewel\",\n\"unjoin\",\n\"unjoint\",\n\"unjolly\",\n\"unjoyed\",\n\"unjudge\",\n\"unjuicy\",\n\"unjust\",\n\"unkamed\",\n\"unked\",\n\"unkempt\",\n\"unken\",\n\"unkept\",\n\"unket\",\n\"unkey\",\n\"unkeyed\",\n\"unkid\",\n\"unkill\",\n\"unkin\",\n\"unkind\",\n\"unking\",\n\"unkink\",\n\"unkirk\",\n\"unkiss\",\n\"unkist\",\n\"unknave\",\n\"unknew\",\n\"unknit\",\n\"unknot\",\n\"unknow\",\n\"unknown\",\n\"unlace\",\n\"unlaced\",\n\"unlade\",\n\"unladen\",\n\"unlaid\",\n\"unlame\",\n\"unlamed\",\n\"unland\",\n\"unlap\",\n\"unlarge\",\n\"unlash\",\n\"unlatch\",\n\"unlath\",\n\"unlaugh\",\n\"unlaved\",\n\"unlaw\",\n\"unlawed\",\n\"unlawly\",\n\"unlay\",\n\"unlead\",\n\"unleaf\",\n\"unleaky\",\n\"unleal\",\n\"unlean\",\n\"unlearn\",\n\"unleash\",\n\"unleave\",\n\"unled\",\n\"unleft\",\n\"unlegal\",\n\"unlent\",\n\"unless\",\n\"unlet\",\n\"unlevel\",\n\"unlid\",\n\"unlie\",\n\"unlight\",\n\"unlike\",\n\"unliked\",\n\"unliken\",\n\"unlimb\",\n\"unlime\",\n\"unlimed\",\n\"unlimp\",\n\"unline\",\n\"unlined\",\n\"unlink\",\n\"unlist\",\n\"unlisty\",\n\"unlit\",\n\"unlive\",\n\"unload\",\n\"unloath\",\n\"unlobed\",\n\"unlocal\",\n\"unlock\",\n\"unlodge\",\n\"unlofty\",\n\"unlogic\",\n\"unlook\",\n\"unloop\",\n\"unloose\",\n\"unlord\",\n\"unlost\",\n\"unlousy\",\n\"unlove\",\n\"unloved\",\n\"unlowly\",\n\"unloyal\",\n\"unlucid\",\n\"unluck\",\n\"unlucky\",\n\"unlunar\",\n\"unlured\",\n\"unlust\",\n\"unlusty\",\n\"unlute\",\n\"unluted\",\n\"unlying\",\n\"unmad\",\n\"unmade\",\n\"unmagic\",\n\"unmaid\",\n\"unmail\",\n\"unmake\",\n\"unmaker\",\n\"unman\",\n\"unmaned\",\n\"unmanly\",\n\"unmarch\",\n\"unmarry\",\n\"unmask\",\n\"unmast\",\n\"unmate\",\n\"unmated\",\n\"unmaze\",\n\"unmeant\",\n\"unmeek\",\n\"unmeet\",\n\"unmerge\",\n\"unmerry\",\n\"unmesh\",\n\"unmet\",\n\"unmeted\",\n\"unmew\",\n\"unmewed\",\n\"unmind\",\n\"unmined\",\n\"unmired\",\n\"unmiry\",\n\"unmist\",\n\"unmiter\",\n\"unmix\",\n\"unmixed\",\n\"unmodel\",\n\"unmoist\",\n\"unmold\",\n\"unmoldy\",\n\"unmoor\",\n\"unmoral\",\n\"unmount\",\n\"unmoved\",\n\"unmowed\",\n\"unmown\",\n\"unmuddy\",\n\"unmuted\",\n\"unnail\",\n\"unnaked\",\n\"unname\",\n\"unnamed\",\n\"unneat\",\n\"unneedy\",\n\"unnegro\",\n\"unnerve\",\n\"unnest\",\n\"unneth\",\n\"unnethe\",\n\"unnew\",\n\"unnewly\",\n\"unnice\",\n\"unnigh\",\n\"unnoble\",\n\"unnobly\",\n\"unnose\",\n\"unnosed\",\n\"unnoted\",\n\"unnovel\",\n\"unoared\",\n\"unobese\",\n\"unode\",\n\"unoften\",\n\"unogled\",\n\"unoil\",\n\"unoiled\",\n\"unoily\",\n\"unold\",\n\"unoped\",\n\"unopen\",\n\"unorbed\",\n\"unorder\",\n\"unorn\",\n\"unornly\",\n\"unovert\",\n\"unowed\",\n\"unowing\",\n\"unown\",\n\"unowned\",\n\"unpaced\",\n\"unpack\",\n\"unpagan\",\n\"unpaged\",\n\"unpaid\",\n\"unpaint\",\n\"unpale\",\n\"unpaled\",\n\"unpanel\",\n\"unpapal\",\n\"unpaper\",\n\"unparch\",\n\"unpared\",\n\"unpark\",\n\"unparty\",\n\"unpass\",\n\"unpaste\",\n\"unpave\",\n\"unpaved\",\n\"unpawed\",\n\"unpawn\",\n\"unpeace\",\n\"unpeel\",\n\"unpeg\",\n\"unpen\",\n\"unpenal\",\n\"unpent\",\n\"unperch\",\n\"unpetal\",\n\"unpick\",\n\"unpiece\",\n\"unpiety\",\n\"unpile\",\n\"unpiled\",\n\"unpin\",\n\"unpious\",\n\"unpiped\",\n\"unplace\",\n\"unplaid\",\n\"unplain\",\n\"unplait\",\n\"unplan\",\n\"unplank\",\n\"unplant\",\n\"unplat\",\n\"unpleat\",\n\"unplied\",\n\"unplow\",\n\"unplug\",\n\"unplumb\",\n\"unplume\",\n\"unplump\",\n\"unpoise\",\n\"unpoled\",\n\"unpope\",\n\"unposed\",\n\"unpot\",\n\"unpower\",\n\"unpray\",\n\"unprim\",\n\"unprime\",\n\"unprint\",\n\"unprop\",\n\"unproud\",\n\"unpure\",\n\"unpurse\",\n\"unput\",\n\"unqueen\",\n\"unquick\",\n\"unquiet\",\n\"unquit\",\n\"unquote\",\n\"unraced\",\n\"unrack\",\n\"unrainy\",\n\"unrake\",\n\"unraked\",\n\"unram\",\n\"unrank\",\n\"unraped\",\n\"unrare\",\n\"unrash\",\n\"unrated\",\n\"unravel\",\n\"unray\",\n\"unrayed\",\n\"unrazed\",\n\"unread\",\n\"unready\",\n\"unreal\",\n\"unreave\",\n\"unrebel\",\n\"unred\",\n\"unreel\",\n\"unreeve\",\n\"unregal\",\n\"unrein\",\n\"unrent\",\n\"unrest\",\n\"unresty\",\n\"unrhyme\",\n\"unrich\",\n\"unricht\",\n\"unrid\",\n\"unride\",\n\"unrife\",\n\"unrig\",\n\"unright\",\n\"unrigid\",\n\"unrind\",\n\"unring\",\n\"unrip\",\n\"unripe\",\n\"unriped\",\n\"unrisen\",\n\"unrisky\",\n\"unrived\",\n\"unriven\",\n\"unrivet\",\n\"unroast\",\n\"unrobe\",\n\"unrobed\",\n\"unroll\",\n\"unroof\",\n\"unroomy\",\n\"unroost\",\n\"unroot\",\n\"unrope\",\n\"unroped\",\n\"unrosed\",\n\"unroted\",\n\"unrough\",\n\"unround\",\n\"unrove\",\n\"unroved\",\n\"unrow\",\n\"unrowed\",\n\"unroyal\",\n\"unrule\",\n\"unruled\",\n\"unruly\",\n\"unrun\",\n\"unrung\",\n\"unrural\",\n\"unrust\",\n\"unruth\",\n\"unsack\",\n\"unsad\",\n\"unsafe\",\n\"unsage\",\n\"unsaid\",\n\"unsaint\",\n\"unsalt\",\n\"unsane\",\n\"unsappy\",\n\"unsash\",\n\"unsated\",\n\"unsatin\",\n\"unsaved\",\n\"unsawed\",\n\"unsawn\",\n\"unsay\",\n\"unscale\",\n\"unscaly\",\n\"unscarb\",\n\"unscent\",\n\"unscrew\",\n\"unseal\",\n\"unseam\",\n\"unseat\",\n\"unsee\",\n\"unseen\",\n\"unself\",\n\"unsense\",\n\"unsent\",\n\"unset\",\n\"unsew\",\n\"unsewed\",\n\"unsewn\",\n\"unsex\",\n\"unsexed\",\n\"unshade\",\n\"unshady\",\n\"unshape\",\n\"unsharp\",\n\"unshawl\",\n\"unsheaf\",\n\"unshed\",\n\"unsheet\",\n\"unshell\",\n\"unship\",\n\"unshod\",\n\"unshoe\",\n\"unshoed\",\n\"unshop\",\n\"unshore\",\n\"unshorn\",\n\"unshort\",\n\"unshot\",\n\"unshown\",\n\"unshowy\",\n\"unshrew\",\n\"unshut\",\n\"unshy\",\n\"unshyly\",\n\"unsick\",\n\"unsided\",\n\"unsiege\",\n\"unsight\",\n\"unsilly\",\n\"unsin\",\n\"unsinew\",\n\"unsing\",\n\"unsized\",\n\"unskin\",\n\"unslack\",\n\"unslain\",\n\"unslate\",\n\"unslave\",\n\"unsleek\",\n\"unslept\",\n\"unsling\",\n\"unslip\",\n\"unslit\",\n\"unslot\",\n\"unslow\",\n\"unslung\",\n\"unsly\",\n\"unsmart\",\n\"unsmoky\",\n\"unsmote\",\n\"unsnaky\",\n\"unsnap\",\n\"unsnare\",\n\"unsnarl\",\n\"unsneck\",\n\"unsnib\",\n\"unsnow\",\n\"unsober\",\n\"unsoft\",\n\"unsoggy\",\n\"unsoil\",\n\"unsolar\",\n\"unsold\",\n\"unsole\",\n\"unsoled\",\n\"unsolid\",\n\"unsome\",\n\"unson\",\n\"unsonsy\",\n\"unsooty\",\n\"unsore\",\n\"unsorry\",\n\"unsort\",\n\"unsoul\",\n\"unsound\",\n\"unsour\",\n\"unsowed\",\n\"unsown\",\n\"unspan\",\n\"unspar\",\n\"unspeak\",\n\"unsped\",\n\"unspeed\",\n\"unspell\",\n\"unspelt\",\n\"unspent\",\n\"unspicy\",\n\"unspied\",\n\"unspike\",\n\"unspin\",\n\"unspit\",\n\"unsplit\",\n\"unspoil\",\n\"unspot\",\n\"unspun\",\n\"unstack\",\n\"unstagy\",\n\"unstaid\",\n\"unstain\",\n\"unstar\",\n\"unstate\",\n\"unsteck\",\n\"unsteel\",\n\"unsteep\",\n\"unstep\",\n\"unstern\",\n\"unstick\",\n\"unstill\",\n\"unsting\",\n\"unstock\",\n\"unstoic\",\n\"unstone\",\n\"unstony\",\n\"unstop\",\n\"unstore\",\n\"unstout\",\n\"unstow\",\n\"unstrap\",\n\"unstrip\",\n\"unstuck\",\n\"unstuff\",\n\"unstung\",\n\"unsty\",\n\"unsued\",\n\"unsuit\",\n\"unsulky\",\n\"unsun\",\n\"unsung\",\n\"unsunk\",\n\"unsunny\",\n\"unsure\",\n\"unswear\",\n\"unsweat\",\n\"unsweet\",\n\"unswell\",\n\"unswept\",\n\"unswing\",\n\"unsworn\",\n\"unswung\",\n\"untack\",\n\"untaint\",\n\"untaken\",\n\"untall\",\n\"untame\",\n\"untamed\",\n\"untap\",\n\"untaped\",\n\"untar\",\n\"untaste\",\n\"untasty\",\n\"untaut\",\n\"untawed\",\n\"untax\",\n\"untaxed\",\n\"unteach\",\n\"unteam\",\n\"unteem\",\n\"untell\",\n\"untense\",\n\"untent\",\n\"untenty\",\n\"untewed\",\n\"unthank\",\n\"unthaw\",\n\"unthick\",\n\"unthink\",\n\"unthorn\",\n\"unthrid\",\n\"unthrob\",\n\"untidal\",\n\"untidy\",\n\"untie\",\n\"untied\",\n\"untight\",\n\"until\",\n\"untile\",\n\"untiled\",\n\"untill\",\n\"untilt\",\n\"untimed\",\n\"untin\",\n\"untinct\",\n\"untine\",\n\"untipt\",\n\"untire\",\n\"untired\",\n\"unto\",\n\"untold\",\n\"untomb\",\n\"untone\",\n\"untoned\",\n\"untooth\",\n\"untop\",\n\"untorn\",\n\"untouch\",\n\"untough\",\n\"untown\",\n\"untrace\",\n\"untrain\",\n\"untread\",\n\"untreed\",\n\"untress\",\n\"untried\",\n\"untrig\",\n\"untrill\",\n\"untrim\",\n\"untripe\",\n\"untrite\",\n\"untrod\",\n\"untruck\",\n\"untrue\",\n\"untruly\",\n\"untruss\",\n\"untrust\",\n\"untruth\",\n\"untuck\",\n\"untumid\",\n\"untune\",\n\"untuned\",\n\"unturf\",\n\"unturn\",\n\"untwine\",\n\"untwirl\",\n\"untwist\",\n\"untying\",\n\"untz\",\n\"unugly\",\n\"unultra\",\n\"unupset\",\n\"unurban\",\n\"unurged\",\n\"unurn\",\n\"unurned\",\n\"unuse\",\n\"unused\",\n\"unusual\",\n\"unvain\",\n\"unvalid\",\n\"unvalue\",\n\"unveil\",\n\"unvenom\",\n\"unvest\",\n\"unvexed\",\n\"unvicar\",\n\"unvisor\",\n\"unvital\",\n\"unvivid\",\n\"unvocal\",\n\"unvoice\",\n\"unvote\",\n\"unvoted\",\n\"unvowed\",\n\"unwaded\",\n\"unwaged\",\n\"unwaked\",\n\"unwall\",\n\"unwan\",\n\"unware\",\n\"unwarm\",\n\"unwarn\",\n\"unwarp\",\n\"unwary\",\n\"unwater\",\n\"unwaved\",\n\"unwax\",\n\"unwaxed\",\n\"unwayed\",\n\"unweal\",\n\"unweary\",\n\"unweave\",\n\"unweb\",\n\"unwed\",\n\"unwedge\",\n\"unweel\",\n\"unweft\",\n\"unweld\",\n\"unwell\",\n\"unwept\",\n\"unwet\",\n\"unwheel\",\n\"unwhig\",\n\"unwhip\",\n\"unwhite\",\n\"unwield\",\n\"unwifed\",\n\"unwig\",\n\"unwild\",\n\"unwill\",\n\"unwily\",\n\"unwind\",\n\"unwindy\",\n\"unwiped\",\n\"unwire\",\n\"unwired\",\n\"unwise\",\n\"unwish\",\n\"unwist\",\n\"unwitch\",\n\"unwitty\",\n\"unwive\",\n\"unwived\",\n\"unwoful\",\n\"unwoman\",\n\"unwomb\",\n\"unwon\",\n\"unwooed\",\n\"unwoof\",\n\"unwooly\",\n\"unwordy\",\n\"unwork\",\n\"unworld\",\n\"unwormy\",\n\"unworn\",\n\"unworth\",\n\"unwound\",\n\"unwoven\",\n\"unwrap\",\n\"unwrit\",\n\"unwrite\",\n\"unwrung\",\n\"unyoke\",\n\"unyoked\",\n\"unyoung\",\n\"unze\",\n\"unzen\",\n\"unzone\",\n\"unzoned\",\n\"up\",\n\"upaisle\",\n\"upalley\",\n\"upalong\",\n\"uparch\",\n\"uparise\",\n\"uparm\",\n\"uparna\",\n\"upas\",\n\"upattic\",\n\"upbank\",\n\"upbar\",\n\"upbay\",\n\"upbear\",\n\"upbeat\",\n\"upbelch\",\n\"upbelt\",\n\"upbend\",\n\"upbid\",\n\"upbind\",\n\"upblast\",\n\"upblaze\",\n\"upblow\",\n\"upboil\",\n\"upbolt\",\n\"upboost\",\n\"upborne\",\n\"upbotch\",\n\"upbound\",\n\"upbrace\",\n\"upbraid\",\n\"upbray\",\n\"upbreak\",\n\"upbred\",\n\"upbreed\",\n\"upbrim\",\n\"upbring\",\n\"upbrook\",\n\"upbrow\",\n\"upbuild\",\n\"upbuoy\",\n\"upburn\",\n\"upburst\",\n\"upbuy\",\n\"upcall\",\n\"upcanal\",\n\"upcarry\",\n\"upcast\",\n\"upcatch\",\n\"upchoke\",\n\"upchuck\",\n\"upcity\",\n\"upclimb\",\n\"upclose\",\n\"upcoast\",\n\"upcock\",\n\"upcoil\",\n\"upcome\",\n\"upcover\",\n\"upcrane\",\n\"upcrawl\",\n\"upcreek\",\n\"upcreep\",\n\"upcrop\",\n\"upcrowd\",\n\"upcry\",\n\"upcurl\",\n\"upcurve\",\n\"upcut\",\n\"updart\",\n\"update\",\n\"updeck\",\n\"updelve\",\n\"updive\",\n\"updo\",\n\"updome\",\n\"updraft\",\n\"updrag\",\n\"updraw\",\n\"updrink\",\n\"updry\",\n\"upeat\",\n\"upend\",\n\"upeygan\",\n\"upfeed\",\n\"upfield\",\n\"upfill\",\n\"upflame\",\n\"upflare\",\n\"upflash\",\n\"upflee\",\n\"upfling\",\n\"upfloat\",\n\"upflood\",\n\"upflow\",\n\"upflung\",\n\"upfly\",\n\"upfold\",\n\"upframe\",\n\"upfurl\",\n\"upgale\",\n\"upgang\",\n\"upgape\",\n\"upgaze\",\n\"upget\",\n\"upgird\",\n\"upgirt\",\n\"upgive\",\n\"upglean\",\n\"upglide\",\n\"upgo\",\n\"upgorge\",\n\"upgrade\",\n\"upgrave\",\n\"upgrow\",\n\"upgully\",\n\"upgush\",\n\"uphand\",\n\"uphang\",\n\"uphasp\",\n\"upheal\",\n\"upheap\",\n\"upheave\",\n\"upheld\",\n\"uphelm\",\n\"uphelya\",\n\"upher\",\n\"uphill\",\n\"uphoard\",\n\"uphoist\",\n\"uphold\",\n\"uphung\",\n\"uphurl\",\n\"upjerk\",\n\"upjet\",\n\"upkeep\",\n\"upknell\",\n\"upknit\",\n\"upla\",\n\"uplaid\",\n\"uplake\",\n\"upland\",\n\"uplane\",\n\"uplay\",\n\"uplead\",\n\"upleap\",\n\"upleg\",\n\"uplick\",\n\"uplift\",\n\"uplight\",\n\"uplimb\",\n\"upline\",\n\"uplock\",\n\"uplong\",\n\"uplook\",\n\"uploom\",\n\"uploop\",\n\"uplying\",\n\"upmast\",\n\"upmix\",\n\"upmost\",\n\"upmount\",\n\"upmove\",\n\"upness\",\n\"upo\",\n\"upon\",\n\"uppard\",\n\"uppent\",\n\"upper\",\n\"upperch\",\n\"upperer\",\n\"uppers\",\n\"uppile\",\n\"upping\",\n\"uppish\",\n\"uppity\",\n\"upplow\",\n\"uppluck\",\n\"uppoint\",\n\"uppoise\",\n\"uppop\",\n\"uppour\",\n\"uppowoc\",\n\"upprick\",\n\"upprop\",\n\"uppuff\",\n\"uppull\",\n\"uppush\",\n\"upraise\",\n\"upreach\",\n\"uprear\",\n\"uprein\",\n\"uprend\",\n\"uprest\",\n\"uprid\",\n\"upridge\",\n\"upright\",\n\"uprip\",\n\"uprisal\",\n\"uprise\",\n\"uprisen\",\n\"upriser\",\n\"uprist\",\n\"uprive\",\n\"upriver\",\n\"uproad\",\n\"uproar\",\n\"uproom\",\n\"uproot\",\n\"uprose\",\n\"uprouse\",\n\"uproute\",\n\"uprun\",\n\"uprush\",\n\"upscale\",\n\"upscrew\",\n\"upseal\",\n\"upseek\",\n\"upseize\",\n\"upsend\",\n\"upset\",\n\"upsey\",\n\"upshaft\",\n\"upshear\",\n\"upshoot\",\n\"upshore\",\n\"upshot\",\n\"upshove\",\n\"upshut\",\n\"upside\",\n\"upsides\",\n\"upsilon\",\n\"upsit\",\n\"upslant\",\n\"upslip\",\n\"upslope\",\n\"upsmite\",\n\"upsoak\",\n\"upsoar\",\n\"upsolve\",\n\"upspeak\",\n\"upspear\",\n\"upspeed\",\n\"upspew\",\n\"upspin\",\n\"upspire\",\n\"upspout\",\n\"upspurt\",\n\"upstaff\",\n\"upstage\",\n\"upstair\",\n\"upstamp\",\n\"upstand\",\n\"upstare\",\n\"upstart\",\n\"upstate\",\n\"upstay\",\n\"upsteal\",\n\"upsteam\",\n\"upstem\",\n\"upstep\",\n\"upstick\",\n\"upstir\",\n\"upsuck\",\n\"upsun\",\n\"upsup\",\n\"upsurge\",\n\"upswarm\",\n\"upsway\",\n\"upsweep\",\n\"upswell\",\n\"upswing\",\n\"uptable\",\n\"uptake\",\n\"uptaker\",\n\"uptear\",\n\"uptend\",\n\"upthrow\",\n\"uptide\",\n\"uptie\",\n\"uptill\",\n\"uptilt\",\n\"uptorn\",\n\"uptoss\",\n\"uptower\",\n\"uptown\",\n\"uptrace\",\n\"uptrack\",\n\"uptrail\",\n\"uptrain\",\n\"uptree\",\n\"uptrend\",\n\"uptrill\",\n\"uptrunk\",\n\"uptruss\",\n\"uptube\",\n\"uptuck\",\n\"upturn\",\n\"uptwist\",\n\"upupoid\",\n\"upvomit\",\n\"upwaft\",\n\"upwall\",\n\"upward\",\n\"upwards\",\n\"upwarp\",\n\"upwax\",\n\"upway\",\n\"upways\",\n\"upwell\",\n\"upwent\",\n\"upwheel\",\n\"upwhelm\",\n\"upwhir\",\n\"upwhirl\",\n\"upwind\",\n\"upwith\",\n\"upwork\",\n\"upwound\",\n\"upwrap\",\n\"upwring\",\n\"upyard\",\n\"upyoke\",\n\"ur\",\n\"ura\",\n\"urachal\",\n\"urachus\",\n\"uracil\",\n\"uraemic\",\n\"uraeus\",\n\"ural\",\n\"urali\",\n\"uraline\",\n\"uralite\",\n\"uralium\",\n\"uramido\",\n\"uramil\",\n\"uramino\",\n\"uran\",\n\"uranate\",\n\"uranic\",\n\"uraniid\",\n\"uranin\",\n\"uranine\",\n\"uranion\",\n\"uranism\",\n\"uranist\",\n\"uranite\",\n\"uranium\",\n\"uranous\",\n\"uranyl\",\n\"urao\",\n\"urare\",\n\"urari\",\n\"urase\",\n\"urate\",\n\"uratic\",\n\"uratoma\",\n\"urazine\",\n\"urazole\",\n\"urban\",\n\"urbane\",\n\"urbian\",\n\"urbic\",\n\"urbify\",\n\"urceole\",\n\"urceoli\",\n\"urceus\",\n\"urchin\",\n\"urd\",\n\"urde\",\n\"urdee\",\n\"ure\",\n\"urea\",\n\"ureal\",\n\"urease\",\n\"uredema\",\n\"uredine\",\n\"uredo\",\n\"ureic\",\n\"ureid\",\n\"ureide\",\n\"ureido\",\n\"uremia\",\n\"uremic\",\n\"urent\",\n\"uresis\",\n\"uretal\",\n\"ureter\",\n\"urethan\",\n\"urethra\",\n\"uretic\",\n\"urf\",\n\"urge\",\n\"urgence\",\n\"urgency\",\n\"urgent\",\n\"urger\",\n\"urging\",\n\"urheen\",\n\"urial\",\n\"uric\",\n\"urinal\",\n\"urinant\",\n\"urinary\",\n\"urinate\",\n\"urine\",\n\"urinose\",\n\"urinous\",\n\"urite\",\n\"urlar\",\n\"urled\",\n\"urling\",\n\"urluch\",\n\"urman\",\n\"urn\",\n\"urna\",\n\"urnae\",\n\"urnal\",\n\"urnful\",\n\"urning\",\n\"urnism\",\n\"urnlike\",\n\"urocele\",\n\"urocyst\",\n\"urodele\",\n\"urogram\",\n\"urohyal\",\n\"urolith\",\n\"urology\",\n\"uromere\",\n\"uronic\",\n\"uropod\",\n\"urosis\",\n\"urosome\",\n\"urostea\",\n\"urotoxy\",\n\"uroxin\",\n\"ursal\",\n\"ursine\",\n\"ursoid\",\n\"ursolic\",\n\"urson\",\n\"ursone\",\n\"ursuk\",\n\"urtica\",\n\"urtite\",\n\"urubu\",\n\"urucu\",\n\"urucuri\",\n\"uruisg\",\n\"urunday\",\n\"urus\",\n\"urushi\",\n\"urushic\",\n\"urva\",\n\"us\",\n\"usable\",\n\"usage\",\n\"usager\",\n\"usance\",\n\"usar\",\n\"usara\",\n\"usaron\",\n\"usation\",\n\"use\",\n\"used\",\n\"usedly\",\n\"usednt\",\n\"usee\",\n\"useful\",\n\"usehold\",\n\"useless\",\n\"usent\",\n\"user\",\n\"ush\",\n\"ushabti\",\n\"usher\",\n\"usherer\",\n\"usings\",\n\"usitate\",\n\"usnea\",\n\"usneoid\",\n\"usnic\",\n\"usninic\",\n\"usque\",\n\"usself\",\n\"ussels\",\n\"ust\",\n\"uster\",\n\"ustion\",\n\"usual\",\n\"usually\",\n\"usuary\",\n\"usucapt\",\n\"usure\",\n\"usurer\",\n\"usuress\",\n\"usurp\",\n\"usurper\",\n\"usurpor\",\n\"usury\",\n\"usward\",\n\"uswards\",\n\"ut\",\n\"uta\",\n\"utahite\",\n\"utai\",\n\"utas\",\n\"utch\",\n\"utchy\",\n\"utees\",\n\"utensil\",\n\"uteri\",\n\"uterine\",\n\"uterus\",\n\"utick\",\n\"utile\",\n\"utility\",\n\"utilize\",\n\"utinam\",\n\"utmost\",\n\"utopia\",\n\"utopian\",\n\"utopism\",\n\"utopist\",\n\"utricle\",\n\"utricul\",\n\"utrubi\",\n\"utrum\",\n\"utsuk\",\n\"utter\",\n\"utterer\",\n\"utterly\",\n\"utu\",\n\"utum\",\n\"uva\",\n\"uval\",\n\"uvalha\",\n\"uvanite\",\n\"uvate\",\n\"uvea\",\n\"uveal\",\n\"uveitic\",\n\"uveitis\",\n\"uveous\",\n\"uvic\",\n\"uvid\",\n\"uviol\",\n\"uvitic\",\n\"uvito\",\n\"uvrou\",\n\"uvula\",\n\"uvulae\",\n\"uvular\",\n\"uvver\",\n\"uxorial\",\n\"uzan\",\n\"uzara\",\n\"uzarin\",\n\"uzaron\",\n\"v\",\n\"vaagmer\",\n\"vaalite\",\n\"vacancy\",\n\"vacant\",\n\"vacate\",\n\"vacatur\",\n\"vaccary\",\n\"vaccina\",\n\"vaccine\",\n\"vache\",\n\"vacoa\",\n\"vacona\",\n\"vacoua\",\n\"vacouf\",\n\"vacual\",\n\"vacuate\",\n\"vacuefy\",\n\"vacuist\",\n\"vacuity\",\n\"vacuole\",\n\"vacuome\",\n\"vacuous\",\n\"vacuum\",\n\"vacuuma\",\n\"vade\",\n\"vadium\",\n\"vadose\",\n\"vady\",\n\"vag\",\n\"vagal\",\n\"vagary\",\n\"vagas\",\n\"vage\",\n\"vagile\",\n\"vagina\",\n\"vaginal\",\n\"vagitus\",\n\"vagrant\",\n\"vagrate\",\n\"vagrom\",\n\"vague\",\n\"vaguely\",\n\"vaguish\",\n\"vaguity\",\n\"vagus\",\n\"vahine\",\n\"vail\",\n\"vain\",\n\"vainful\",\n\"vainly\",\n\"vair\",\n\"vairagi\",\n\"vaire\",\n\"vairy\",\n\"vaivode\",\n\"vajra\",\n\"vakass\",\n\"vakia\",\n\"vakil\",\n\"valance\",\n\"vale\",\n\"valence\",\n\"valency\",\n\"valent\",\n\"valeral\",\n\"valeric\",\n\"valerin\",\n\"valeryl\",\n\"valet\",\n\"valeta\",\n\"valetry\",\n\"valeur\",\n\"valgoid\",\n\"valgus\",\n\"valhall\",\n\"vali\",\n\"valiant\",\n\"valid\",\n\"validly\",\n\"valine\",\n\"valise\",\n\"vall\",\n\"vallar\",\n\"vallary\",\n\"vallate\",\n\"valley\",\n\"vallis\",\n\"vallum\",\n\"valonia\",\n\"valor\",\n\"valse\",\n\"valsoid\",\n\"valuate\",\n\"value\",\n\"valued\",\n\"valuer\",\n\"valuta\",\n\"valva\",\n\"valval\",\n\"valvate\",\n\"valve\",\n\"valved\",\n\"valvula\",\n\"valvule\",\n\"valyl\",\n\"vamfont\",\n\"vamoose\",\n\"vamp\",\n\"vamped\",\n\"vamper\",\n\"vampire\",\n\"van\",\n\"vanadic\",\n\"vanadyl\",\n\"vane\",\n\"vaned\",\n\"vanfoss\",\n\"vang\",\n\"vangee\",\n\"vangeli\",\n\"vanglo\",\n\"vanilla\",\n\"vanille\",\n\"vanish\",\n\"vanity\",\n\"vanman\",\n\"vanmost\",\n\"vanner\",\n\"vannet\",\n\"vansire\",\n\"vantage\",\n\"vanward\",\n\"vapid\",\n\"vapidly\",\n\"vapor\",\n\"vapored\",\n\"vaporer\",\n\"vapory\",\n\"vara\",\n\"varahan\",\n\"varan\",\n\"varanid\",\n\"vardy\",\n\"vare\",\n\"varec\",\n\"vareuse\",\n\"vari\",\n\"variant\",\n\"variate\",\n\"varical\",\n\"varices\",\n\"varied\",\n\"varier\",\n\"variety\",\n\"variola\",\n\"variole\",\n\"various\",\n\"varisse\",\n\"varix\",\n\"varlet\",\n\"varment\",\n\"varna\",\n\"varnish\",\n\"varsha\",\n\"varsity\",\n\"varus\",\n\"varve\",\n\"varved\",\n\"vary\",\n\"vas\",\n\"vasa\",\n\"vasal\",\n\"vase\",\n\"vaseful\",\n\"vaselet\",\n\"vassal\",\n\"vast\",\n\"vastate\",\n\"vastily\",\n\"vastity\",\n\"vastly\",\n\"vasty\",\n\"vasu\",\n\"vat\",\n\"vatful\",\n\"vatic\",\n\"vatman\",\n\"vatter\",\n\"vau\",\n\"vaudy\",\n\"vault\",\n\"vaulted\",\n\"vaulter\",\n\"vaulty\",\n\"vaunt\",\n\"vaunted\",\n\"vaunter\",\n\"vaunty\",\n\"vauxite\",\n\"vavasor\",\n\"vaward\",\n\"veal\",\n\"vealer\",\n\"vealy\",\n\"vection\",\n\"vectis\",\n\"vector\",\n\"vecture\",\n\"vedana\",\n\"vedette\",\n\"vedika\",\n\"vedro\",\n\"veduis\",\n\"vee\",\n\"veen\",\n\"veep\",\n\"veer\",\n\"veery\",\n\"vegetal\",\n\"vegete\",\n\"vehicle\",\n\"vei\",\n\"veigle\",\n\"veil\",\n\"veiled\",\n\"veiler\",\n\"veiling\",\n\"veily\",\n\"vein\",\n\"veinage\",\n\"veinal\",\n\"veined\",\n\"veiner\",\n\"veinery\",\n\"veining\",\n\"veinlet\",\n\"veinous\",\n\"veinule\",\n\"veiny\",\n\"vejoces\",\n\"vela\",\n\"velal\",\n\"velamen\",\n\"velar\",\n\"velaric\",\n\"velary\",\n\"velate\",\n\"velated\",\n\"veldman\",\n\"veldt\",\n\"velic\",\n\"veliger\",\n\"vell\",\n\"vellala\",\n\"velleda\",\n\"vellon\",\n\"vellum\",\n\"vellumy\",\n\"velo\",\n\"velours\",\n\"velte\",\n\"velum\",\n\"velumen\",\n\"velure\",\n\"velvet\",\n\"velvety\",\n\"venada\",\n\"venal\",\n\"venally\",\n\"venatic\",\n\"venator\",\n\"vencola\",\n\"vend\",\n\"vendace\",\n\"vendee\",\n\"vender\",\n\"vending\",\n\"vendor\",\n\"vendue\",\n\"veneer\",\n\"venene\",\n\"veneral\",\n\"venerer\",\n\"venery\",\n\"venesia\",\n\"venger\",\n\"venial\",\n\"venie\",\n\"venin\",\n\"venison\",\n\"vennel\",\n\"venner\",\n\"venom\",\n\"venomed\",\n\"venomer\",\n\"venomly\",\n\"venomy\",\n\"venosal\",\n\"venose\",\n\"venous\",\n\"vent\",\n\"ventage\",\n\"ventail\",\n\"venter\",\n\"ventil\",\n\"ventose\",\n\"ventrad\",\n\"ventral\",\n\"ventric\",\n\"venture\",\n\"venue\",\n\"venula\",\n\"venular\",\n\"venule\",\n\"venust\",\n\"vera\",\n\"veranda\",\n\"verb\",\n\"verbal\",\n\"verbate\",\n\"verbena\",\n\"verbene\",\n\"verbid\",\n\"verbify\",\n\"verbile\",\n\"verbose\",\n\"verbous\",\n\"verby\",\n\"verchok\",\n\"verd\",\n\"verdant\",\n\"verdea\",\n\"verdet\",\n\"verdict\",\n\"verdin\",\n\"verdoy\",\n\"verdun\",\n\"verdure\",\n\"verek\",\n\"verge\",\n\"vergent\",\n\"verger\",\n\"vergery\",\n\"vergi\",\n\"verglas\",\n\"veri\",\n\"veridic\",\n\"verify\",\n\"verily\",\n\"verine\",\n\"verism\",\n\"verist\",\n\"verite\",\n\"verity\",\n\"vermeil\",\n\"vermian\",\n\"vermin\",\n\"verminy\",\n\"vermis\",\n\"vermix\",\n\"vernal\",\n\"vernant\",\n\"vernier\",\n\"vernile\",\n\"vernin\",\n\"vernine\",\n\"verre\",\n\"verrel\",\n\"verruca\",\n\"verruga\",\n\"versal\",\n\"versant\",\n\"versate\",\n\"verse\",\n\"versed\",\n\"verser\",\n\"verset\",\n\"versify\",\n\"versine\",\n\"version\",\n\"verso\",\n\"versor\",\n\"verst\",\n\"versta\",\n\"versual\",\n\"versus\",\n\"vert\",\n\"vertex\",\n\"vertigo\",\n\"veruled\",\n\"vervain\",\n\"verve\",\n\"vervel\",\n\"vervet\",\n\"very\",\n\"vesania\",\n\"vesanic\",\n\"vesbite\",\n\"vesicae\",\n\"vesical\",\n\"vesicle\",\n\"veskit\",\n\"vespal\",\n\"vesper\",\n\"vespers\",\n\"vespery\",\n\"vespid\",\n\"vespine\",\n\"vespoid\",\n\"vessel\",\n\"vest\",\n\"vestal\",\n\"vestee\",\n\"vester\",\n\"vestige\",\n\"vesting\",\n\"vestlet\",\n\"vestral\",\n\"vestry\",\n\"vesture\",\n\"vet\",\n\"veta\",\n\"vetanda\",\n\"vetch\",\n\"vetchy\",\n\"veteran\",\n\"vetiver\",\n\"veto\",\n\"vetoer\",\n\"vetoism\",\n\"vetoist\",\n\"vetust\",\n\"vetusty\",\n\"veuve\",\n\"vex\",\n\"vexable\",\n\"vexed\",\n\"vexedly\",\n\"vexer\",\n\"vexful\",\n\"vexil\",\n\"vext\",\n\"via\",\n\"viable\",\n\"viaduct\",\n\"viagram\",\n\"viajaca\",\n\"vial\",\n\"vialful\",\n\"viand\",\n\"viander\",\n\"viatic\",\n\"viatica\",\n\"viator\",\n\"vibex\",\n\"vibgyor\",\n\"vibix\",\n\"vibrant\",\n\"vibrate\",\n\"vibrato\",\n\"vibrion\",\n\"vicar\",\n\"vicarly\",\n\"vice\",\n\"viceroy\",\n\"vicety\",\n\"vicilin\",\n\"vicinal\",\n\"vicine\",\n\"vicious\",\n\"vicoite\",\n\"victim\",\n\"victor\",\n\"victory\",\n\"victrix\",\n\"victual\",\n\"vicuna\",\n\"viddui\",\n\"video\",\n\"vidette\",\n\"vidonia\",\n\"vidry\",\n\"viduage\",\n\"vidual\",\n\"viduate\",\n\"viduine\",\n\"viduity\",\n\"viduous\",\n\"vidya\",\n\"vie\",\n\"vielle\",\n\"vier\",\n\"viertel\",\n\"view\",\n\"viewer\",\n\"viewly\",\n\"viewy\",\n\"vifda\",\n\"viga\",\n\"vigia\",\n\"vigil\",\n\"vignin\",\n\"vigonia\",\n\"vigor\",\n\"vihara\",\n\"vihuela\",\n\"vijao\",\n\"viking\",\n\"vila\",\n\"vilayet\",\n\"vile\",\n\"vilely\",\n\"vilify\",\n\"vility\",\n\"vill\",\n\"villa\",\n\"village\",\n\"villain\",\n\"villar\",\n\"villate\",\n\"ville\",\n\"villein\",\n\"villoid\",\n\"villose\",\n\"villous\",\n\"villus\",\n\"vim\",\n\"vimana\",\n\"vimen\",\n\"vimful\",\n\"viminal\",\n\"vina\",\n\"vinage\",\n\"vinal\",\n\"vinasse\",\n\"vinata\",\n\"vincent\",\n\"vindex\",\n\"vine\",\n\"vinea\",\n\"vineal\",\n\"vined\",\n\"vinegar\",\n\"vineity\",\n\"vinelet\",\n\"viner\",\n\"vinery\",\n\"vinic\",\n\"vinny\",\n\"vino\",\n\"vinose\",\n\"vinous\",\n\"vint\",\n\"vinta\",\n\"vintage\",\n\"vintem\",\n\"vintner\",\n\"vintry\",\n\"viny\",\n\"vinyl\",\n\"vinylic\",\n\"viol\",\n\"viola\",\n\"violal\",\n\"violate\",\n\"violent\",\n\"violer\",\n\"violet\",\n\"violety\",\n\"violin\",\n\"violina\",\n\"violine\",\n\"violist\",\n\"violon\",\n\"violone\",\n\"viper\",\n\"viperan\",\n\"viperid\",\n\"vipery\",\n\"viqueen\",\n\"viragin\",\n\"virago\",\n\"viral\",\n\"vire\",\n\"virelay\",\n\"viremia\",\n\"viremic\",\n\"virent\",\n\"vireo\",\n\"virga\",\n\"virgal\",\n\"virgate\",\n\"virgin\",\n\"virgula\",\n\"virgule\",\n\"virial\",\n\"virid\",\n\"virific\",\n\"virify\",\n\"virile\",\n\"virl\",\n\"virole\",\n\"viroled\",\n\"viron\",\n\"virose\",\n\"virosis\",\n\"virous\",\n\"virtu\",\n\"virtual\",\n\"virtue\",\n\"virtued\",\n\"viruela\",\n\"virus\",\n\"vis\",\n\"visa\",\n\"visage\",\n\"visaged\",\n\"visarga\",\n\"viscera\",\n\"viscid\",\n\"viscin\",\n\"viscose\",\n\"viscous\",\n\"viscus\",\n\"vise\",\n\"viseman\",\n\"visible\",\n\"visibly\",\n\"visie\",\n\"visile\",\n\"vision\",\n\"visit\",\n\"visita\",\n\"visite\",\n\"visitee\",\n\"visiter\",\n\"visitor\",\n\"visive\",\n\"visne\",\n\"vison\",\n\"visor\",\n\"vista\",\n\"vistaed\",\n\"vistal\",\n\"visto\",\n\"visual\",\n\"vita\",\n\"vital\",\n\"vitalic\",\n\"vitally\",\n\"vitals\",\n\"vitamer\",\n\"vitamin\",\n\"vitasti\",\n\"vitiate\",\n\"vitium\",\n\"vitrage\",\n\"vitrail\",\n\"vitrain\",\n\"vitraux\",\n\"vitreal\",\n\"vitrean\",\n\"vitreum\",\n\"vitric\",\n\"vitrics\",\n\"vitrify\",\n\"vitrine\",\n\"vitriol\",\n\"vitrite\",\n\"vitrous\",\n\"vitta\",\n\"vittate\",\n\"vitular\",\n\"viuva\",\n\"viva\",\n\"vivary\",\n\"vivax\",\n\"vive\",\n\"vively\",\n\"vivency\",\n\"viver\",\n\"vivers\",\n\"vives\",\n\"vivid\",\n\"vividly\",\n\"vivific\",\n\"vivify\",\n\"vixen\",\n\"vixenly\",\n\"vizard\",\n\"vizier\",\n\"vlei\",\n\"voar\",\n\"vocable\",\n\"vocably\",\n\"vocal\",\n\"vocalic\",\n\"vocally\",\n\"vocate\",\n\"vocular\",\n\"vocule\",\n\"vodka\",\n\"voe\",\n\"voet\",\n\"voeten\",\n\"vog\",\n\"voglite\",\n\"vogue\",\n\"voguey\",\n\"voguish\",\n\"voice\",\n\"voiced\",\n\"voicer\",\n\"voicing\",\n\"void\",\n\"voided\",\n\"voidee\",\n\"voider\",\n\"voiding\",\n\"voidly\",\n\"voile\",\n\"voivode\",\n\"vol\",\n\"volable\",\n\"volage\",\n\"volant\",\n\"volar\",\n\"volata\",\n\"volatic\",\n\"volcan\",\n\"volcano\",\n\"vole\",\n\"volency\",\n\"volent\",\n\"volery\",\n\"volet\",\n\"volley\",\n\"volost\",\n\"volt\",\n\"voltage\",\n\"voltaic\",\n\"voltize\",\n\"voluble\",\n\"volubly\",\n\"volume\",\n\"volumed\",\n\"volupt\",\n\"volupty\",\n\"voluta\",\n\"volute\",\n\"voluted\",\n\"volutin\",\n\"volva\",\n\"volvate\",\n\"volvent\",\n\"vomer\",\n\"vomica\",\n\"vomit\",\n\"vomiter\",\n\"vomito\",\n\"vomitus\",\n\"voodoo\",\n\"vorago\",\n\"vorant\",\n\"vorhand\",\n\"vorpal\",\n\"vortex\",\n\"vota\",\n\"votable\",\n\"votal\",\n\"votally\",\n\"votary\",\n\"vote\",\n\"voteen\",\n\"voter\",\n\"voting\",\n\"votive\",\n\"votress\",\n\"vouch\",\n\"vouchee\",\n\"voucher\",\n\"vouge\",\n\"vow\",\n\"vowed\",\n\"vowel\",\n\"vowely\",\n\"vower\",\n\"vowess\",\n\"vowless\",\n\"voyage\",\n\"voyager\",\n\"voyance\",\n\"voyeur\",\n\"vraic\",\n\"vrbaite\",\n\"vriddhi\",\n\"vrother\",\n\"vug\",\n\"vuggy\",\n\"vulgar\",\n\"vulgare\",\n\"vulgate\",\n\"vulgus\",\n\"vuln\",\n\"vulnose\",\n\"vulpic\",\n\"vulpine\",\n\"vulture\",\n\"vulturn\",\n\"vulva\",\n\"vulval\",\n\"vulvar\",\n\"vulvate\",\n\"vum\",\n\"vying\",\n\"vyingly\",\n\"w\",\n\"wa\",\n\"waag\",\n\"waapa\",\n\"waar\",\n\"wab\",\n\"wabber\",\n\"wabble\",\n\"wabbly\",\n\"wabby\",\n\"wabe\",\n\"wabeno\",\n\"wabster\",\n\"wacago\",\n\"wace\",\n\"wachna\",\n\"wack\",\n\"wacke\",\n\"wacken\",\n\"wacker\",\n\"wacky\",\n\"wad\",\n\"waddent\",\n\"wadder\",\n\"wadding\",\n\"waddler\",\n\"waddly\",\n\"waddy\",\n\"wade\",\n\"wader\",\n\"wadi\",\n\"wading\",\n\"wadlike\",\n\"wadmal\",\n\"wadmeal\",\n\"wadna\",\n\"wadset\",\n\"wae\",\n\"waeg\",\n\"waer\",\n\"waesome\",\n\"waesuck\",\n\"wafer\",\n\"waferer\",\n\"wafery\",\n\"waff\",\n\"waffle\",\n\"waffly\",\n\"waft\",\n\"waftage\",\n\"wafter\",\n\"wafture\",\n\"wafty\",\n\"wag\",\n\"wagaun\",\n\"wage\",\n\"waged\",\n\"wagedom\",\n\"wager\",\n\"wagerer\",\n\"wages\",\n\"waggel\",\n\"wagger\",\n\"waggery\",\n\"waggie\",\n\"waggish\",\n\"waggle\",\n\"waggly\",\n\"waggy\",\n\"waglike\",\n\"wagling\",\n\"wagon\",\n\"wagoner\",\n\"wagonry\",\n\"wagsome\",\n\"wagtail\",\n\"wagwag\",\n\"wagwit\",\n\"wah\",\n\"wahahe\",\n\"wahine\",\n\"wahoo\",\n\"waiata\",\n\"waif\",\n\"waik\",\n\"waikly\",\n\"wail\",\n\"wailer\",\n\"wailful\",\n\"waily\",\n\"wain\",\n\"wainage\",\n\"wainer\",\n\"wainful\",\n\"wainman\",\n\"waipiro\",\n\"wairch\",\n\"waird\",\n\"wairepo\",\n\"wairsh\",\n\"waise\",\n\"waist\",\n\"waisted\",\n\"waister\",\n\"wait\",\n\"waiter\",\n\"waiting\",\n\"waive\",\n\"waiver\",\n\"waivery\",\n\"waivod\",\n\"waiwode\",\n\"wajang\",\n\"waka\",\n\"wakan\",\n\"wake\",\n\"wakeel\",\n\"wakeful\",\n\"waken\",\n\"wakener\",\n\"waker\",\n\"wakes\",\n\"wakf\",\n\"wakif\",\n\"wakiki\",\n\"waking\",\n\"wakiup\",\n\"wakken\",\n\"wakon\",\n\"wakonda\",\n\"waky\",\n\"walahee\",\n\"wale\",\n\"waled\",\n\"waler\",\n\"wali\",\n\"waling\",\n\"walk\",\n\"walker\",\n\"walking\",\n\"walkist\",\n\"walkout\",\n\"walkway\",\n\"wall\",\n\"wallaba\",\n\"wallaby\",\n\"wallah\",\n\"walled\",\n\"waller\",\n\"wallet\",\n\"walleye\",\n\"wallful\",\n\"walling\",\n\"wallise\",\n\"wallman\",\n\"walloon\",\n\"wallop\",\n\"wallow\",\n\"wally\",\n\"walnut\",\n\"walrus\",\n\"walsh\",\n\"walt\",\n\"walter\",\n\"walth\",\n\"waltz\",\n\"waltzer\",\n\"wamara\",\n\"wambais\",\n\"wamble\",\n\"wambly\",\n\"wame\",\n\"wamefou\",\n\"wamel\",\n\"wamp\",\n\"wampee\",\n\"wample\",\n\"wampum\",\n\"wampus\",\n\"wamus\",\n\"wan\",\n\"wand\",\n\"wander\",\n\"wandery\",\n\"wandle\",\n\"wandoo\",\n\"wandy\",\n\"wane\",\n\"waned\",\n\"wang\",\n\"wanga\",\n\"wangala\",\n\"wangan\",\n\"wanghee\",\n\"wangle\",\n\"wangler\",\n\"wanhope\",\n\"wanhorn\",\n\"wanigan\",\n\"waning\",\n\"wankle\",\n\"wankly\",\n\"wanle\",\n\"wanly\",\n\"wanner\",\n\"wanness\",\n\"wannish\",\n\"wanny\",\n\"wanrufe\",\n\"want\",\n\"wantage\",\n\"wanter\",\n\"wantful\",\n\"wanting\",\n\"wanton\",\n\"wantwit\",\n\"wanty\",\n\"wany\",\n\"wap\",\n\"wapacut\",\n\"wapatoo\",\n\"wapiti\",\n\"wapp\",\n\"wapper\",\n\"wapping\",\n\"war\",\n\"warabi\",\n\"waratah\",\n\"warble\",\n\"warbled\",\n\"warbler\",\n\"warblet\",\n\"warbly\",\n\"warch\",\n\"ward\",\n\"wardage\",\n\"warday\",\n\"warded\",\n\"warden\",\n\"warder\",\n\"warding\",\n\"wardite\",\n\"wardman\",\n\"ware\",\n\"warehou\",\n\"wareman\",\n\"warf\",\n\"warfare\",\n\"warful\",\n\"warily\",\n\"warish\",\n\"warison\",\n\"wark\",\n\"warl\",\n\"warless\",\n\"warlike\",\n\"warlock\",\n\"warluck\",\n\"warly\",\n\"warm\",\n\"warman\",\n\"warmed\",\n\"warmer\",\n\"warmful\",\n\"warming\",\n\"warmish\",\n\"warmly\",\n\"warmth\",\n\"warmus\",\n\"warn\",\n\"warnel\",\n\"warner\",\n\"warning\",\n\"warnish\",\n\"warnoth\",\n\"warnt\",\n\"warp\",\n\"warpage\",\n\"warped\",\n\"warper\",\n\"warping\",\n\"warple\",\n\"warran\",\n\"warrand\",\n\"warrant\",\n\"warree\",\n\"warren\",\n\"warrer\",\n\"warrin\",\n\"warrior\",\n\"warrok\",\n\"warsaw\",\n\"warse\",\n\"warsel\",\n\"warship\",\n\"warsle\",\n\"warsler\",\n\"warst\",\n\"wart\",\n\"warted\",\n\"wartern\",\n\"warth\",\n\"wartime\",\n\"wartlet\",\n\"warty\",\n\"warve\",\n\"warwolf\",\n\"warworn\",\n\"wary\",\n\"was\",\n\"wasabi\",\n\"wase\",\n\"wasel\",\n\"wash\",\n\"washday\",\n\"washed\",\n\"washen\",\n\"washer\",\n\"washery\",\n\"washin\",\n\"washing\",\n\"washman\",\n\"washoff\",\n\"washout\",\n\"washpot\",\n\"washrag\",\n\"washtub\",\n\"washway\",\n\"washy\",\n\"wasnt\",\n\"wasp\",\n\"waspen\",\n\"waspily\",\n\"waspish\",\n\"waspy\",\n\"wassail\",\n\"wassie\",\n\"wast\",\n\"wastage\",\n\"waste\",\n\"wasted\",\n\"wastel\",\n\"waster\",\n\"wasting\",\n\"wastrel\",\n\"wasty\",\n\"wat\",\n\"watap\",\n\"watch\",\n\"watched\",\n\"watcher\",\n\"water\",\n\"watered\",\n\"waterer\",\n\"waterie\",\n\"watery\",\n\"wath\",\n\"watt\",\n\"wattage\",\n\"wattape\",\n\"wattle\",\n\"wattled\",\n\"wattman\",\n\"wauble\",\n\"wauch\",\n\"wauchle\",\n\"waucht\",\n\"wauf\",\n\"waugh\",\n\"waughy\",\n\"wauken\",\n\"waukit\",\n\"waul\",\n\"waumle\",\n\"wauner\",\n\"wauns\",\n\"waup\",\n\"waur\",\n\"wauve\",\n\"wavable\",\n\"wavably\",\n\"wave\",\n\"waved\",\n\"wavelet\",\n\"waver\",\n\"waverer\",\n\"wavery\",\n\"waveson\",\n\"wavey\",\n\"wavicle\",\n\"wavily\",\n\"waving\",\n\"wavy\",\n\"waw\",\n\"wawa\",\n\"wawah\",\n\"wax\",\n\"waxbill\",\n\"waxbird\",\n\"waxbush\",\n\"waxen\",\n\"waxer\",\n\"waxily\",\n\"waxing\",\n\"waxlike\",\n\"waxman\",\n\"waxweed\",\n\"waxwing\",\n\"waxwork\",\n\"waxy\",\n\"way\",\n\"wayaka\",\n\"wayang\",\n\"wayback\",\n\"waybill\",\n\"waybird\",\n\"waybook\",\n\"waybung\",\n\"wayfare\",\n\"waygang\",\n\"waygate\",\n\"waygone\",\n\"waying\",\n\"waylaid\",\n\"waylay\",\n\"wayless\",\n\"wayman\",\n\"waymark\",\n\"waymate\",\n\"waypost\",\n\"ways\",\n\"wayside\",\n\"wayward\",\n\"waywode\",\n\"wayworn\",\n\"waywort\",\n\"we\",\n\"weak\",\n\"weaken\",\n\"weakish\",\n\"weakly\",\n\"weaky\",\n\"weal\",\n\"weald\",\n\"wealth\",\n\"wealthy\",\n\"weam\",\n\"wean\",\n\"weanel\",\n\"weaner\",\n\"weanyer\",\n\"weapon\",\n\"wear\",\n\"wearer\",\n\"wearied\",\n\"wearier\",\n\"wearily\",\n\"wearing\",\n\"wearish\",\n\"weary\",\n\"weasand\",\n\"weasel\",\n\"weaser\",\n\"weason\",\n\"weather\",\n\"weave\",\n\"weaved\",\n\"weaver\",\n\"weaving\",\n\"weazen\",\n\"weazeny\",\n\"web\",\n\"webbed\",\n\"webber\",\n\"webbing\",\n\"webby\",\n\"weber\",\n\"webeye\",\n\"webfoot\",\n\"webless\",\n\"weblike\",\n\"webster\",\n\"webwork\",\n\"webworm\",\n\"wecht\",\n\"wed\",\n\"wedana\",\n\"wedbed\",\n\"wedded\",\n\"wedder\",\n\"wedding\",\n\"wede\",\n\"wedge\",\n\"wedged\",\n\"wedger\",\n\"wedging\",\n\"wedgy\",\n\"wedlock\",\n\"wedset\",\n\"wee\",\n\"weeble\",\n\"weed\",\n\"weeda\",\n\"weedage\",\n\"weeded\",\n\"weeder\",\n\"weedery\",\n\"weedful\",\n\"weedish\",\n\"weedow\",\n\"weedy\",\n\"week\",\n\"weekday\",\n\"weekend\",\n\"weekly\",\n\"weekwam\",\n\"weel\",\n\"weemen\",\n\"ween\",\n\"weeness\",\n\"weening\",\n\"weenong\",\n\"weeny\",\n\"weep\",\n\"weeper\",\n\"weepful\",\n\"weeping\",\n\"weeps\",\n\"weepy\",\n\"weesh\",\n\"weeshy\",\n\"weet\",\n\"weever\",\n\"weevil\",\n\"weevily\",\n\"weewow\",\n\"weeze\",\n\"weft\",\n\"weftage\",\n\"wefted\",\n\"wefty\",\n\"weigh\",\n\"weighed\",\n\"weigher\",\n\"weighin\",\n\"weight\",\n\"weighty\",\n\"weir\",\n\"weird\",\n\"weirdly\",\n\"weiring\",\n\"weism\",\n\"wejack\",\n\"weka\",\n\"wekau\",\n\"wekeen\",\n\"weki\",\n\"welcome\",\n\"weld\",\n\"welder\",\n\"welding\",\n\"weldor\",\n\"welfare\",\n\"welk\",\n\"welkin\",\n\"well\",\n\"wellat\",\n\"welling\",\n\"wellish\",\n\"wellman\",\n\"welly\",\n\"wels\",\n\"welsh\",\n\"welsher\",\n\"welsium\",\n\"welt\",\n\"welted\",\n\"welter\",\n\"welting\",\n\"wem\",\n\"wemless\",\n\"wen\",\n\"wench\",\n\"wencher\",\n\"wend\",\n\"wende\",\n\"wene\",\n\"wennish\",\n\"wenny\",\n\"went\",\n\"wenzel\",\n\"wept\",\n\"wer\",\n\"were\",\n\"werefox\",\n\"werent\",\n\"werf\",\n\"wergil\",\n\"weri\",\n\"wert\",\n\"wervel\",\n\"wese\",\n\"weskit\",\n\"west\",\n\"weste\",\n\"wester\",\n\"western\",\n\"westing\",\n\"westy\",\n\"wet\",\n\"weta\",\n\"wetback\",\n\"wetbird\",\n\"wetched\",\n\"wetchet\",\n\"wether\",\n\"wetly\",\n\"wetness\",\n\"wetted\",\n\"wetter\",\n\"wetting\",\n\"wettish\",\n\"weve\",\n\"wevet\",\n\"wey\",\n\"wha\",\n\"whabby\",\n\"whack\",\n\"whacker\",\n\"whacky\",\n\"whale\",\n\"whaler\",\n\"whalery\",\n\"whaling\",\n\"whalish\",\n\"whally\",\n\"whalm\",\n\"whalp\",\n\"whaly\",\n\"wham\",\n\"whamble\",\n\"whame\",\n\"whammle\",\n\"whamp\",\n\"whampee\",\n\"whample\",\n\"whan\",\n\"whand\",\n\"whang\",\n\"whangam\",\n\"whangee\",\n\"whank\",\n\"whap\",\n\"whappet\",\n\"whapuka\",\n\"whapuku\",\n\"whar\",\n\"whare\",\n\"whareer\",\n\"wharf\",\n\"wharl\",\n\"wharp\",\n\"wharry\",\n\"whart\",\n\"wharve\",\n\"whase\",\n\"whasle\",\n\"what\",\n\"whata\",\n\"whatkin\",\n\"whatna\",\n\"whatnot\",\n\"whats\",\n\"whatso\",\n\"whatten\",\n\"whau\",\n\"whauk\",\n\"whaup\",\n\"whaur\",\n\"whauve\",\n\"wheal\",\n\"whealy\",\n\"wheam\",\n\"wheat\",\n\"wheaten\",\n\"wheaty\",\n\"whedder\",\n\"whee\",\n\"wheedle\",\n\"wheel\",\n\"wheeled\",\n\"wheeler\",\n\"wheely\",\n\"wheem\",\n\"wheen\",\n\"wheenge\",\n\"wheep\",\n\"wheeple\",\n\"wheer\",\n\"wheesht\",\n\"wheetle\",\n\"wheeze\",\n\"wheezer\",\n\"wheezle\",\n\"wheezy\",\n\"wheft\",\n\"whein\",\n\"whekau\",\n\"wheki\",\n\"whelk\",\n\"whelked\",\n\"whelker\",\n\"whelky\",\n\"whelm\",\n\"whelp\",\n\"whelve\",\n\"whemmel\",\n\"when\",\n\"whenas\",\n\"whence\",\n\"wheneer\",\n\"whenso\",\n\"where\",\n\"whereas\",\n\"whereat\",\n\"whereby\",\n\"whereer\",\n\"wherein\",\n\"whereof\",\n\"whereon\",\n\"whereso\",\n\"whereto\",\n\"whereup\",\n\"wherret\",\n\"wherrit\",\n\"wherry\",\n\"whet\",\n\"whether\",\n\"whetile\",\n\"whetter\",\n\"whew\",\n\"whewer\",\n\"whewl\",\n\"whewt\",\n\"whey\",\n\"wheyey\",\n\"wheyish\",\n\"whiba\",\n\"which\",\n\"whick\",\n\"whicken\",\n\"whicker\",\n\"whid\",\n\"whidah\",\n\"whidder\",\n\"whiff\",\n\"whiffer\",\n\"whiffet\",\n\"whiffle\",\n\"whiffy\",\n\"whift\",\n\"whig\",\n\"while\",\n\"whileen\",\n\"whilere\",\n\"whiles\",\n\"whilie\",\n\"whilk\",\n\"whill\",\n\"whilly\",\n\"whilock\",\n\"whilom\",\n\"whils\",\n\"whilst\",\n\"whilter\",\n\"whim\",\n\"whimble\",\n\"whimmy\",\n\"whimper\",\n\"whimsey\",\n\"whimsic\",\n\"whin\",\n\"whincow\",\n\"whindle\",\n\"whine\",\n\"whiner\",\n\"whing\",\n\"whinge\",\n\"whinger\",\n\"whinnel\",\n\"whinner\",\n\"whinny\",\n\"whiny\",\n\"whip\",\n\"whipcat\",\n\"whipman\",\n\"whippa\",\n\"whipped\",\n\"whipper\",\n\"whippet\",\n\"whippy\",\n\"whipsaw\",\n\"whipt\",\n\"whir\",\n\"whirken\",\n\"whirl\",\n\"whirled\",\n\"whirler\",\n\"whirley\",\n\"whirly\",\n\"whirret\",\n\"whirrey\",\n\"whirroo\",\n\"whirry\",\n\"whirtle\",\n\"whish\",\n\"whisk\",\n\"whisker\",\n\"whiskey\",\n\"whisky\",\n\"whisp\",\n\"whisper\",\n\"whissle\",\n\"whist\",\n\"whister\",\n\"whistle\",\n\"whistly\",\n\"whit\",\n\"white\",\n\"whited\",\n\"whitely\",\n\"whiten\",\n\"whites\",\n\"whither\",\n\"whiting\",\n\"whitish\",\n\"whitlow\",\n\"whits\",\n\"whittaw\",\n\"whitten\",\n\"whitter\",\n\"whittle\",\n\"whity\",\n\"whiz\",\n\"whizgig\",\n\"whizzer\",\n\"whizzle\",\n\"who\",\n\"whoa\",\n\"whoever\",\n\"whole\",\n\"wholly\",\n\"whom\",\n\"whomble\",\n\"whomso\",\n\"whone\",\n\"whoo\",\n\"whoof\",\n\"whoop\",\n\"whoopee\",\n\"whooper\",\n\"whoops\",\n\"whoosh\",\n\"whop\",\n\"whopper\",\n\"whorage\",\n\"whore\",\n\"whorish\",\n\"whorl\",\n\"whorled\",\n\"whorly\",\n\"whort\",\n\"whortle\",\n\"whose\",\n\"whosen\",\n\"whud\",\n\"whuff\",\n\"whuffle\",\n\"whulk\",\n\"whulter\",\n\"whummle\",\n\"whun\",\n\"whup\",\n\"whush\",\n\"whuskie\",\n\"whussle\",\n\"whute\",\n\"whuther\",\n\"whutter\",\n\"whuz\",\n\"why\",\n\"whyever\",\n\"whyfor\",\n\"whyness\",\n\"whyo\",\n\"wi\",\n\"wice\",\n\"wicht\",\n\"wichtje\",\n\"wick\",\n\"wicked\",\n\"wicken\",\n\"wicker\",\n\"wicket\",\n\"wicking\",\n\"wickiup\",\n\"wickup\",\n\"wicky\",\n\"wicopy\",\n\"wid\",\n\"widbin\",\n\"widder\",\n\"widdle\",\n\"widdy\",\n\"wide\",\n\"widegab\",\n\"widely\",\n\"widen\",\n\"widener\",\n\"widgeon\",\n\"widish\",\n\"widow\",\n\"widowed\",\n\"widower\",\n\"widowly\",\n\"widowy\",\n\"width\",\n\"widu\",\n\"wield\",\n\"wielder\",\n\"wieldy\",\n\"wiener\",\n\"wienie\",\n\"wife\",\n\"wifedom\",\n\"wifeism\",\n\"wifekin\",\n\"wifelet\",\n\"wifely\",\n\"wifie\",\n\"wifish\",\n\"wifock\",\n\"wig\",\n\"wigan\",\n\"wigdom\",\n\"wigful\",\n\"wigged\",\n\"wiggen\",\n\"wigger\",\n\"wiggery\",\n\"wigging\",\n\"wiggish\",\n\"wiggism\",\n\"wiggle\",\n\"wiggler\",\n\"wiggly\",\n\"wiggy\",\n\"wight\",\n\"wightly\",\n\"wigless\",\n\"wiglet\",\n\"wiglike\",\n\"wigtail\",\n\"wigwag\",\n\"wigwam\",\n\"wiikite\",\n\"wild\",\n\"wildcat\",\n\"wilded\",\n\"wilder\",\n\"wilding\",\n\"wildish\",\n\"wildly\",\n\"wile\",\n\"wileful\",\n\"wilga\",\n\"wilgers\",\n\"wilily\",\n\"wilk\",\n\"wilkin\",\n\"will\",\n\"willawa\",\n\"willed\",\n\"willer\",\n\"willet\",\n\"willey\",\n\"willful\",\n\"willie\",\n\"willier\",\n\"willies\",\n\"willing\",\n\"willock\",\n\"willow\",\n\"willowy\",\n\"willy\",\n\"willyer\",\n\"wilsome\",\n\"wilt\",\n\"wilter\",\n\"wily\",\n\"wim\",\n\"wimble\",\n\"wimbrel\",\n\"wime\",\n\"wimick\",\n\"wimple\",\n\"win\",\n\"wince\",\n\"wincer\",\n\"wincey\",\n\"winch\",\n\"wincher\",\n\"wincing\",\n\"wind\",\n\"windage\",\n\"windbag\",\n\"winddog\",\n\"winded\",\n\"winder\",\n\"windigo\",\n\"windily\",\n\"winding\",\n\"windle\",\n\"windles\",\n\"windlin\",\n\"windock\",\n\"windore\",\n\"window\",\n\"windowy\",\n\"windrow\",\n\"windup\",\n\"windway\",\n\"windy\",\n\"wine\",\n\"wined\",\n\"winemay\",\n\"winepot\",\n\"winer\",\n\"winery\",\n\"winesop\",\n\"winevat\",\n\"winful\",\n\"wing\",\n\"wingcut\",\n\"winged\",\n\"winger\",\n\"wingle\",\n\"winglet\",\n\"wingman\",\n\"wingy\",\n\"winish\",\n\"wink\",\n\"winkel\",\n\"winker\",\n\"winking\",\n\"winkle\",\n\"winklet\",\n\"winly\",\n\"winna\",\n\"winnard\",\n\"winnel\",\n\"winner\",\n\"winning\",\n\"winnle\",\n\"winnow\",\n\"winrace\",\n\"winrow\",\n\"winsome\",\n\"wint\",\n\"winter\",\n\"wintle\",\n\"wintry\",\n\"winy\",\n\"winze\",\n\"wipe\",\n\"wiper\",\n\"wippen\",\n\"wips\",\n\"wir\",\n\"wirable\",\n\"wirble\",\n\"wird\",\n\"wire\",\n\"wirebar\",\n\"wired\",\n\"wireman\",\n\"wirer\",\n\"wireway\",\n\"wirily\",\n\"wiring\",\n\"wirl\",\n\"wirling\",\n\"wirr\",\n\"wirra\",\n\"wirrah\",\n\"wiry\",\n\"wis\",\n\"wisdom\",\n\"wise\",\n\"wisely\",\n\"wiseman\",\n\"wisen\",\n\"wisent\",\n\"wiser\",\n\"wish\",\n\"wisha\",\n\"wished\",\n\"wisher\",\n\"wishful\",\n\"wishing\",\n\"wishly\",\n\"wishmay\",\n\"wisht\",\n\"wisket\",\n\"wisp\",\n\"wispish\",\n\"wispy\",\n\"wiss\",\n\"wisse\",\n\"wissel\",\n\"wist\",\n\"wiste\",\n\"wistful\",\n\"wistit\",\n\"wistiti\",\n\"wit\",\n\"witan\",\n\"witch\",\n\"witched\",\n\"witchen\",\n\"witchet\",\n\"witchy\",\n\"wite\",\n\"witess\",\n\"witful\",\n\"with\",\n\"withal\",\n\"withe\",\n\"withen\",\n\"wither\",\n\"withers\",\n\"withery\",\n\"within\",\n\"without\",\n\"withy\",\n\"witjar\",\n\"witless\",\n\"witlet\",\n\"witling\",\n\"witloof\",\n\"witness\",\n\"witney\",\n\"witship\",\n\"wittal\",\n\"witted\",\n\"witter\",\n\"wittily\",\n\"witting\",\n\"wittol\",\n\"witty\",\n\"witwall\",\n\"wive\",\n\"wiver\",\n\"wivern\",\n\"wiz\",\n\"wizard\",\n\"wizen\",\n\"wizened\",\n\"wizier\",\n\"wizzen\",\n\"wloka\",\n\"wo\",\n\"woad\",\n\"woader\",\n\"woadman\",\n\"woady\",\n\"woak\",\n\"woald\",\n\"woan\",\n\"wob\",\n\"wobble\",\n\"wobbler\",\n\"wobbly\",\n\"wobster\",\n\"wod\",\n\"woddie\",\n\"wode\",\n\"wodge\",\n\"wodgy\",\n\"woe\",\n\"woeful\",\n\"woesome\",\n\"woevine\",\n\"woeworn\",\n\"woffler\",\n\"woft\",\n\"wog\",\n\"wogiet\",\n\"woibe\",\n\"wokas\",\n\"woke\",\n\"wokowi\",\n\"wold\",\n\"woldy\",\n\"wolf\",\n\"wolfdom\",\n\"wolfen\",\n\"wolfer\",\n\"wolfish\",\n\"wolfkin\",\n\"wolfram\",\n\"wollop\",\n\"wolter\",\n\"wolve\",\n\"wolver\",\n\"woman\",\n\"womanly\",\n\"womb\",\n\"wombat\",\n\"wombed\",\n\"womble\",\n\"womby\",\n\"womera\",\n\"won\",\n\"wonder\",\n\"wone\",\n\"wonegan\",\n\"wong\",\n\"wonga\",\n\"wongen\",\n\"wongshy\",\n\"wongsky\",\n\"woning\",\n\"wonky\",\n\"wonna\",\n\"wonned\",\n\"wonner\",\n\"wonning\",\n\"wonnot\",\n\"wont\",\n\"wonted\",\n\"wonting\",\n\"woo\",\n\"wooable\",\n\"wood\",\n\"woodbin\",\n\"woodcut\",\n\"wooded\",\n\"wooden\",\n\"woodeny\",\n\"woodine\",\n\"wooding\",\n\"woodish\",\n\"woodlet\",\n\"woodly\",\n\"woodman\",\n\"woodrow\",\n\"woodsy\",\n\"woodwax\",\n\"woody\",\n\"wooer\",\n\"woof\",\n\"woofed\",\n\"woofell\",\n\"woofer\",\n\"woofy\",\n\"woohoo\",\n\"wooing\",\n\"wool\",\n\"woold\",\n\"woolder\",\n\"wooled\",\n\"woolen\",\n\"wooler\",\n\"woolert\",\n\"woolly\",\n\"woolman\",\n\"woolsey\",\n\"woom\",\n\"woomer\",\n\"woon\",\n\"woons\",\n\"woorali\",\n\"woorari\",\n\"woosh\",\n\"wootz\",\n\"woozle\",\n\"woozy\",\n\"wop\",\n\"woppish\",\n\"wops\",\n\"worble\",\n\"word\",\n\"wordage\",\n\"worded\",\n\"worder\",\n\"wordily\",\n\"wording\",\n\"wordish\",\n\"wordle\",\n\"wordman\",\n\"wordy\",\n\"wore\",\n\"work\",\n\"workbag\",\n\"workbox\",\n\"workday\",\n\"worked\",\n\"worker\",\n\"working\",\n\"workman\",\n\"workout\",\n\"workpan\",\n\"works\",\n\"worky\",\n\"world\",\n\"worlded\",\n\"worldly\",\n\"worldy\",\n\"worm\",\n\"wormed\",\n\"wormer\",\n\"wormil\",\n\"worming\",\n\"wormy\",\n\"worn\",\n\"wornil\",\n\"worral\",\n\"worried\",\n\"worrier\",\n\"worrit\",\n\"worry\",\n\"worse\",\n\"worsen\",\n\"worser\",\n\"worset\",\n\"worship\",\n\"worst\",\n\"worsted\",\n\"wort\",\n\"worth\",\n\"worthy\",\n\"wosbird\",\n\"wot\",\n\"wote\",\n\"wots\",\n\"wottest\",\n\"wotteth\",\n\"woubit\",\n\"wouch\",\n\"wouf\",\n\"wough\",\n\"would\",\n\"wouldnt\",\n\"wouldst\",\n\"wound\",\n\"wounded\",\n\"wounder\",\n\"wounds\",\n\"woundy\",\n\"wourali\",\n\"wourari\",\n\"wournil\",\n\"wove\",\n\"woven\",\n\"wow\",\n\"wowser\",\n\"wowsery\",\n\"wowt\",\n\"woy\",\n\"wrack\",\n\"wracker\",\n\"wraggle\",\n\"wraith\",\n\"wraithe\",\n\"wraithy\",\n\"wraitly\",\n\"wramp\",\n\"wran\",\n\"wrang\",\n\"wrangle\",\n\"wranny\",\n\"wrap\",\n\"wrapped\",\n\"wrapper\",\n\"wrasse\",\n\"wrastle\",\n\"wrath\",\n\"wrathy\",\n\"wraw\",\n\"wrawl\",\n\"wrawler\",\n\"wraxle\",\n\"wreak\",\n\"wreat\",\n\"wreath\",\n\"wreathe\",\n\"wreathy\",\n\"wreck\",\n\"wrecker\",\n\"wrecky\",\n\"wren\",\n\"wrench\",\n\"wrenlet\",\n\"wrest\",\n\"wrester\",\n\"wrestle\",\n\"wretch\",\n\"wricht\",\n\"wrick\",\n\"wride\",\n\"wried\",\n\"wrier\",\n\"wriest\",\n\"wrig\",\n\"wriggle\",\n\"wriggly\",\n\"wright\",\n\"wring\",\n\"wringer\",\n\"wrinkle\",\n\"wrinkly\",\n\"wrist\",\n\"wristed\",\n\"wrister\",\n\"writ\",\n\"write\",\n\"writee\",\n\"writer\",\n\"writh\",\n\"writhe\",\n\"writhed\",\n\"writhen\",\n\"writher\",\n\"writhy\",\n\"writing\",\n\"written\",\n\"writter\",\n\"wrive\",\n\"wro\",\n\"wrocht\",\n\"wroke\",\n\"wroken\",\n\"wrong\",\n\"wronged\",\n\"wronger\",\n\"wrongly\",\n\"wrossle\",\n\"wrote\",\n\"wroth\",\n\"wrothly\",\n\"wrothy\",\n\"wrought\",\n\"wrox\",\n\"wrung\",\n\"wry\",\n\"wrybill\",\n\"wryly\",\n\"wryneck\",\n\"wryness\",\n\"wrytail\",\n\"wud\",\n\"wuddie\",\n\"wudge\",\n\"wudu\",\n\"wugg\",\n\"wulk\",\n\"wull\",\n\"wullcat\",\n\"wulliwa\",\n\"wumble\",\n\"wumman\",\n\"wummel\",\n\"wun\",\n\"wungee\",\n\"wunna\",\n\"wunner\",\n\"wunsome\",\n\"wup\",\n\"wur\",\n\"wurley\",\n\"wurmal\",\n\"wurrus\",\n\"wurset\",\n\"wurzel\",\n\"wush\",\n\"wusp\",\n\"wuss\",\n\"wusser\",\n\"wust\",\n\"wut\",\n\"wuther\",\n\"wuzu\",\n\"wuzzer\",\n\"wuzzle\",\n\"wuzzy\",\n\"wy\",\n\"wyde\",\n\"wye\",\n\"wyke\",\n\"wyle\",\n\"wymote\",\n\"wyn\",\n\"wynd\",\n\"wyne\",\n\"wynn\",\n\"wype\",\n\"wyson\",\n\"wyss\",\n\"wyve\",\n\"wyver\",\n\"x\",\n\"xanthic\",\n\"xanthin\",\n\"xanthyl\",\n\"xarque\",\n\"xebec\",\n\"xenia\",\n\"xenial\",\n\"xenian\",\n\"xenium\",\n\"xenon\",\n\"xenyl\",\n\"xerafin\",\n\"xerarch\",\n\"xerasia\",\n\"xeric\",\n\"xeriff\",\n\"xerogel\",\n\"xeroma\",\n\"xeronic\",\n\"xerosis\",\n\"xerotes\",\n\"xerotic\",\n\"xi\",\n\"xiphias\",\n\"xiphiid\",\n\"xiphoid\",\n\"xoana\",\n\"xoanon\",\n\"xurel\",\n\"xyla\",\n\"xylan\",\n\"xylate\",\n\"xylem\",\n\"xylene\",\n\"xylenol\",\n\"xylenyl\",\n\"xyletic\",\n\"xylic\",\n\"xylidic\",\n\"xylinid\",\n\"xylite\",\n\"xylitol\",\n\"xylogen\",\n\"xyloid\",\n\"xylol\",\n\"xyloma\",\n\"xylon\",\n\"xylonic\",\n\"xylose\",\n\"xyloyl\",\n\"xylyl\",\n\"xylylic\",\n\"xyphoid\",\n\"xyrid\",\n\"xyst\",\n\"xyster\",\n\"xysti\",\n\"xystos\",\n\"xystum\",\n\"xystus\",\n\"y\",\n\"ya\",\n\"yaba\",\n\"yabber\",\n\"yabbi\",\n\"yabble\",\n\"yabby\",\n\"yabu\",\n\"yacal\",\n\"yacca\",\n\"yachan\",\n\"yacht\",\n\"yachter\",\n\"yachty\",\n\"yad\",\n\"yade\",\n\"yaff\",\n\"yaffle\",\n\"yagger\",\n\"yagi\",\n\"yagua\",\n\"yaguaza\",\n\"yah\",\n\"yahan\",\n\"yahoo\",\n\"yair\",\n\"yaird\",\n\"yaje\",\n\"yajeine\",\n\"yak\",\n\"yakalo\",\n\"yakamik\",\n\"yakin\",\n\"yakka\",\n\"yakman\",\n\"yalb\",\n\"yale\",\n\"yali\",\n\"yalla\",\n\"yallaer\",\n\"yallow\",\n\"yam\",\n\"yamamai\",\n\"yamanai\",\n\"yamen\",\n\"yamilke\",\n\"yammer\",\n\"yamp\",\n\"yampa\",\n\"yamph\",\n\"yamshik\",\n\"yan\",\n\"yander\",\n\"yang\",\n\"yangtao\",\n\"yank\",\n\"yanking\",\n\"yanky\",\n\"yaoort\",\n\"yaourti\",\n\"yap\",\n\"yapa\",\n\"yaply\",\n\"yapness\",\n\"yapok\",\n\"yapp\",\n\"yapped\",\n\"yapper\",\n\"yapping\",\n\"yappish\",\n\"yappy\",\n\"yapster\",\n\"yar\",\n\"yarak\",\n\"yaray\",\n\"yarb\",\n\"yard\",\n\"yardage\",\n\"yardang\",\n\"yardarm\",\n\"yarder\",\n\"yardful\",\n\"yarding\",\n\"yardman\",\n\"yare\",\n\"yareta\",\n\"yark\",\n\"yarke\",\n\"yarl\",\n\"yarly\",\n\"yarm\",\n\"yarn\",\n\"yarnen\",\n\"yarner\",\n\"yarpha\",\n\"yarr\",\n\"yarran\",\n\"yarrow\",\n\"yarth\",\n\"yarthen\",\n\"yarwhip\",\n\"yas\",\n\"yashiro\",\n\"yashmak\",\n\"yat\",\n\"yate\",\n\"yati\",\n\"yatter\",\n\"yaud\",\n\"yauld\",\n\"yaupon\",\n\"yautia\",\n\"yava\",\n\"yaw\",\n\"yawl\",\n\"yawler\",\n\"yawn\",\n\"yawner\",\n\"yawney\",\n\"yawnful\",\n\"yawnily\",\n\"yawning\",\n\"yawnups\",\n\"yawny\",\n\"yawp\",\n\"yawper\",\n\"yawroot\",\n\"yaws\",\n\"yawweed\",\n\"yawy\",\n\"yaxche\",\n\"yaya\",\n\"ycie\",\n\"yday\",\n\"ye\",\n\"yea\",\n\"yeah\",\n\"yealing\",\n\"yean\",\n\"year\",\n\"yeara\",\n\"yeard\",\n\"yearday\",\n\"yearful\",\n\"yearly\",\n\"yearn\",\n\"yearock\",\n\"yearth\",\n\"yeast\",\n\"yeasty\",\n\"yeat\",\n\"yeather\",\n\"yed\",\n\"yede\",\n\"yee\",\n\"yeel\",\n\"yees\",\n\"yegg\",\n\"yeggman\",\n\"yeguita\",\n\"yeld\",\n\"yeldrin\",\n\"yelk\",\n\"yell\",\n\"yeller\",\n\"yelling\",\n\"yelloch\",\n\"yellow\",\n\"yellows\",\n\"yellowy\",\n\"yelm\",\n\"yelmer\",\n\"yelp\",\n\"yelper\",\n\"yelt\",\n\"yen\",\n\"yender\",\n\"yeni\",\n\"yenite\",\n\"yeo\",\n\"yeoman\",\n\"yep\",\n\"yer\",\n\"yerb\",\n\"yerba\",\n\"yercum\",\n\"yerd\",\n\"yere\",\n\"yerga\",\n\"yerk\",\n\"yern\",\n\"yerth\",\n\"yes\",\n\"yese\",\n\"yeso\",\n\"yesso\",\n\"yest\",\n\"yester\",\n\"yestern\",\n\"yesty\",\n\"yet\",\n\"yeta\",\n\"yetapa\",\n\"yeth\",\n\"yether\",\n\"yetlin\",\n\"yeuk\",\n\"yeuky\",\n\"yeven\",\n\"yew\",\n\"yex\",\n\"yez\",\n\"yezzy\",\n\"ygapo\",\n\"yield\",\n\"yielden\",\n\"yielder\",\n\"yieldy\",\n\"yigh\",\n\"yill\",\n\"yilt\",\n\"yin\",\n\"yince\",\n\"yinst\",\n\"yip\",\n\"yird\",\n\"yirk\",\n\"yirm\",\n\"yirn\",\n\"yirr\",\n\"yirth\",\n\"yis\",\n\"yite\",\n\"ym\",\n\"yn\",\n\"ynambu\",\n\"yo\",\n\"yobi\",\n\"yocco\",\n\"yochel\",\n\"yock\",\n\"yockel\",\n\"yodel\",\n\"yodeler\",\n\"yodh\",\n\"yoe\",\n\"yoga\",\n\"yogh\",\n\"yoghurt\",\n\"yogi\",\n\"yogin\",\n\"yogism\",\n\"yogist\",\n\"yogoite\",\n\"yohimbe\",\n\"yohimbi\",\n\"yoi\",\n\"yoick\",\n\"yoicks\",\n\"yojan\",\n\"yojana\",\n\"yok\",\n\"yoke\",\n\"yokeage\",\n\"yokel\",\n\"yokelry\",\n\"yoker\",\n\"yoking\",\n\"yoky\",\n\"yolden\",\n\"yolk\",\n\"yolked\",\n\"yolky\",\n\"yom\",\n\"yomer\",\n\"yon\",\n\"yond\",\n\"yonder\",\n\"yonner\",\n\"yonside\",\n\"yont\",\n\"yook\",\n\"yoop\",\n\"yor\",\n\"yore\",\n\"york\",\n\"yorker\",\n\"yot\",\n\"yote\",\n\"you\",\n\"youd\",\n\"youden\",\n\"youdith\",\n\"youff\",\n\"youl\",\n\"young\",\n\"younger\",\n\"youngly\",\n\"youngun\",\n\"younker\",\n\"youp\",\n\"your\",\n\"yourn\",\n\"yours\",\n\"yoursel\",\n\"youse\",\n\"youth\",\n\"youthen\",\n\"youthy\",\n\"youve\",\n\"youward\",\n\"youze\",\n\"yoven\",\n\"yow\",\n\"yowie\",\n\"yowl\",\n\"yowler\",\n\"yowley\",\n\"yowt\",\n\"yox\",\n\"yoy\",\n\"yperite\",\n\"yr\",\n\"yttria\",\n\"yttric\",\n\"yttrium\",\n\"yuan\",\n\"yuca\",\n\"yucca\",\n\"yuck\",\n\"yuckel\",\n\"yucker\",\n\"yuckle\",\n\"yucky\",\n\"yuft\",\n\"yugada\",\n\"yuh\",\n\"yukkel\",\n\"yulan\",\n\"yule\",\n\"yummy\",\n\"yungan\",\n\"yurt\",\n\"yurta\",\n\"yus\",\n\"yusdrum\",\n\"yutu\",\n\"yuzlik\",\n\"yuzluk\",\n\"z\",\n\"za\",\n\"zabeta\",\n\"zabra\",\n\"zabti\",\n\"zabtie\",\n\"zac\",\n\"zacate\",\n\"zacaton\",\n\"zachun\",\n\"zad\",\n\"zadruga\",\n\"zaffar\",\n\"zaffer\",\n\"zafree\",\n\"zag\",\n\"zagged\",\n\"zain\",\n\"zak\",\n\"zakkeu\",\n\"zaman\",\n\"zamang\",\n\"zamarra\",\n\"zamarro\",\n\"zambo\",\n\"zamorin\",\n\"zamouse\",\n\"zander\",\n\"zanella\",\n\"zant\",\n\"zante\",\n\"zany\",\n\"zanyish\",\n\"zanyism\",\n\"zanze\",\n\"zapas\",\n\"zaphara\",\n\"zapota\",\n\"zaptiah\",\n\"zaptieh\",\n\"zapupe\",\n\"zaqqum\",\n\"zar\",\n\"zareba\",\n\"zarf\",\n\"zarnich\",\n\"zarp\",\n\"zat\",\n\"zati\",\n\"zattare\",\n\"zax\",\n\"zayat\",\n\"zayin\",\n\"zeal\",\n\"zealful\",\n\"zealot\",\n\"zealous\",\n\"zebra\",\n\"zebraic\",\n\"zebrass\",\n\"zebrine\",\n\"zebroid\",\n\"zebrula\",\n\"zebrule\",\n\"zebu\",\n\"zebub\",\n\"zeburro\",\n\"zechin\",\n\"zed\",\n\"zedoary\",\n\"zee\",\n\"zeed\",\n\"zehner\",\n\"zein\",\n\"zeism\",\n\"zeist\",\n\"zel\",\n\"zelator\",\n\"zemeism\",\n\"zemi\",\n\"zemmi\",\n\"zemni\",\n\"zemstvo\",\n\"zenana\",\n\"zendik\",\n\"zenick\",\n\"zenith\",\n\"zenu\",\n\"zeolite\",\n\"zephyr\",\n\"zephyry\",\n\"zequin\",\n\"zer\",\n\"zerda\",\n\"zero\",\n\"zeroize\",\n\"zest\",\n\"zestful\",\n\"zesty\",\n\"zeta\",\n\"zetetic\",\n\"zeugma\",\n\"ziamet\",\n\"ziara\",\n\"ziarat\",\n\"zibet\",\n\"zibetum\",\n\"ziega\",\n\"zieger\",\n\"ziffs\",\n\"zig\",\n\"ziganka\",\n\"zigzag\",\n\"zihar\",\n\"zikurat\",\n\"zillah\",\n\"zimarra\",\n\"zimb\",\n\"zimbi\",\n\"zimme\",\n\"zimmi\",\n\"zimmis\",\n\"zimocca\",\n\"zinc\",\n\"zincate\",\n\"zincic\",\n\"zincide\",\n\"zincify\",\n\"zincing\",\n\"zincite\",\n\"zincize\",\n\"zincke\",\n\"zincky\",\n\"zinco\",\n\"zincous\",\n\"zincum\",\n\"zing\",\n\"zingel\",\n\"zink\",\n\"zinsang\",\n\"zip\",\n\"ziphian\",\n\"zipper\",\n\"zipping\",\n\"zippy\",\n\"zira\",\n\"zirai\",\n\"zircite\",\n\"zircon\",\n\"zither\",\n\"zizz\",\n\"zloty\",\n\"zo\",\n\"zoa\",\n\"zoacum\",\n\"zoaria\",\n\"zoarial\",\n\"zoarium\",\n\"zobo\",\n\"zocco\",\n\"zoccolo\",\n\"zodiac\",\n\"zoea\",\n\"zoeal\",\n\"zoeform\",\n\"zoetic\",\n\"zogan\",\n\"zogo\",\n\"zoic\",\n\"zoid\",\n\"zoisite\",\n\"zoism\",\n\"zoist\",\n\"zoistic\",\n\"zokor\",\n\"zoll\",\n\"zolle\",\n\"zombi\",\n\"zombie\",\n\"zonal\",\n\"zonally\",\n\"zonar\",\n\"zonary\",\n\"zonate\",\n\"zonated\",\n\"zone\",\n\"zoned\",\n\"zonelet\",\n\"zonic\",\n\"zoning\",\n\"zonite\",\n\"zonitid\",\n\"zonoid\",\n\"zonular\",\n\"zonule\",\n\"zonulet\",\n\"zonure\",\n\"zonurid\",\n\"zoo\",\n\"zoocarp\",\n\"zoocyst\",\n\"zooecia\",\n\"zoogamy\",\n\"zoogene\",\n\"zoogeny\",\n\"zoogony\",\n\"zooid\",\n\"zooidal\",\n\"zooks\",\n\"zoolite\",\n\"zoolith\",\n\"zoology\",\n\"zoom\",\n\"zoon\",\n\"zoonal\",\n\"zoonic\",\n\"zoonist\",\n\"zoonite\",\n\"zoonomy\",\n\"zoons\",\n\"zoonule\",\n\"zoopery\",\n\"zoopsia\",\n\"zoosis\",\n\"zootaxy\",\n\"zooter\",\n\"zootic\",\n\"zootomy\",\n\"zootype\",\n\"zoozoo\",\n\"zorgite\",\n\"zoril\",\n\"zorilla\",\n\"zorillo\",\n\"zorro\",\n\"zoster\",\n\"zounds\",\n\"zowie\",\n\"zudda\",\n\"zuisin\",\n\"zumatic\",\n\"zunyite\",\n\"zuza\",\n\"zwitter\",\n\"zyga\",\n\"zygal\",\n\"zygion\",\n\"zygite\",\n\"zygoma\",\n\"zygon\",\n\"zygose\",\n\"zygosis\",\n\"zygote\",\n\"zygotic\",\n\"zygous\",\n\"zymase\",\n\"zyme\",\n\"zymic\",\n\"zymin\",\n\"zymite\",\n\"zymogen\",\n\"zymoid\",\n\"zymome\",\n\"zymomin\",\n\"zymosis\",\n\"zymotic\",\n\"zymurgy\",\n\"zythem\",\n\"zythum\" };\n\n\nconst uint32_t word_list_size = sizeof(word_list)/sizeof(word_list[0]);\n\nvoid hide_unused_warning() {\n  (void)word_list_size; \n  (void)word_list; \n}\n\n} } // graphene::words\n"
  },
  {
    "path": "libraries/wallet/CMakeLists.txt",
    "content": "file(GLOB HEADERS \"include/graphene/wallet/*.hpp\")\n\nfind_package( Perl )\nfind_package( Doxygen )\n\nif( PERL_FOUND AND DOXYGEN_FOUND AND NOT \"${CMAKE_GENERATOR}\" STREQUAL \"Ninja\" )\n  configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile )\n  add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen/perlmod/DoxyDocs.pm\n                      WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\n                      COMMAND ${DOXYGEN_EXECUTABLE}\n                      DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile include/graphene/wallet/wallet.hpp )\n  if(MSVC)\n    add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp\n                        COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate_api_documentation.pl ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new\n                        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp\n                        COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new\n                        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate_api_documentation.pl ${CMAKE_CURRENT_BINARY_DIR}/doxygen/perlmod/DoxyDocs.pm )\n  else(MSVC)\n    add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp\n                        COMMAND ${PERL_EXECUTABLE} -I \"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_CURRENT_SOURCE_DIR}/generate_api_documentation.pl ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new\n                        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp\n                        COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new\n                        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate_api_documentation.pl ${CMAKE_CURRENT_BINARY_DIR}/doxygen/perlmod/DoxyDocs.pm )\n  endif(MSVC)\nelse()\n  # no perl and doxygen, generate the best docs we can at runtime from reflection\n  add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp\n                      COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/api_documentation_standin.cpp ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp\n                      DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/api_documentation_standin.cpp )\nendif()\n\nset( SOURCES\n     operation_printer.cpp\n     reflect_util.cpp\n     wallet.cpp\n     wallet_account.cpp\n     wallet_api_impl.cpp\n     wallet_asset.cpp\n     wallet_builder.cpp\n     wallet_debug.cpp\n     wallet_network.cpp\n     wallet_results.cpp\n     wallet_sign.cpp\n     wallet_transfer.cpp\n     wallet_voting.cpp\n   )\n\nadd_library( graphene_wallet ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp ${HEADERS} )\ntarget_link_libraries( graphene_wallet PRIVATE graphene_app graphene_chain graphene_utilities fc\n                       ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\ntarget_include_directories( graphene_db PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/include\" )\n\nset( GRAPHENE_WALLET_BIG_FILES\n     wallet.cpp\n     wallet_api_impl.cpp\n     wallet_voting.cpp\n   )\n\nif(MSVC)\n  set_source_files_properties( ${GRAPHENE_WALLET_BIG_FILES} PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nelse( MSVC )\n   if( MINGW )\n      set_source_files_properties( ${GRAPHENE_WALLET_BIG_FILES} PROPERTIES COMPILE_FLAGS -Wa,-mbig-obj )\n   endif( MINGW )\nendif(MSVC)\n\ninstall( TARGETS\n   graphene_wallet\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\ninstall( FILES ${HEADERS} DESTINATION \"include/graphene/wallet\" )\n"
  },
  {
    "path": "libraries/wallet/Doxyfile.in",
    "content": "# Doxyfile 1.8.9.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"BitShares Wallet API\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = ${CMAKE_CURRENT_BINARY_DIR}/doxygen\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces.\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = ${CMAKE_CURRENT_SOURCE_DIR}/include/graphene/wallet/wallet.hpp\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank the\n# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,\n# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,\n# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,\n# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,\n# *.qsf, *.as and *.js.\n\nFILE_PATTERNS          =\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = NO\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. To get the times font for\n# instance you can specify\n# EXTRA_PACKAGES=times\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sf.net) file that captures the\n# structure of the code including all documentation. Note that this feature is\n# still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = YES\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot.\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif and svg.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "libraries/wallet/api_documentation_standin.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <iomanip>\n#include <boost/algorithm/string/join.hpp>\n#include <graphene/wallet/wallet.hpp>\n#include <graphene/wallet/api_documentation.hpp>\n\nnamespace graphene { namespace wallet {\n   namespace detail {\n      namespace\n      {\n         template <typename... Args>\n         struct types_to_string_list_helper;\n\n         template <typename First, typename... Args>\n         struct types_to_string_list_helper<First, Args...>\n         {\n            std::list<std::string> operator()() const\n            {\n               std::list<std::string> argsList = types_to_string_list_helper<Args...>()();\n               argsList.push_front(fc::get_typename<typename std::decay<First>::type>::name());\n               return argsList;\n            }\n         };\n\n         template <>\n         struct types_to_string_list_helper<>\n         {\n            std::list<std::string> operator()() const\n            {\n               return std::list<std::string>();\n            }\n         };\n\n         template <typename... Args>\n         std::list<std::string> types_to_string_list()\n         {\n            return types_to_string_list_helper<Args...>()();\n         }\n      } // end anonymous namespace\n\n      struct help_visitor\n      {\n         std::vector<method_description> method_descriptions;\n\n         template<typename R, typename... Args>\n         void operator()( const char* name, std::function<R(Args...)>& memb )\n         {\n            method_description this_method;\n            this_method.method_name = name;\n            std::ostringstream ss;\n            ss << std::setw(40) << std::left << fc::get_typename<R>::name() << \" \" << name << \"(\";\n            ss << boost::algorithm::join(types_to_string_list<Args...>(), \", \");\n            ss << \")\\n\";\n            this_method.brief_description = ss.str();\n            method_descriptions.push_back(this_method);\n         }\n      };\n   } // end namespace detail\n\n   api_documentation::api_documentation()\n   {\n      fc::api<wallet_api> tmp;\n      detail::help_visitor visitor;\n      tmp->visit(visitor);\n      std::copy(visitor.method_descriptions.begin(), visitor.method_descriptions.end(),\n                std::inserter(method_descriptions, method_descriptions.end()));\n   }\n\n} } // end namespace graphene::wallet\n"
  },
  {
    "path": "libraries/wallet/generate_api_documentation.pl",
    "content": "#! /usr/bin/perl\n\nuse Text::Wrap;\nuse IO::File;\n\nrequire 'doxygen/perlmod/DoxyDocs.pm';\n\nmy($outputFileName) = @ARGV;\ndie \"usage: $0 output_file_name\" unless $outputFileName;\nmy $outFile = new IO::File($outputFileName, \"w\")\n  or die \"Error opening output file: $!\";\n\nmy $fileHeader = <<'END';\n#include <set>\n#include <graphene/wallet/api_documentation.hpp>\n#include <graphene/wallet/wallet.hpp>\n\nnamespace graphene { namespace wallet {\n   namespace detail\n   {\n      struct api_method_name_collector_visitor\n      {\n         std::set<std::string> method_names;\n\n         template<typename R, typename... Args>\n         void operator()( const char* name, std::function<R(Args...)>& memb )\n         {\n            method_names.emplace(name);\n         }\n      };\n   }\n\n   api_documentation::api_documentation()\n   {\nEND\n$outFile->print($fileHeader);\n\nfor my $class (@{$doxydocs->{classes}})\n{\n  if ($class->{name} eq 'graphene::wallet::wallet_api')\n  {\n    for my $member (@{$class->{public_methods}->{members}})\n    {\n      if ($member->{kind} eq 'function')\n      {\n        my @params = map { join(' ', cleanupDoxygenType($_->{type}), $_->{declaration_name}) } @{$member->{parameters}};\n        my $briefDescription = sprintf(\"%40s %s(%s)\\n\", cleanupDoxygenType($member->{type}), $member->{name}, join(', ', @params));\n        my $escapedBriefDescription = \"\\\"\" . escapeStringForC($briefDescription) . \"\\\"\";\n        my %paramInfo = map { $_->{declaration_name} => { type => $_->{type}} } @{$member->{parameters}};\n        my $escapedDetailedDescription = \"\\\"\\\"\\n\";\n        if ($member->{detailed}->{doc})\n        {\n          my $docString = formatDocComment($member->{detailed}->{doc}, \\%paramInfo);\n          for my $line (split(/\\n/, $docString))\n          {\n            $escapedDetailedDescription .=  \"                \\\"\" .  escapeStringForC($line . \"\\n\") . \"\\\"\\n\";\n          }\n        }\n        my $codeFragment = <<\"END\";\n     {\n        method_description this_method;\n        this_method.method_name = \"$member->{name}\";\n        this_method.brief_description = $escapedBriefDescription;\n        this_method.detailed_description = $escapedDetailedDescription;\n        method_descriptions.insert(this_method);\n     }\n\nEND\n        $outFile->print($codeFragment);\n      }\n    }\n  }\n}\n\nmy $fileFooter = <<'END';\n      fc::api<wallet_api> tmp;\n      detail::api_method_name_collector_visitor visitor;\n      tmp->visit(visitor);\n      for (auto iter = method_descriptions.begin(); iter != method_descriptions.end();)\n        if (visitor.method_names.find(iter->method_name) == visitor.method_names.end())\n          iter = method_descriptions.erase(iter);\n        else\n          ++iter;\n   }\n\n} } // end namespace graphene::wallet\nEND\n$outFile->print($fileFooter);\n$outFile->close();\n\nsub cleanupDoxygenType\n{\n  my($type) = @_;\n  $type =~ s/std:://g;\n  $type =~ s/, less<>//g;\n  $type =~ s/const //g;\n  $type =~ s/ &//g;\n  $type =~ s/< /</g;\n  $type =~ s/ >/>/g;\n  return $type;\n}\n\nsub formatDocComment\n{\n  my($doc, $paramInfo) = @_;\n\n  my $bodyDocs = '';\n  my $paramDocs = '';\n  my $returnDocs = '';\n\n  for (my $i = 0; $i < @{$doc}; ++$i)\n  {\n    if (($doc->[$i] eq 'params') # doxygen version 1.8.11 (Ubuntu 16.04) or 1.8.13 (Ubuntu 18.04)\n        or ($doc->[$i]->{params})) # doxygen version 1.8.17 (Ubuntu 20.04)\n    {\n      $paramDocs .= \"Parameters:\\n\";\n      if ($doc->[$i] eq 'params')\n      {\n        ++$i;\n        @parametersList = @{$doc->[$i]};\n      }\n      else\n      {\n        @parametersList = @{$doc->[$i]->{params}};\n      }\n      for my $parameter (@parametersList)\n      {\n        my $declname = $parameter->{parameters}->[0]->{name};\n        my $decltype = cleanupDoxygenType($paramInfo->{$declname}->{type});\n        $paramDocs .= Text::Wrap::fill('    ', '        ', \"$declname: \" . formatDocComment($parameter->{doc})\n                                                                         . \" (type: $decltype)\") . \"\\n\";\n      }\n    }\n    elsif ($doc->[$i]->{return})\n    {\n      $returnDocs .= \"Returns\\n\";\n      $returnDocs .= Text::Wrap::fill('    ','        ', formatDocComment($doc->[$i]->{return})) . \"\\n\";\n    }\n    else\n    {\n      my $docElement = $doc->[$i];\n      if ($docElement->{type} eq 'text' or $docElement->{type} eq 'url')\n      {\n        $bodyDocs .= $docElement->{content};\n      }\n      elsif ($docElement->{type} eq 'parbreak')\n      {\n        $bodyDocs .= \"\\n\\n\";\n      }\n      elsif ($docElement->{type} eq 'style' and $docElement->{style} eq 'code')\n      {\n        $bodyDocs .= \"'\";\n      }\n    }\n  }\n\n  $bodyDocs =~ s/^\\s+|\\s+$//g;\n  $bodyDocs = Text::Wrap::fill('', '', $bodyDocs);\n\n  $paramDocs =~ s/^\\s+|\\s+$//g;\n  $returnDocs =~ s/^\\s+|\\s+$//g;\n\n  my $result = Text::Wrap::fill('', '', $bodyDocs);\n  $result .= \"\\n\\n\" . $paramDocs if $paramDocs;\n  $result .= \"\\n\\n\" . $returnDocs if $returnDocs;\n\n  return $result;\n}\n\nsub escapeCharForCString\n{\n  my($char) = @_;\n  return \"\\\\a\" if $char eq \"\\x07\";\n  return \"\\\\b\" if $char eq \"\\x08\";\n  return \"\\\\f\" if $char eq \"\\x0c\";\n  return \"\\\\n\" if $char eq \"\\x0a\";\n  return \"\\\\r\" if $char eq \"\\x0d\";\n  return \"\\\\t\" if $char eq \"\\x09\";\n  return \"\\\\v\" if $char eq \"\\x0b\";\n  return \"\\\\\\\"\" if $char eq \"\\x22\";\n  return \"\\\\\\\\\" if $char eq \"\\x5c\";\n  return $char;\n}\n\nsub escapeStringForC\n{\n  my($str) = @_;\n  return join('', map { escapeCharForCString($_) } split('', $str));\n}\n\n\n"
  },
  {
    "path": "libraries/wallet/include/graphene/wallet/api_documentation.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <string>\n\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/member.hpp>\n\n#include <fc/exception/exception.hpp>\n\nnamespace graphene { namespace wallet {\n\n   struct method_description\n   {\n      std::string method_name;\n      std::string brief_description;\n      std::string detailed_description;\n   };\n\n   class api_documentation\n   {\n      using method_description_set = boost::multi_index::multi_index_container<method_description,\n         boost::multi_index::indexed_by<\n            boost::multi_index::ordered_unique<\n               boost::multi_index::member<method_description, std::string, &method_description::method_name> > > >;\n      method_description_set method_descriptions;\n   public:\n      api_documentation();\n      std::string get_brief_description(const std::string& method_name) const\n      {\n         auto iter = method_descriptions.find(method_name);\n         if (iter != method_descriptions.end())\n            return iter->brief_description;\n         else\n            FC_THROW_EXCEPTION(fc::key_not_found_exception, \"No entry for method ${name}\", (\"name\", method_name));\n      }\n      std::string get_detailed_description(const std::string& method_name) const\n      {\n         auto iter = method_descriptions.find(method_name);\n         if (iter != method_descriptions.end())\n            return iter->detailed_description;\n         else\n            FC_THROW_EXCEPTION(fc::key_not_found_exception, \"No entry for method ${name}\", (\"name\", method_name));\n      }\n      std::vector<std::string> get_method_names() const\n      {\n         std::vector<std::string> method_names;\n         for (const method_description& method_description: method_descriptions)\n            method_names.emplace_back(method_description.method_name);\n         return method_names;\n      }\n   };\n\n} } // end namespace graphene::wallet\n"
  },
  {
    "path": "libraries/wallet/include/graphene/wallet/reflect_util.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n// This file contains various reflection methods that are used to\n// support the wallet, e.g. allow specifying operations by name\n// instead of ID.\n\n#include <string>\n#include <vector>\n#include <boost/container/flat_map.hpp>\n#include <fc/exception/exception.hpp>\n#include <fc/variant.hpp>\n\nnamespace graphene { namespace wallet {\n\nusing std::string;\nusing std::vector;\nusing boost::container::flat_map;\nusing fc::variant;\n\nstruct static_variant_map\n{\n   flat_map< string, int > name_to_which;\n   vector< string > which_to_name;\n};\n\nnamespace impl {\n\nstd::string clean_name( const std::string& name );\n\nstruct static_variant_map_visitor\n{\n   static_variant_map_visitor() {}\n\n   typedef void result_type;\n\n   template< typename T >\n   result_type operator()( const T& dummy )\n   {\n      FC_ASSERT( which == m.which_to_name.size(), \"This should not happen\" );\n      std::string name = clean_name( fc::get_typename<T>::name() );\n      m.name_to_which[ name ] = which;\n      m.which_to_name.push_back( name );\n   }\n\n   static_variant_map m;\n   uint16_t which; // 16 bit should be practically enough\n};\n\ntemplate< typename StaticVariant >\nstruct from_which_visitor\n{\n   typedef StaticVariant result_type;\n\n   template< typename Member >   // Member is member of static_variant\n   result_type operator()( const Member& dummy )\n   {\n      Member result;\n      from_variant( v, result, _max_depth );\n      return result;    // converted from StaticVariant to Result automatically due to return type\n   }\n\n   const variant& v;\n   const uint32_t _max_depth;\n\n   from_which_visitor( const variant& _v, uint32_t max_depth ) : v(_v), _max_depth(max_depth) {}\n};\n\n} // namespace impl\n\ntemplate< typename T >\nT from_which_variant( int which, const variant& v, uint32_t max_depth )\n{\n   // Parse a variant for a known which()\n   T dummy;\n   dummy.set_which( which );\n   impl::from_which_visitor< T > vtor(v, max_depth);\n   return dummy.visit( vtor );\n}\n\ntemplate<typename T>\nstatic_variant_map create_static_variant_map()\n{\n   T dummy;\n   int n = dummy.count();\n   FC_ASSERT( n <= std::numeric_limits<uint16_t>::max(), \"Too many items in this static_variant\" );\n   impl::static_variant_map_visitor vtor;\n   for( int i=0; i<n; i++ )\n   {\n      dummy.set_which(i);\n      vtor.which = i;\n      dummy.visit( vtor );\n   }\n   return vtor.m;\n}\n\n} } // namespace graphene::wallet\n"
  },
  {
    "path": "libraries/wallet/include/graphene/wallet/wallet.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/optional.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/app/api.hpp>\n#include <graphene/utilities/key_conversion.hpp>\n#include \"wallet_structs.hpp\"\n\nnamespace fc\n{\n   void to_variant( const account_multi_index_type& accts, variant& vo, uint32_t max_depth );\n   void from_variant( const variant &var, account_multi_index_type &vo, uint32_t max_depth );\n}\n\nnamespace graphene { namespace wallet {\n\n/**\n * This class takes a variant and turns it into an object\n * of the given type, with the new operator.\n */\nobject* create_object( const variant& v );\n\n/**\n * This wallet assumes it is connected to the database server with a high-bandwidth, low-latency connection and\n * performs minimal caching. This API could be provided locally to be used by a web interface.\n */\nclass wallet_api\n{\n   public:\n      // Variables\n      fc::signal<void(bool)> lock_changed;\n      std::shared_ptr<detail::wallet_api_impl> my;\n\n      // Methods\n      wallet_api( const wallet_data& initial_data, const fc::api<login_api>& rapi );\n      virtual ~wallet_api();\n\n      bool copy_wallet_file( const string& destination_filename )const;\n\n      fc::ecc::private_key derive_private_key( const string& prefix_string, uint32_t sequence_number ) const;\n\n      /** Returns info about head block, chain_id, maintenance, participation, current active witnesses and\n       * committee members.\n       * @returns runtime info about the blockchain\n       */\n      variant                           info()const;\n      /** Returns info such as client version, git version of graphene/fc, version of boost, openssl.\n       * @returns compile time info and client and dependencies versions\n       */\n      variant_object                    about() const;\n      /** Returns info about a specified block.\n       * @param num height of the block to retrieve\n       * @returns info about the block, or null if not found\n       */\n      optional<signed_block_with_info>    get_block( uint32_t num )const;\n      /** Returns the number of accounts registered on the blockchain\n       * @returns the number of registered accounts\n       */\n      uint64_t                          get_account_count()const;\n      /** Lists all accounts controlled by this wallet.\n       * This returns a list of the full account objects for all accounts whose private keys\n       * we possess.\n       * @returns a list of account objects\n       */\n      vector<account_object>            list_my_accounts()const;\n      /** Lists all accounts registered in the blockchain.\n       * This returns a list of all account names and their account ids, sorted by account name.\n       *\n       * Use the \\c lowerbound and limit parameters to page through the list.  To retrieve all accounts,\n       * start by setting \\c lowerbound to the empty string \\c \"\", and then each iteration, pass\n       * the last account name returned as the \\c lowerbound for the next \\c list_accounts() call.\n       *\n       * @param lowerbound the name of the first account to return.  If the named account does not exist,\n       *                   the list will start at the account that comes after \\c lowerbound\n       * @param limit the maximum number of accounts to return (max: 1000)\n       * @returns a list of accounts mapping account names to account ids\n       */\n      map<string, account_id_type, std::less<>> list_accounts( const string& lowerbound, uint32_t limit )const;\n      /** List the balances of an account.\n       * Each account can have multiple balances, one for each type of asset owned by that\n       * account.  The returned list will only contain assets for which the account has a\n       * nonzero balance\n       * @param account_name_or_id the name or id of the account whose balances you want\n       * @returns a list of the given account's balances\n       */\n      vector<asset>                     list_account_balances( const string& account_name_or_id )const;\n      /** Lists all assets registered on the blockchain.\n       *\n       * To list all assets, pass the empty string \\c \"\" for the lowerbound to start\n       * at the beginning of the list, and iterate as necessary.\n       *\n       * @param lowerbound  the symbol of the first asset to include in the list.\n       * @param limit the maximum number of assets to return (max: 100)\n       * @returns the list of asset objects, ordered by symbol\n       */\n      vector<extended_asset_object>     list_assets( const string& lowerbound, uint32_t limit )const;\n      /** Returns assets count registered on the blockchain.\n       *\n       * @returns assets count\n       */\n      uint64_t get_asset_count()const;\n\n      /** Returns the most recent operations on the named account.\n       *\n       * This returns a list of operation history objects, which describe activity on the account.\n       *\n       * @param account_name_or_id the name or id of the account\n       * @param limit the number of entries to return (starting from the most recent)\n       * @returns a list of \\c operation_history_objects\n       */\n      vector<operation_detail>  get_account_history( const string& account_name_or_id, uint32_t limit )const;\n\n      /** Returns the relative operations on the named account from start number.\n       *\n       * @param account_name_or_id the name or id of the account\n       * @param stop Sequence number of earliest operation.\n       * @param limit the number of entries to return\n       * @param start  the sequence number where to start looping back throw the history\n       * @returns a list of \\c operation_history_objects\n       */\n     vector<operation_detail>  get_relative_account_history( const string& account_name_or_id, uint32_t stop,\n                                                             uint32_t limit, uint32_t start )const;\n\n      /**\n       * @brief Fetch all objects relevant to the specified account\n       * @param name_or_id Must be the name or ID of an account to retrieve\n       * @return All info about the specified account\n       *\n       * This function fetches all relevant objects for the given account. If the string\n       * of \\c name_or_id cannot be tied to an account, that input will be ignored.\n       *\n       */\n      full_account                      get_full_account( const string& name_or_id )const;\n\n      /**\n       * @brief Get OHLCV data of a trading pair in a time range\n       * @param symbol symbol or ID of the base asset\n       * @param symbol2 symbol or ID of the quote asset\n       * @param bucket length of each time bucket in seconds.\n       * @param start the start of a time range, E.G. \"2018-01-01T00:00:00\"\n       * @param end the end of the time range\n       * @return A list of OHLCV data, in \"least recent first\" order.\n       */\n      vector<bucket_object> get_market_history( const string& symbol, const string& symbol2, uint32_t bucket,\n                                                const time_point_sec& start, const time_point_sec& end )const;\n\n      /**\n       * @brief Fetch all orders relevant to the specified account sorted descendingly by price\n       *\n       * @param name_or_id  The name or ID of an account to retrieve\n       * @param base  Base asset\n       * @param quote  Quote asset\n       * @param limit  The limitation of items each query can fetch (max: 101)\n       * @param ostart_id  Start order id, fetch orders which price are lower than or equal to this order\n       * @param ostart_price  Fetch orders with price lower than or equal to this price\n       *\n       * @return List of orders from \\c name_or_id to the corresponding account\n       *\n       * @note\n       * 1. if \\c name_or_id cannot be tied to an account, empty result will be returned\n       * 2. \\c ostart_id and \\c ostart_price can be \\c null, if so the api will return the \"first page\" of orders.\n       *    if \\c ostart_id is specified and valid, its price will be used to do page query preferentially,\n       *    otherwise the \\c ostart_price will be used\n       */\n      vector<limit_order_object>        get_account_limit_orders( const string& name_or_id,\n                                            const string& base,\n                                            const string& quote,\n                                            uint32_t limit = 101,\n                                            const optional<limit_order_id_type>& ostart_id = {},\n                                            const optional<price>& ostart_price = optional<price>() )const;\n\n      /**\n       * @brief Get limit orders in a given market\n       * @param a symbol or ID of asset being sold\n       * @param b symbol or ID of asset being purchased\n       * @param limit Maximum number of orders to retrieve\n       * @return The limit orders, ordered from least price to greatest\n       */\n      vector<limit_order_object>        get_limit_orders( const string& a, const string& b, uint32_t limit )const;\n\n      /**\n       * @brief Get call orders (aka margin positions) for a given asset\n       * @param asset_symbol_or_id symbol or ID of the debt asset\n       * @param limit Maximum number of orders to retrieve\n       * @return The call orders, ordered from earliest to be called to latest\n       */\n      vector<call_order_object>         get_call_orders( const string& asset_symbol_or_id, uint32_t limit )const;\n\n      /**\n       * @brief Get forced settlement orders in a given asset\n       * @param a Symbol or ID of asset being settled\n       * @param limit Maximum number of orders to retrieve\n       * @return The settle orders, ordered from earliest settlement date to latest\n       */\n      vector<force_settlement_object>   get_settle_orders( const string& a, uint32_t limit )const;\n\n      /** Returns the collateral_bid object for the given MPA\n       *\n       * @param asset_symbol_or_id the symbol or id of the asset\n       * @param limit the number of entries to return\n       * @param start the sequence number where to start looping back throw the history\n       * @returns a list of \\c collateral_bid_objects\n       */\n      vector<collateral_bid_object> get_collateral_bids( const string& asset_symbol_or_id, uint32_t limit = 100,\n                                                         uint32_t start = 0 )const;\n\n      /** Returns the block chain's slowly-changing settings.\n       * This object contains all of the properties of the blockchain that are fixed\n       * or that change only once per maintenance interval (daily) such as the\n       * current list of witnesses, committee_members, block interval, etc.\n       * @see \\c get_dynamic_global_properties() for frequently changing properties\n       * @returns the global properties\n       */\n      global_property_object            get_global_properties() const;\n\n      /**\n       * Get operations relevant to the specified account filtering by operation type, with transaction id\n       *\n       * @param account_name_or_id the name or id of the account, whose history shoulde be queried\n       * @param operation_types The IDs of the operation we want to get operations in the account\n       *                        ( 0 = transfer , 1 = limit order create, ...)\n       * @param start the sequence number where to start looping back throw the history\n       * @param limit the max number of entries to return (from start number)\n       * @returns account_history_operation_detail\n       */\n      account_history_operation_detail get_account_history_by_operations( const string& account_name_or_id,\n                                                                          const flat_set<uint16_t>& operation_types,\n                                                                          uint32_t start, uint32_t limit )const;\n\n      /** Returns the block chain's rapidly-changing properties.\n       * The returned object contains information that changes every block interval\n       * such as the head block number, the next witness, etc.\n       * @see \\c get_global_properties() for less-frequently changing properties\n       * @returns the dynamic global properties\n       */\n      dynamic_global_property_object    get_dynamic_global_properties() const;\n\n      /** Returns information about the given account.\n       *\n       * @param account_name_or_id the name or ID of the account to provide information about\n       * @returns the public account data stored in the blockchain\n       */\n      account_object                    get_account( const string& account_name_or_id ) const;\n\n      /** Returns information about the given asset.\n       * @param asset_symbol_or_id the symbol or id of the asset in question\n       * @returns the information about the asset stored in the block chain\n       */\n      extended_asset_object             get_asset( const string& asset_symbol_or_id ) const;\n\n      /** Returns the BitAsset-specific data for a given asset.\n       * Market-issued assets's behavior are determined both by their \"BitAsset Data\" and\n       * their basic asset data, as returned by \\c get_asset().\n       * @param asset_symbol_or_id the symbol or id of the BitAsset in question\n       * @returns the BitAsset-specific data for this asset\n       */\n      asset_bitasset_data_object        get_bitasset_data( const string& asset_symbol_or_id )const;\n\n      /**\n       * Returns information about the given HTLC object.\n       * @param htlc_id the id of the HTLC object.\n       * @returns the information about the HTLC object\n       */\n      optional<variant>                 get_htlc( const htlc_id_type& htlc_id ) const;\n\n      /** Lookup the id of a named account.\n       * @param account_name_or_id the name or ID of the account to look up\n       * @returns the id of the named account\n       */\n      account_id_type                   get_account_id( const string& account_name_or_id ) const;\n\n      /** Lookup the name of an account.\n       * @param account_name_or_id the name or ID of the account to look up\n       * @returns the name of the account\n       */\n      string                            get_account_name( const string& account_name_or_id ) const\n      { return get_account( account_name_or_id ).name; }\n\n      /**\n       * Lookup the id of an asset.\n       * @param asset_symbol_or_id the symbol or ID of an asset to look up\n       * @returns the id of the given asset\n       */\n      asset_id_type                     get_asset_id( const string& asset_symbol_or_id ) const;\n\n      /**\n       * Lookup the symbol of an asset.\n       * @param asset_symbol_or_id the symbol or ID of an asset to look up\n       * @returns the symbol of the given asset\n       */\n      string                            get_asset_symbol( const string& asset_symbol_or_id ) const\n      { return get_asset( asset_symbol_or_id ).symbol; }\n\n      /**\n       * Lookup the symbol of an asset. Synonym of @ref get_asset_symbol.\n       * @param asset_symbol_or_id the symbol or ID of an asset to look up\n       * @returns the symbol of the given asset\n       */\n      string                            get_asset_name( const string& asset_symbol_or_id ) const\n      { return get_asset_symbol( asset_symbol_or_id ); }\n\n      /**\n       * Returns the blockchain object corresponding to the given id.\n       *\n       * This generic function can be used to retrieve any object from the blockchain\n       * that is assigned an ID.  Certain types of objects have specialized convenience\n       * functions to return their objects -- e.g., assets have \\c get_asset(), accounts\n       * have \\c get_account(), but this function will work for any object.\n       *\n       * @param id the id of the object to return\n       * @returns the requested object\n       */\n      variant                           get_object( const object_id_type& id ) const;\n\n      /** Returns the current wallet filename.\n       *\n       * This is the filename that will be used when automatically saving the wallet.\n       *\n       * @see set_wallet_filename()\n       * @return the wallet filename\n       */\n      string                            get_wallet_filename() const;\n\n      /**\n       * Get the WIF private key corresponding to a public key.  The\n       * private key must already be in the wallet.\n       * @param pubkey a public key in Base58 format\n       * @return the WIF private key\n       */\n      string                            get_private_key( const public_key_type& pubkey )const;\n\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Create a new transaction builder.\n       * @return handle of the new transaction builder\n       */\n      transaction_handle_type begin_builder_transaction()const;\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Append a new operation to a transaction builder.\n       * @param transaction_handle handle of the transaction builder\n       * @param op the operation in JSON format\n       */\n      void add_operation_to_builder_transaction( transaction_handle_type transaction_handle,\n                                                 const operation& op )const;\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Replace an operation in a transaction builder with a new operation.\n       * @param handle handle of the transaction builder\n       * @param operation_index the index of the old operation in the builder to be replaced\n       * @param new_op the new operation in JSON format\n       */\n      void replace_operation_in_builder_transaction( transaction_handle_type handle,\n                                                     uint32_t operation_index,\n                                                     const operation& new_op )const;\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Calculate and update fees for the operations in a transaction builder.\n       * @param handle handle of the transaction builder\n       * @param fee_asset symbol or ID of an asset that to be used to pay fees\n       * @return total fees\n       */\n      asset set_fees_on_builder_transaction( transaction_handle_type handle,\n                                             const string& fee_asset = GRAPHENE_SYMBOL )const;\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Show content of a transaction builder.\n       * @param handle handle of the transaction builder\n       * @return a transaction\n       */\n      transaction preview_builder_transaction( transaction_handle_type handle )const;\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Sign the transaction in a transaction builder and optionally broadcast to the network.\n       * @param transaction_handle handle of the transaction builder\n       * @param broadcast whether to broadcast the signed transaction to the network\n       * @return a signed transaction\n       */\n      signed_transaction sign_builder_transaction( transaction_handle_type transaction_handle,\n                                                   bool broadcast = true )const;\n\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Sign the transaction in a transaction builder and optionally broadcast to the network.\n       * @param transaction_handle handle of the transaction builder\n       * @param signing_keys Keys that must be used when signing the transaction\n       * @param broadcast whether to broadcast the signed transaction to the network\n       * @return a signed transaction\n       */\n      signed_transaction sign_builder_transaction2( transaction_handle_type transaction_handle,\n                                            const vector<public_key_type>& signing_keys = vector<public_key_type>(),\n                                            bool broadcast = true )const;\n\n      /** Broadcast signed transaction\n       * @param tx signed transaction\n       * @returns the transaction ID along with the signed transaction.\n       */\n      pair<transaction_id_type,signed_transaction> broadcast_transaction( const signed_transaction& tx )const;\n\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Create a proposal containing the operations in a transaction builder (create a new proposal_create\n       * operation, then replace the transaction builder with the new operation), then sign the transaction\n       * and optionally broadcast to the network.\n       *\n       * Note: this command is buggy because unable to specify proposer. It will be deprecated in a future release.\n       *       Please use \\c propose_builder_transaction2() instead.\n       *\n       * @param handle handle of the transaction builder\n       * @param expiration when the proposal will expire\n       * @param review_period_seconds review period of the proposal in seconds\n       * @param broadcast whether to broadcast the signed transaction to the network\n       * @return a signed transaction\n       */\n      signed_transaction propose_builder_transaction(\n          transaction_handle_type handle,\n          const time_point_sec& expiration = time_point::now() + fc::minutes(1),\n          uint32_t review_period_seconds = 0,\n          bool broadcast = true\n         )const;\n\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Create a proposal containing the operations in a transaction builder (create a new proposal_create\n       * operation, then replace the transaction builder with the new operation), then sign the transaction\n       * and optionally broadcast to the network.\n       *\n       * @param handle handle of the transaction builder\n       * @param account_name_or_id name or ID of the account who would pay fees for creating the proposal\n       * @param expiration when the proposal will expire\n       * @param review_period_seconds review period of the proposal in seconds\n       * @param broadcast whether to broadcast the signed transaction to the network\n       * @return a signed transaction\n       */\n      signed_transaction propose_builder_transaction2(\n         transaction_handle_type handle,\n         const string& account_name_or_id,\n         const time_point_sec& expiration = time_point::now() + fc::minutes(1),\n         uint32_t review_period_seconds = 0,\n         bool broadcast = true\n        )const;\n\n      /**\n       * @ingroup Transaction Builder API\n       *\n       * Destroy a transaction builder.\n       * @param handle handle of the transaction builder\n       */\n      void remove_builder_transaction(transaction_handle_type handle)const;\n\n      /** Checks whether the wallet has just been created and has not yet had a password set.\n       *\n       * Calling \\c set_password will transition the wallet to the locked state.\n       * @return true if the wallet is new\n       * @ingroup Wallet Management\n       */\n      bool    is_new()const;\n\n      /** Checks whether the wallet is locked (is unable to use its private keys).\n       *\n       * This state can be changed by calling \\c lock() or \\c unlock().\n       * @return true if the wallet is locked\n       * @ingroup Wallet Management\n       */\n      bool    is_locked()const;\n\n      /** Locks the wallet immediately.\n       * @ingroup Wallet Management\n       */\n      void    lock()const;\n\n      /** Unlocks the wallet.\n       *\n       * The wallet remain unlocked until the \\c lock is called\n       * or the program exits.\n       *\n       * When used in command line, if typed \"unlock\" without a password followed, the user will be prompted\n       * to input a password without echo.\n       *\n       * @param password the password previously set with \\c set_password()\n       * @ingroup Wallet Management\n       */\n      void    unlock( const string& password )const;\n\n      /** Sets a new password on the wallet.\n       *\n       * The wallet must be either 'new' or 'unlocked' to\n       * execute this command.\n       *\n       * When used in command line, if typed \"set_password\" without a password followed, the user will be prompted\n       * to input a password without echo.\n       *\n       * @param password a new password\n       * @ingroup Wallet Management\n       */\n      void    set_password( const string& password )const;\n\n      /** Dumps all private keys owned by the wallet.\n       *\n       * The keys are printed in WIF format.  You can import these keys into another wallet\n       * using \\c import_key()\n       * @returns a map containing the private keys, indexed by their public key\n       */\n      map<public_key_type, string> dump_private_keys()const;\n\n      /** Returns a list of all commands supported by the wallet API.\n       *\n       * This lists each command, along with its arguments and return types.\n       * For more detailed help on a single command, use \\c gethelp()\n       *\n       * @returns a multi-line string suitable for displaying on a terminal\n       */\n      string  help()const;\n\n      /** Returns detailed help on a single API command.\n       * @param method the name of the API command you want help with\n       * @returns a multi-line string suitable for displaying on a terminal\n       */\n      string  gethelp( const string& method )const;\n\n      /** Loads a specified BitShares wallet.\n       *\n       * The current wallet is closed before the new wallet is loaded.\n       *\n       * @warning This does not change the filename that will be used for future\n       * wallet writes, so this may cause you to overwrite your original\n       * wallet unless you also call \\c set_wallet_filename()\n       *\n       * @param wallet_filename the filename of the wallet JSON file to load.\n       *                        If \\c wallet_filename is empty, it reloads the\n       *                        existing wallet file\n       * @returns true if the specified wallet is loaded\n       */\n      bool    load_wallet_file( const string& wallet_filename = \"\" )const;\n\n      /** Quit from the wallet.\n       *\n       * The current wallet will be closed and saved.\n       */\n      void    quit()const;\n\n      /** Saves the current wallet to the given filename.\n       *\n       * @warning This does not change the wallet filename that will be used for future\n       * writes, so think of this function as 'Save a Copy As...' instead of\n       * 'Save As...'.  Use \\c set_wallet_filename() to make the filename\n       * persist.\n       * @param wallet_filename the filename of the new wallet JSON file to create\n       *                        or overwrite.  If \\c wallet_filename is empty,\n       *                        save to the current filename.\n       */\n      void    save_wallet_file( const string& wallet_filename = \"\" )const;\n\n      /** Sets the wallet filename used for future writes.\n       *\n       * This does not trigger a save, it only changes the default filename\n       * that will be used the next time a save is triggered.\n       *\n       * @param wallet_filename the new filename to use for future saves\n       */\n      void    set_wallet_filename( const string& wallet_filename )const;\n\n      /** Suggests a safe brain key to use for creating your account.\n       * \\c create_account_with_brain_key() requires you to specify a 'brain key',\n       * a long passphrase that provides enough entropy to generate cyrptographic\n       * keys.  This function will suggest a suitably random string that should\n       * be easy to write down (and, with effort, memorize).\n       * @returns a suggested brain_key\n       */\n      brain_key_info suggest_brain_key()const;\n\n     /**\n      * Derive any number of *possible* owner keys from a given brain key.\n      *\n      * NOTE: These keys may or may not match with the owner keys of any account.\n      * This function is merely intended to assist with account or key recovery.\n      *\n      * @see suggest_brain_key()\n      *\n      * @param brain_key    Brain key\n      * @param number_of_desired_keys  Number of desired keys\n      * @return A list of keys that are deterministically derived from the brainkey\n      */\n     vector<brain_key_info> derive_owner_keys_from_brain_key( const string& brain_key,\n                                                              uint32_t number_of_desired_keys = 1 ) const;\n\n     /**\n      * Determine whether a textual representation of a public key\n      * (in Base-58 format) is *currently* linked\n      * to any *registered* (i.e. non-stealth) account on the blockchain\n      * @param public_key Public key\n      * @return Whether a public key is known\n      */\n     bool is_public_key_registered( const string& public_key ) const;\n\n      /** Converts a signed_transaction in JSON form to its binary representation.\n       *\n       * @param tx the transaction to serialize\n       * @returns the binary form of the transaction, hex encoded.\n       */\n      string serialize_transaction( const signed_transaction& tx ) const;\n\n      /** Imports the private key for an existing account.\n       *\n       * The private key must match either an owner key or an active key for the\n       * named account.\n       *\n       * @see dump_private_keys()\n       *\n       * @param account_name_or_id the account owning the key\n       * @param wif_key the private key in WIF format\n       * @returns true if the key was imported\n       */\n      bool import_key( const string& account_name_or_id, const string& wif_key )const;\n\n      /** Imports accounts from a BitShares 0.x wallet file.\n       * Current wallet file must be unlocked to perform the import.\n       *\n       * @param filename the BitShares 0.x wallet file to import\n       * @param password the password to encrypt the BitShares 0.x wallet file\n       * @returns a map containing the accounts found and whether imported\n       */\n      map<string, bool, std::less<>> import_accounts( const string& filename, const string& password )const;\n\n      /** Imports from a BitShares 0.x wallet file, find keys that were bound to a given account name on the\n       * BitShares 0.x chain, rebind them to an account name on the 2.0 chain.\n       * Current wallet file must be unlocked to perform the import.\n       *\n       * @param filename the BitShares 0.x wallet file to import\n       * @param password the password to encrypt the BitShares 0.x wallet file\n       * @param src_account_name name of the account on BitShares 0.x chain\n       * @param dest_account_name name of the account on BitShares 2.0 chain,\n       *                          can be same or different to \\c src_account_name\n       * @returns whether the import has succeeded\n       */\n      bool import_account_keys( const string& filename, const string& password,\n                                const string& src_account_name, const string& dest_account_name )const;\n\n      /**\n       * This call will construct transaction(s) that will claim all balances controled\n       * by wif_keys and deposit them into the given account.\n       *\n       * @param account_name_or_id name or ID of an account that to claim balances to\n       * @param wif_keys private WIF keys of balance objects to claim balances from\n       * @param broadcast true to broadcast the transaction on the network\n       */\n      vector< signed_transaction > import_balance( const string& account_name_or_id, const vector<string>& wif_keys,\n                                                   bool broadcast )const;\n\n      /** Transforms a brain key to reduce the chance of errors when re-entering the key from memory.\n       *\n       * This takes a user-supplied brain key and normalizes it into the form used\n       * for generating private keys.  In particular, this upper-cases all ASCII characters\n       * and collapses multiple spaces into one.\n       * @param s the brain key as supplied by the user\n       * @returns the brain key in its normalized form\n       */\n      string normalize_brain_key( const string& s ) const;\n\n      /** Registers a third party's account on the blockckain.\n       *\n       * This function is used to register an account for which you do not own the private keys.\n       * When acting as a registrar, an end user will generate their own private keys and send\n       * you the public keys.  The registrar will use this function to register the account\n       * on behalf of the end user.\n       *\n       * @see create_account_with_brain_key()\n       *\n       * @param name the name of the account, must be unique on the blockchain.  Shorter names\n       *             are more expensive to register. The rules are still in flux, but in general\n       *             names of more than 8 characters with at least one digit will be cheap.\n       * @param owner the owner key for the new account\n       * @param active the active key for the new account\n       * @param registrar_account the account which will pay the fee to register the user\n       * @param referrer_account the account who is acting as a referrer, and may receive a\n       *                         portion of the user's transaction fees.  This can be the\n       *                         same as the registrar_account if there is no referrer.\n       * @param referrer_percent the percentage (0 - 100) of the new user's transaction fees\n       *                         not claimed by the blockchain that will be distributed to the\n       *                         referrer, the rest will be sent to the registrar.  Will be\n       *                         multiplied by GRAPHENE_1_PERCENT when constructing the transaction.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction registering the account\n       */\n      signed_transaction register_account( const string& name,\n                                           const public_key_type& owner,\n                                           const public_key_type& active,\n                                           const string& registrar_account,\n                                           const string& referrer_account,\n                                           uint32_t referrer_percent,\n                                           bool broadcast = false )const;\n\n      /**\n       *  Upgrades an account to prime status.\n       *  This makes the account holder a 'lifetime member'.\n       *\n       * @param account_name_or_id the name or id of the account to upgrade\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction upgrading the account\n       */\n      signed_transaction upgrade_account( const string& account_name_or_id, bool broadcast )const;\n\n      /** Creates a new account and registers it on the blockchain.\n       *\n       * @todo why no referrer_percent here?\n       *\n       * @see suggest_brain_key()\n       * @see register_account()\n       *\n       * @param brain_key the brain key used for generating the account's private keys\n       * @param account_name the name of the account, must be unique on the blockchain.\n       *                     Names with only latin letters and at least one vowel are\n       *                     premium names and expensive to register.\n       *                     Names with only consonants, or at least with a digit, a dot or\n       *                     a minus sign are cheap.\n       * @param registrar_account the account which will pay the fee to register the user\n       * @param referrer_account the account who is acting as a referrer, and may receive a\n       *                         portion of the user's transaction fees.  This can be the\n       *                         same as the registrar_account if there is no referrer.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction registering the account\n       */\n      signed_transaction create_account_with_brain_key( const string& brain_key,\n                                                        const string& account_name,\n                                                        const string& registrar_account,\n                                                        const string& referrer_account,\n                                                        bool broadcast = false )const;\n\n      /** Transfer an amount from one account to another.\n       * @param from the name or id of the account sending the funds\n       * @param to the name or id of the account receiving the funds\n       * @param amount the amount to send (in nominal units -- to send half of a BTS, specify 0.5)\n       * @param asset_symbol_or_id the symbol or id of the asset to send\n       * @param memo a memo to attach to the transaction.  The memo will be encrypted in the\n       *             transaction and readable for the receiver.  There is no length limit\n       *             other than the limit imposed by maximum transaction size, but transaction\n       *             increase with transaction size\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction transferring funds\n       */\n      signed_transaction transfer( const string& from,\n                                   const string& to,\n                                   const string& amount,\n                                   const string& asset_symbol_or_id,\n                                   const string& memo,\n                                   bool broadcast = false )const;\n\n      /**\n       *  This method works just like transfer, except it always broadcasts and\n       *  returns the transaction ID (hash) along with the signed transaction.\n       * @param from the name or id of the account sending the funds\n       * @param to the name or id of the account receiving the funds\n       * @param amount the amount to send (in nominal units -- to send half of a BTS, specify 0.5)\n       * @param asset_symbol_or_id the symbol or id of the asset to send\n       * @param memo a memo to attach to the transaction.  The memo will be encrypted in the\n       *             transaction and readable for the receiver.  There is no length limit\n       *             other than the limit imposed by maximum transaction size, but transaction\n       *             increase with transaction size\n       * @returns the transaction ID (hash) along with the signed transaction transferring funds\n       */\n      pair<transaction_id_type,signed_transaction> transfer2( const string& from,\n                                                              const string& to,\n                                                              const string& amount,\n                                                              const string& asset_symbol_or_id,\n                                                              const string& memo ) const {\n         auto trx = transfer( from, to, amount, asset_symbol_or_id, memo, true );\n         return std::make_pair(trx.id(),trx);\n      }\n\n\n      /**\n       *  This method is used to convert a JSON transaction to its transactin ID.\n       * @param trx a JSON transaction\n       * @return the ID (hash) of the transaction\n       */\n      transaction_id_type get_transaction_id( const signed_transaction& trx )const { return trx.id(); }\n\n\n      /** Sign a memo message.\n       *\n       * @param from the name or id of signing account, or a public key, or a label of a public key\n       * @param to the name or id of receiving account, or a public key, or a label of a public key\n       * @param memo text to sign\n       * @return the signed memo data\n       */\n      memo_data sign_memo( const string& from, const string& to, const string& memo )const;\n\n      /** Read a memo.\n       *\n       * @param memo JSON-encoded memo.\n       * @returns string with decrypted message.\n       */\n      string read_memo( const memo_data& memo )const;\n\n\n      /** Sign a message using an account's memo key. The signature is generated as in\n       *    https://github.com/xeroc/python-graphenelib/blob/d9634d74/graphenecommon/message.py#L64 .\n       *\n       * @param signer the name or id of signing account\n       * @param message text to sign\n       * @return the signed message in an abstract format\n       */\n      signed_message sign_message( const string& signer, const string& message )const;\n\n      /** Verify a message signed with sign_message using the given account's memo key.\n       *\n       * @param message the message text\n       * @param account the account name of the message\n       * @param block the block number of the message\n       * @param msg_time the timestamp of the message\n       * @param sig the message signature\n       * @return true if signature matches\n       */\n      bool verify_message( const string& message, const string& account, int32_t block, const string& msg_time,\n                           const fc::ecc::compact_signature& sig )const;\n\n      /** Verify a message signed with sign_message\n       *\n       * @param message the signed_message structure containing message, meta data and signature\n       * @return true if signature matches\n       */\n      bool verify_signed_message( const signed_message& message )const;\n\n      /** Verify a message signed with sign_message, in its encapsulated form.\n       *\n       * @param message the complete encapsulated message string including separators and line feeds\n       * @return true if signature matches\n       */\n      bool verify_encapsulated_message( const string& message )const;\n\n      /** These methods are used for stealth transfers */\n      ///@{\n      /**\n       * This method can be used to set a label for a public key\n       *\n       * @note No two keys can have the same label.\n       * @param key a public key\n       * @param label a user-defined string as label\n       * @return true if the label was set, otherwise false\n       */\n      bool                        set_key_label( const public_key_type& key, const string& label )const;\n\n      /**\n       * Get label of a public key.\n       * @param key a public key\n       * @return the label if already set by \\c set_key_label(), or an empty string if not set\n       */\n      string                      get_key_label( const public_key_type& key )const;\n\n      /**\n       * Generates a new blind account for the given brain key and assigns it the given label.\n       * @param label a label\n       * @param brain_key the brain key to be used to generate a new blind account\n       * @return the public key of the new account\n       */\n      public_key_type             create_blind_account( const string& label, const string& brain_key )const;\n\n      /**\n       * Return the total balances of all blinded commitments that can be claimed by the\n       * given account key or label.\n       * @param key_or_label a public key in Base58 format or a label\n       * @return the total balances of all blinded commitments that can be claimed by the\n       * given account key or label\n       */\n      vector<asset>                get_blind_balances( const string& key_or_label )const;\n      /**\n       * Get all blind accounts.\n       * @return all blind accounts\n       */\n      map<string, public_key_type, std::less<>> get_blind_accounts()const;\n      /**\n       * Get all blind accounts for which this wallet has the private key.\n       * @return all blind accounts for which this wallet has the private key\n       */\n      map<string, public_key_type, std::less<>> get_my_blind_accounts()const;\n      /**\n       * Get the public key associated with a given label.\n       * @param label a label\n       * @return the public key associated with the given label\n       */\n      public_key_type             get_public_key( const string& label )const;\n      ///@}\n\n      /**\n       * Get all blind receipts to/form a particular account\n       * @param key_or_account a public key in Base58 format or an account\n       * @return all blind receipts to/form the account\n       */\n      vector<blind_receipt> blind_history( const string& key_or_account )const;\n\n      /**\n       * Given a confirmation receipt, this method will parse it for a blinded balance and confirm\n       * that it exists in the blockchain.  If it exists then it will report the amount received and\n       * who sent it.\n       *\n       * @param confirmation_receipt a base58 encoded stealth confirmation\n       * @param opt_from if not empty and the sender is a unknown public key,\n       *                 then the unknown public key will be given the label \\c opt_from\n       * @param opt_memo a self-defined label for this transfer to be saved in local wallet file\n       * @return a blind receipt\n       */\n      blind_receipt receive_blind_transfer( const string& confirmation_receipt,\n                                            const string& opt_from,\n                                            const string& opt_memo )const;\n\n      /**\n       * Transfers a public balance from \\c from_account_name_or_id to one or more blinded balances using a\n       * stealth transfer.\n       * @param from_account_name_or_id name or ID of an account to transfer from\n       * @param asset_symbol_or_id symbol or ID of the asset to be transferred\n       * @param to_amounts map from key or label to amount\n       * @param broadcast true to broadcast the transaction on the network\n       * @return a blind confirmation\n       */\n      blind_confirmation transfer_to_blind( const string& from_account_name_or_id,\n                                            const string& asset_symbol_or_id,\n                                            const vector<pair<string, string>>& to_amounts,\n                                            bool broadcast = false )const;\n\n      /**\n       * Transfers funds from a set of blinded balances to a public account balance.\n       * @param from_blind_account_key_or_label a public key in Base58 format or a label to transfer from\n       * @param to_account_name_or_id name or ID of an account to transfer to\n       * @param amount the amount to be transferred\n       * @param asset_symbol_or_id symbol or ID of the asset to be transferred\n       * @param broadcast true to broadcast the transaction on the network\n       * @return a blind confirmation\n       */\n      blind_confirmation transfer_from_blind( const string& from_blind_account_key_or_label,\n                                              const string& to_account_name_or_id,\n                                              const string& amount,\n                                              const string& asset_symbol_or_id,\n                                              bool broadcast = false )const;\n\n      /**\n       * Transfer from one set of blinded balances to another.\n       * @param from_key_or_label a public key in Base58 format or a label to transfer from\n       * @param to_key_or_label a public key in Base58 format or a label to transfer to\n       * @param amount the amount to be transferred\n       * @param symbol_or_id symbol or ID of the asset to be transferred\n       * @param broadcast true to broadcast the transaction on the network\n       * @return a blind confirmation\n       */\n      blind_confirmation blind_transfer( const string& from_key_or_label,\n                                         const string& to_key_or_label,\n                                         const string& amount,\n                                         const string& symbol_or_id,\n                                         bool broadcast = false )const;\n\n      /** Place a limit order attempting to sell one asset for another.\n       *\n       * Buying and selling are the same operation on BitShares. If you want to buy BTS\n       * with USD, you should sell USD for BTS.\n       *\n       * The blockchain will attempt to sell the \\c symbol_or_id_to_sell for as\n       * much \\c symbol_or_id_to_receive as possible, as long as the price is at\n       * least \\c min_to_receive / \\c amount_to_sell.\n       *\n       * In addition to the transaction fees, market fees will apply as specified\n       * by the issuer of both the selling asset and the receiving asset as\n       * a percentage of the amount exchanged.\n       *\n       * If either the selling asset or the receiving asset is whitelist\n       * restricted, the order will only be created if the seller is on\n       * the whitelist of the restricted asset type.\n       *\n       * Market orders are matched in the order they are included\n       * in the block chain.\n       *\n       * @todo Document default/max expiration time\n       *\n       * @param seller_account the account providing the asset being sold, and which will\n       *                       receive the proceeds of the sale.\n       * @param amount_to_sell the amount of the asset being sold to sell (in nominal units)\n       * @param symbol_or_id_to_sell the symbol or id of the asset to sell\n       * @param min_to_receive the minimum amount you are willing to receive in return for\n       *                       selling the entire amount_to_sell\n       * @param symbol_or_id_to_receive the symbol or id of the asset you wish to receive\n       * @param timeout_sec if the order does not fill immediately, this is the length of\n       *                    time the order will remain on the order books before it is\n       *                    cancelled and the un-spent funds are returned to the seller's\n       *                    account\n       * @param fill_or_kill if true, the order will only be included in the blockchain\n       *                     if it is filled immediately. if false, an open order will be\n       *                     left on the books to fill any amount that cannot be filled\n       *                     immediately.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction selling the funds\n       */\n      signed_transaction sell_asset( const string& seller_account,\n                                     const string& amount_to_sell,\n                                     const string& symbol_or_id_to_sell,\n                                     const string& min_to_receive,\n                                     const string& symbol_or_id_to_receive,\n                                     uint32_t timeout_sec = 0,\n                                     bool     fill_or_kill = false,\n                                     bool     broadcast = false )const;\n\n      /** Borrow an asset or update the debt/collateral ratio for the loan.\n       *\n       * This is the first step in shorting an asset.  Call \\c sell_asset() to complete the short.\n       *\n       * @param borrower the name or id of the account associated with the transaction.\n       * @param amount_to_borrow the amount of the asset being borrowed.  Make this value\n       *                         negative to pay back debt.\n       * @param asset_symbol_or_id the symbol or id of the asset being borrowed.\n       * @param amount_of_collateral the amount of the backing asset to add to your collateral\n       *        position.  Make this negative to claim back some of your collateral.\n       *        The backing asset is defined in the \\c bitasset_options for the asset being borrowed.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction borrowing the asset\n       */\n      signed_transaction borrow_asset( const string& borrower, const string& amount_to_borrow,\n                                       const string& asset_symbol_or_id,\n                                       const string& amount_of_collateral, bool broadcast = false )const;\n\n      /** Borrow an asset or update the debt/collateral ratio for the loan, with additional options.\n       *\n       * This is the first step in shorting an asset.  Call \\c sell_asset() to complete the short.\n       *\n       * @param borrower the name or id of the account associated with the transaction.\n       * @param amount_to_borrow the amount of the asset being borrowed.  Make this value\n       *                         negative to pay back debt.\n       * @param asset_symbol_or_id the symbol or id of the asset being borrowed.\n       * @param amount_of_collateral the amount of the backing asset to add to your collateral\n       *        position.  Make this negative to claim back some of your collateral.\n       *        The backing asset is defined in the \\c bitasset_options for the asset being borrowed.\n       * @param extensions additional options\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction borrowing the asset\n       */\n      signed_transaction borrow_asset_ext( const string& borrower, const string& amount_to_borrow,\n                                           const string& asset_symbol_or_id,\n                                           const string& amount_of_collateral,\n                                           const call_order_update_operation::extensions_type& extensions,\n                                           bool broadcast = false )const;\n\n      /** Cancel an existing order\n       *\n       * @param order_id the id of order to be cancelled\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction canceling the order\n       */\n      signed_transaction cancel_order( const limit_order_id_type& order_id, bool broadcast = false ) const;\n\n      /** Creates a new user-issued or market-issued asset.\n       *\n       * Many options can be changed later using \\c update_asset()\n       *\n       * Right now this function is difficult to use because you must provide raw JSON data\n       * structures for the options objects, and those include prices and asset ids.\n       *\n       * @param issuer the name or id of the account who will pay the fee and become the\n       *               issuer of the new asset.  This can be updated later\n       * @param symbol the ticker symbol of the new asset\n       * @param precision the number of digits of precision to the right of the decimal point,\n       *                  must be less than or equal to 12\n       * @param common asset options required for all new assets.\n       *               Note that core_exchange_rate technically needs to store the asset ID of\n       *               this new asset. Since this ID is not known at the time this operation is\n       *               created, create this price as though the new asset has instance ID 1, and\n       *               the chain will overwrite it with the new asset's ID.\n       * @param bitasset_opts options specific to BitAssets.  This may be null unless the\n       *               \\c market_issued flag is set in common.flags\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction creating a new asset\n       */\n      signed_transaction create_asset( const string& issuer,\n                                       const string& symbol,\n                                       uint8_t precision,\n                                       const asset_options& common,\n                                       const optional<bitasset_options>& bitasset_opts,\n                                       bool broadcast = false )const;\n\n      /** Create the specified amount of the specified asset and credit into the specified account.\n       *\n       * @param to_account the name or id of the account to receive the new supply\n       * @param amount the amount to issue, in nominal units\n       * @param symbol_or_id the ticker symbol or id of the asset to issue\n       * @param memo a memo to include in the transaction, readable by the recipient\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction issuing the new supply\n       */\n      signed_transaction issue_asset( const string& to_account, const string& amount,\n                                      const string& symbol_or_id,\n                                      const string& memo,\n                                      bool broadcast = false )const;\n\n      /** Update the core options on an asset.\n       * There are a number of options which all assets in the network use. These options are\n       * enumerated in the asset_object::asset_options struct. This command is used to update\n       * these options for an existing asset.\n       *\n       * @note This operation cannot be used to update BitAsset-specific options. For these options,\n       * \\c update_bitasset() instead.\n       *\n       * @param symbol_or_id the symbol or id of the asset to update\n       * @param new_issuer if changing the asset's issuer, the name or id of the new issuer.\n       *                   null if you wish to remain the issuer of the asset\n       * @param new_options the new asset_options object, which will entirely replace the existing\n       *                    options.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction updating the asset\n       */\n      signed_transaction update_asset( const string& symbol_or_id,\n                                       const optional<string>& new_issuer,\n                                       const asset_options& new_options,\n                                       bool broadcast = false )const;\n\n      /** Update the issuer of an asset\n       * Since this call requires the owner authority of the current issuer to sign the transaction,\n       * a separated operation is used to change the issuer. This call simplifies the use of this action.\n       *\n       * @note This operation requires the owner key to be available in the wallet.\n       *\n       * @param symbol_or_id the symbol or id of the asset to update\n       * @param new_issuer if changing the asset's issuer, the name or id of the new issuer.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction updating the asset\n       */\n      signed_transaction update_asset_issuer( const string& symbol_or_id,\n                                              const string& new_issuer,\n                                              bool broadcast = false )const;\n\n      /** Update the options specific to a BitAsset.\n       *\n       * BitAssets have some options which are not relevant to other asset types.\n       * This operation is used to update those options an an existing BitAsset.\n       *\n       * @see update_asset()\n       *\n       * @param symbol_or_id the symbol or id of the asset to update, which must be a market-issued asset\n       * @param new_options the new bitasset_options object, which will entirely replace the existing\n       *                    options.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction updating the bitasset\n       */\n      signed_transaction update_bitasset( const string& symbol_or_id,\n                                          const bitasset_options& new_options,\n                                          bool broadcast = false )const;\n\n      /** Update the set of feed-producing accounts for a BitAsset.\n       *\n       * BitAssets have price feeds selected by taking the median values of recommendations from\n       * a set of feed producers.\n       * This command is used to specify which accounts may produce feeds for a given BitAsset.\n       * @param symbol_or_id the symbol or id of the asset to update\n       * @param new_feed_producers a list of account names or ids which are authorized to produce feeds for the asset.\n       *                           this list will completely replace the existing list\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction updating the bitasset's feed producers\n       */\n      signed_transaction update_asset_feed_producers( const string& symbol_or_id,\n                                                      const flat_set<string>& new_feed_producers,\n                                                      bool broadcast = false )const;\n\n      /** Publishes a price feed for the named asset.\n       *\n       * Price feed providers use this command to publish their price feeds for market-issued assets. A price feed is\n       * used to tune the market for a particular market-issued asset. For each value in the feed, the median across\n       * all committee_member feeds for that asset is calculated and the market for the asset is configured with the\n       * median of that value.\n       *\n       * The feed object in this command contains three prices: a call price limit, a short price limit,\n       * and a settlement price.\n       * The call limit price is structured as (collateral asset) / (debt asset) and the short limit price is\n       * structured as (asset for sale) / (collateral asset).\n       * Note that the asset IDs are opposite to eachother, so if we're\n       * publishing a feed for USD, the call limit price will be CORE/USD and the short limit price will be USD/CORE.\n       * The settlement price may be flipped either direction, as long as it is a ratio between the market-issued\n       * asset and its collateral.\n       *\n       * @param publishing_account the account publishing the price feed\n       * @param symbol_or_id the symbol or id of the asset whose feed we're publishing\n       * @param feed the price_feed object containing the three prices making up the feed\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction updating the price feed for the given asset\n       */\n      signed_transaction publish_asset_feed( const string& publishing_account,\n                                             const string& symbol_or_id,\n                                             const price_feed& feed,\n                                             bool broadcast = false )const;\n\n      /** Pay into the fee pool for the given asset.\n       *\n       * User-issued assets can optionally have a pool of the core asset which is\n       * automatically used to pay transaction fees for any transaction using that\n       * asset (using the asset's core exchange rate).\n       *\n       * This command allows anyone to deposit the core asset into this fee pool.\n       *\n       * @param from the name or id of the account sending the core asset\n       * @param symbol_or_id the symbol or id of the asset whose fee pool you wish to fund\n       * @param amount the amount of the core asset to deposit\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction funding the fee pool\n       */\n      signed_transaction fund_asset_fee_pool( const string& from,\n                                              const string& symbol_or_id,\n                                              const string& amount,\n                                              bool broadcast = false )const;\n\n      /** Claim funds from the fee pool for the given asset.\n       *\n       * User-issued assets can optionally have a pool of the core asset which is\n       * automatically used to pay transaction fees for any transaction using that\n       * asset (using the asset's core exchange rate).\n       *\n       * This command allows the issuer to withdraw those funds from the fee pool.\n       *\n       * @param symbol_or_id the symbol or id of the asset whose fee pool you wish to claim\n       * @param amount the amount of the core asset to withdraw\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction claiming from the fee pool\n       */\n      signed_transaction claim_asset_fee_pool( const string& symbol_or_id,\n                                               const string& amount,\n                                               bool broadcast = false )const;\n\n      /** Burns an amount of given asset to its reserve pool.\n       *\n       * This command burns an amount of given asset to reduce the amount in circulation.\n       * @note you cannot burn market-issued assets.\n       * @param from the account containing the asset you wish to burn\n       * @param amount the amount to burn, in nominal units\n       * @param symbol_or_id the symbol or id of the asset to burn\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction burning the asset\n       */\n      signed_transaction reserve_asset( const string& from,\n                                        const string& amount,\n                                        const string& symbol_or_id,\n                                        bool broadcast = false )const;\n\n      /** Forces a global settling of the given asset (black swan or prediction markets).\n       *\n       * In order to use this operation, asset_to_settle must have the \\c global_settle permission set\n       *\n       * When this operation is executed all open margin positions are called at the settle price.\n       * A pool will be formed containing the collateral got from the margin positions.\n       * Users owning an amount of the asset may use \\c settle_asset() to claim collateral instantly\n       * at the settle price from the pool.\n       * If this asset is used as backing for other bitassets, those bitassets will not be affected.\n       *\n       * @note this operation is used only by the asset issuer.\n       *\n       * @param symbol_or_id the symbol or id of the asset to globally settle\n       * @param settle_price the price at which to settle\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction settling the named asset\n       */\n      signed_transaction global_settle_asset( const string& symbol_or_id,\n                                              const price& settle_price,\n                                              bool broadcast = false )const;\n\n      /** Schedules a market-issued asset for automatic settlement.\n       *\n       * Holders of market-issued assests may request a forced settlement for some amount of their asset.\n       * This means that the specified sum will be locked by the chain and held for the settlement period,\n       * after which time the chain will\n       * choose a margin posision holder and buy the settled asset using the margin's collateral.\n       * The price of this sale\n       * will be based on the feed price for the market-issued asset being settled.\n       * The exact settlement price will be the\n       * feed price at the time of settlement with an offset in favor of the margin position, where the offset is a\n       * blockchain parameter set in the asset's bitasset options.\n       *\n       * @param account_to_settle the name or id of the account owning the asset\n       * @param amount_to_settle the amount of the asset to schedule for settlement\n       * @param symbol_or_id the symbol or id of the asset to settle\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction settling the named asset\n       */\n      signed_transaction settle_asset( const string& account_to_settle,\n                                       const string& amount_to_settle,\n                                       const string& symbol_or_id,\n                                       bool broadcast = false )const;\n\n      /** Creates or updates a bid on an MPA after global settlement.\n       *\n       * In order to revive a market-pegged asset after global settlement (aka\n       * black swan), investors can bid collateral in order to take over part of\n       * the debt and the settlement fund, see BSIP-0018. Updating an existing\n       * bid to cover 0 debt will delete the bid.\n       *\n       * @param bidder the name or id of the account making the bid\n       * @param debt_amount the amount of debt of the named asset to bid for\n       * @param debt_symbol_or_id the symbol or id of the MPA to bid for\n       * @param additional_collateral the amount of additional collateral to bid\n       *        for taking over debt_amount. The asset type of this amount is\n       *        determined automatically from \\c debt_symbol_or_id.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction creating/updating the bid\n       */\n      signed_transaction bid_collateral( const string& bidder, const string& debt_amount,\n                                         const string& debt_symbol_or_id,\n                                         const string& additional_collateral, bool broadcast = false )const;\n\n      /** Whitelist and blacklist accounts, primarily for transacting in whitelisted assets.\n       *\n       * Accounts can freely specify opinions about other accounts, in the form of either whitelisting or blacklisting\n       * them. This information is used in chain validation only to determine whether an account is authorized to\n       * transact in an asset type which enforces a whitelist, but third parties can use this information for other\n       * uses as well, as long as it does not conflict with the use of whitelisted assets.\n       *\n       * An asset which enforces a whitelist specifies a list of accounts to maintain its whitelist, and a list of\n       * accounts to maintain its blacklist. In order for a given account A to hold and transact in a whitelisted\n       * asset S, A must be whitelisted by at least one of S's whitelist_authorities and blacklisted by none of S's\n       * blacklist_authorities. If A receives a balance of S, and is later removed from the whitelist(s) which allowed\n       * it to hold S, or added to any blacklist S specifies as authoritative, A's balance of S will be frozen until\n       * A's authorization is reinstated.\n       *\n       * @param authorizing_account the account who is doing the whitelisting\n       * @param account_to_list the account being whitelisted\n       * @param new_listing_status the new whitelisting status\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction changing the whitelisting status\n       */\n      signed_transaction whitelist_account( const string& authorizing_account,\n                                            const string& account_to_list,\n                                            account_whitelist_operation::account_listing new_listing_status,\n                                            bool broadcast = false )const;\n\n      /** Creates a committee_member object owned by the given account.\n       *\n       * An account can have at most one committee_member object.\n       *\n       * @param owner_account the name or id of the account which is creating the committee_member\n       * @param url a URL to include in the committee_member record in the blockchain.  Clients may\n       *            display this when showing a list of committee_members.  May be blank.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction registering a committee_member\n       */\n      signed_transaction create_committee_member( const string& owner_account,\n                                                  const string& url,\n                                                  bool broadcast = false )const;\n\n      /** Lists all witnesses registered in the blockchain.\n       * This returns a list of all account names that own witnesses, and the associated witness id,\n       * sorted by name.  This lists witnesses whether they are currently voted in or not.\n       *\n       * Use the \\c lowerbound and limit parameters to page through the list.  To retrieve all witnesss,\n       * start by setting \\c lowerbound to the empty string \\c \"\", and then each iteration, pass\n       * the last witness name returned as the \\c lowerbound for the next \\c list_witnesss() call.\n       *\n       * @param lowerbound the name of the first witness to return.  If the named witness does not exist,\n       *                   the list will start at the witness that comes after \\c lowerbound\n       * @param limit the maximum number of witnesss to return (max: 1000)\n       * @returns a list of witnesss mapping witness names to witness ids\n       */\n      map<string, witness_id_type, std::less<>> list_witnesses( const string& lowerbound, uint32_t limit )const;\n\n      /** Lists all committee_members registered in the blockchain.\n       * This returns a list of all account names that own committee_members, and the associated committee_member id,\n       * sorted by name.  This lists committee_members whether they are currently voted in or not.\n       *\n       * Use the \\c lowerbound and limit parameters to page through the list.  To retrieve all committee_members,\n       * start by setting \\c lowerbound to the empty string \\c \"\", and then each iteration, pass\n       * the last committee_member name returned as the \\c lowerbound for the next \\c list_committee_members() call.\n       *\n       * @param lowerbound the name of the first committee_member to return.  If the named committee_member does not\n       *                   exist, the list will start at the committee_member that comes after \\c lowerbound\n       * @param limit the maximum number of committee_members to return (max: 1000)\n       * @returns a list of committee_members mapping committee_member names to committee_member ids\n       */\n      map<string, committee_member_id_type, std::less<>> list_committee_members(\n            const string& lowerbound, uint32_t limit )const;\n\n      /** Returns information about the given witness.\n       * @param owner_account the name or id of the witness account owner, or the id of the witness\n       * @returns the information about the witness stored in the block chain\n       */\n      witness_object get_witness( const string& owner_account )const;\n\n      /** Returns information about the given committee_member.\n       * @param owner_account the name or id of the committee_member account owner, or the id of the committee_member\n       * @returns the information about the committee_member stored in the block chain\n       */\n      committee_member_object get_committee_member( const string& owner_account )const;\n\n      /** Creates a witness object owned by the given account.\n       *\n       * An account can have at most one witness object.\n       *\n       * @param owner_account the name or id of the account which is creating the witness\n       * @param url a URL to include in the witness record in the blockchain.  Clients may\n       *            display this when showing a list of witnesses.  May be blank.\n       * @param broadcast true to broadcast the transaction on the network\n       * @returns the signed transaction registering a witness\n       */\n      signed_transaction create_witness( const string& owner_account,\n                                         const string& url,\n                                         bool broadcast = false )const;\n\n      /**\n       * Update a witness object owned by the given account.\n       *\n       * @param witness_name The name of the witness's owner account.\n       *                     Also accepts the ID of the owner account or the ID of the witness.\n       * @param url Same as for create_witness.  The empty string makes it remain the same.\n       * @param block_signing_key The new block signing public key.  The empty string makes it remain the same.\n       * @param broadcast true if you wish to broadcast the transaction.\n       * @return the signed transaction\n       */\n      signed_transaction update_witness( const string& witness_name,\n                                         const string& url,\n                                         const string& block_signing_key,\n                                         bool broadcast = false )const;\n\n\n      /**\n       * Create a worker object.\n       *\n       * @param owner_account The account which owns the worker and will be paid\n       * @param work_begin_date When the work begins\n       * @param work_end_date When the work ends\n       * @param daily_pay Amount of pay per day (NOT per maint interval)\n       * @param name Any text\n       * @param url Any text\n       * @param worker_settings {\"type\" : \"burn\"|\"refund\"|\"vesting\", \"pay_vesting_period_days\" : x}\n       * @param broadcast true if you wish to broadcast the transaction.\n       * @return the signed transaction\n       */\n      signed_transaction create_worker(\n         const string& owner_account,\n         const time_point_sec& work_begin_date,\n         const time_point_sec& work_end_date,\n         const share_type& daily_pay,\n         const string& name,\n         const string& url,\n         const variant& worker_settings,\n         bool broadcast = false\n         )const;\n\n      /**\n       * Update your votes for workers\n       *\n       * @param account The account which will pay the fee and update votes.\n       * @param delta {\"vote_for\" : [...], \"vote_against\" : [...], \"vote_abstain\" : [...]}\n       * @param broadcast true if you wish to broadcast the transaction.\n       * @return the signed transaction\n       */\n      signed_transaction update_worker_votes(\n         const string& account,\n         const worker_vote_delta& delta,\n         bool broadcast = false\n         )const;\n\n      /**\n       * Create a hashed time lock contract\n       *\n       * @param source The account that will reserve the funds (and pay the fee)\n       * @param destination The account that will receive the funds if the preimage is presented\n       * @param amount the amount of the asset that is to be traded\n       * @param asset_symbol_or_id The asset that is to be traded\n       * @param hash_algorithm the algorithm used to generate the hash from the preimage. Can be RIPEMD160 or SHA256.\n       * @param preimage_hash the hash of the preimage\n       * @param preimage_size the size of the preimage in bytes\n       * @param claim_period_seconds how long after creation until the lock expires\n       * @param memo the memo\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed transaction\n       */\n      signed_transaction htlc_create( const string& source, const string& destination,\n            const string& amount, const string& asset_symbol_or_id, const string& hash_algorithm,\n            const string& preimage_hash, uint32_t preimage_size,\n            uint32_t claim_period_seconds, const string& memo, bool broadcast = false ) const;\n\n      /****\n       * Update a hashed time lock contract\n       *\n       * @param htlc_id The object identifier of the HTLC on the blockchain\n       * @param issuer Who is performing this operation (and paying the fee)\n       * @param preimage the preimage that should evaluate to the preimage_hash\n       * @return the signed transaction\n       */\n      signed_transaction htlc_redeem( const htlc_id_type& htlc_id, const string& issuer, const string& preimage,\n            bool broadcast = false ) const;\n\n      /*****\n       * Increase the timelock on an existing HTLC\n       *\n       * @param htlc_id The object identifier of the HTLC on the blockchain\n       * @param issuer Who is performing this operation (and paying the fee)\n       * @param seconds_to_add how many seconds to add to the existing timelock\n       * @param broadcast true to broadcast to the network\n       * @return the signed transaction\n       */\n      signed_transaction htlc_extend( const htlc_id_type& htlc_id, const string& issuer, uint32_t seconds_to_add,\n            bool broadcast = false ) const;\n\n      /**\n       * Get information about a vesting balance object or vesting balance objects owned by an account.\n       *\n       * @param account_name An account name, account ID, or vesting balance object ID.\n       * @return a list of vesting balance objects with additional info\n       */\n      vector< vesting_balance_object_with_info > get_vesting_balances( const string& account_name )const;\n\n      /**\n       * Withdraw a vesting balance.\n       *\n       * @param witness_name The account name of the witness, also accepts account ID or vesting balance ID type.\n       * @param amount The amount to withdraw.\n       * @param asset_symbol_or_id The symbol or id of the asset to withdraw.\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed transaction\n       */\n      signed_transaction withdraw_vesting(\n         const string& witness_name,\n         const string& amount,\n         const string& asset_symbol_or_id,\n         bool broadcast = false )const;\n\n      /** Vote for a given committee_member.\n       *\n       * An account can publish a list of all committee_members they approve of.  This\n       * command allows you to add or remove committee_members from this list.\n       * Each account's vote is weighted according to the number of voting stake\n       * owned by that account at the time the votes are tallied.\n       *\n       * @note you cannot vote against a committee_member, you can only vote for the committee_member\n       *       or not vote for the committee_member.\n       *\n       * @param voting_account the name or id of the account who is voting with their stake\n       * @param committee_member the name or id of the committee_member's owner account\n       * @param approve true if you wish to vote in favor of that committee_member, false to\n       *                remove your vote in favor of that committee_member\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed transaction changing your vote for the given committee_member\n       */\n      signed_transaction vote_for_committee_member( const string& voting_account,\n                                                    const string& committee_member,\n                                                    bool approve,\n                                                    bool broadcast = false )const;\n\n      /** Vote for a given witness.\n       *\n       * An account can publish a list of all witnesses they approve of.  This\n       * command allows you to add or remove witnesses from this list.\n       * Each account's vote is weighted according to the number of voting stake\n       * owned by that account at the time the votes are tallied.\n       *\n       * @note you cannot vote against a witness, you can only vote for the witness\n       *       or not vote for the witness.\n       *\n       * @param voting_account the name or id of the account who is voting with their stake\n       * @param witness the name or id of the witness' owner account\n       * @param approve true if you wish to vote in favor of that witness, false to\n       *                remove your vote in favor of that witness\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed transaction changing your vote for the given witness\n       */\n      signed_transaction vote_for_witness( const string& voting_account,\n                                           const string& witness,\n                                           bool approve,\n                                           bool broadcast = false )const;\n\n      /** Set the voting proxy for an account.\n       *\n       * If a user does not wish to take an active part in voting, they can choose\n       * to allow another account to vote their stake.\n       *\n       * Setting a vote proxy does not remove your previous votes from the blockchain,\n       * they remain there but are ignored.  If you later null out your vote proxy,\n       * your previous votes will take effect again.\n       *\n       * This setting can be changed at any time.\n       *\n       * @param account_to_modify the name or id of the account to update\n       * @param voting_account the name or id of an account authorized to vote account_to_modify's stake,\n       *                       or null to vote your own stake\n       *\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed transaction changing your vote proxy settings\n       */\n      signed_transaction set_voting_proxy( const string& account_to_modify,\n                                           const optional<string>& voting_account,\n                                           bool broadcast = false )const;\n\n      /** Set your vote for the number of witnesses and committee_members in the system.\n       *\n       * Each account can voice their opinion on how many committee_members and how many\n       * witnesses there should be in the active committee_member/active witness list.  These\n       * are independent of each other.  You must vote your approval of at least as many\n       * committee_members or witnesses as you claim there should be (you can't say that there should\n       * be 20 committee_members but only vote for 10).\n       *\n       * There are maximum values for each set in the blockchain parameters (currently\n       * defaulting to 1001).\n       *\n       * This setting can be changed at any time.  If your account has a voting proxy\n       * set, your preferences will be ignored.\n       *\n       * @param account_to_modify the name or id of the account to update\n       * @param desired_number_of_witnesses desired number of active witnesses\n       * @param desired_number_of_committee_members desired number of active committee members\n       *\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed transaction changing your vote proxy settings\n       */\n      signed_transaction set_desired_witness_and_committee_member_count( const string& account_to_modify,\n                                                                uint16_t desired_number_of_witnesses,\n                                                                uint16_t desired_number_of_committee_members,\n                                                                bool broadcast = false )const;\n\n      /** Signs a transaction.\n       *\n       * Given a fully-formed transaction that is only lacking signatures, this signs\n       * the transaction with the necessary keys and optionally broadcasts the transaction\n       * @param tx the transaction to be signed\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed version of the transaction\n       */\n      signed_transaction sign_transaction( const signed_transaction& tx, bool broadcast = false )const;\n\n      /** Signs a transaction.\n       *\n       * Given a fully-formed transaction that is only lacking signatures, this signs\n       * the transaction with the inferred necessary keys and the explicitly provided keys,\n       * and optionally broadcasts the transaction\n       * @param tx the transaction to be signed\n       * @param signing_keys Keys that must be used when signing the transaction\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed version of the transaction\n       */\n      signed_transaction sign_transaction2( const signed_transaction& tx,\n                                            const vector<public_key_type>& signing_keys = vector<public_key_type>(),\n                                            bool broadcast = true )const;\n\n\n      /** Get transaction signers.\n       *\n       * Returns information about who signed the transaction, specifically,\n       * the corresponding public keys of the private keys used to sign the transaction.\n       * @param tx the signed transaction\n       * @return the set of public_keys\n       */\n      flat_set<public_key_type> get_transaction_signers( const signed_transaction& tx ) const;\n\n      /** Get key references.\n       *\n       * Returns accounts related to given public keys.\n       * @param keys public keys to search for related accounts\n       * @return the set of related accounts\n       */\n      vector<flat_set<account_id_type>> get_key_references( const vector<public_key_type>& keys ) const;\n\n      /** Returns an uninitialized object representing a given blockchain operation.\n       *\n       * This returns a default-initialized object of the given type. It can be used\n       * during early development of the wallet when we don't yet have custom commands for\n       * creating all of the operations the blockchain supports.\n       *\n       * Any operation the blockchain supports can be created using the transaction builder's\n       * \\c add_operation_to_builder_transaction() , but to do that from the CLI you need to\n       * know what the JSON form of the operation looks like.  This will give you a template\n       * you can fill in.  It's better than nothing.\n       *\n       * @param operation_type the type of operation to return, must be one of the\n       *                       operations defined in `graphene/protocol/operations.hpp`\n       *                       (e.g., \"global_parameters_update_operation\")\n       * @return a default-constructed operation of the given type\n       */\n      operation get_prototype_operation( const string& operation_type )const;\n\n      /** Creates a transaction to propose a parameter change.\n       *\n       * Multiple parameters can be specified if an atomic change is\n       * desired.\n       *\n       * @param proposing_account The account paying the fee to propose the tx\n       * @param expiration_time Timestamp specifying when the proposal will either take effect or expire.\n       * @param changed_values The values to change. All other chain parameters are filled in with default values\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed version of the transaction\n       */\n      signed_transaction propose_parameter_change(\n         const string& proposing_account,\n         const time_point_sec& expiration_time,\n         const variant_object& changed_values,\n         bool broadcast = false )const;\n\n      /** Propose a fee change.\n       *\n       * @param proposing_account The account paying the fee to propose the tx\n       * @param expiration_time Timestamp specifying when the proposal will either take effect or expire.\n       * @param changed_values Map of operation type to new fee.  Operations may be specified by name or ID.\n       *    The \"scale\" key changes the scale.  All other operations will maintain current values.\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed version of the transaction\n       */\n      signed_transaction propose_fee_change(\n         const string& proposing_account,\n         const time_point_sec& expiration_time,\n         const variant_object& changed_values,\n         bool broadcast = false )const;\n\n      /** Approve or disapprove a proposal.\n       *\n       * @param fee_paying_account The account paying the fee for the op.\n       * @param proposal_id The proposal to modify.\n       * @param delta Members contain approvals to create or remove.  In JSON you can leave empty members undefined.\n       * @param broadcast true if you wish to broadcast the transaction\n       * @return the signed version of the transaction\n       */\n      signed_transaction approve_proposal(\n         const string& fee_paying_account,\n         const string& proposal_id,\n         const approval_delta& delta,\n         bool broadcast /* = false */\n         )const;\n\n      /**\n       * Returns the order book for the market base:quote.\n       * @param base symbol or ID of the base asset\n       * @param quote symbol or ID of the quote asset\n       * @param limit depth of the order book to retrieve, for bids and asks each, capped at 50\n       * @return Order book of the market\n       */\n      order_book get_order_book( const string& base, const string& quote, uint32_t limit = 50 )const;\n\n      /** Signs a transaction.\n       *\n       * Given a fully-formed transaction with or without signatures, signs\n       * the transaction with the owned keys and optionally broadcasts the\n       * transaction.\n       *\n       * @param tx the transaction to add signature to\n       * @param broadcast true if you wish to broadcast the transaction\n       *\n       * @return the signed transaction\n       */\n      signed_transaction add_transaction_signature( const signed_transaction& tx,\n                                                    bool broadcast = false )const;\n\n      void dbg_make_uia( const string& creator, const string& symbol )const;\n      void dbg_make_mia( const string& creator, const string& symbol )const;\n      void dbg_push_blocks( const string& src_filename, uint32_t count )const;\n      void dbg_generate_blocks( const string& debug_wif_key, uint32_t count )const;\n      void dbg_stream_json_objects( const string& filename )const;\n      void dbg_update_object( const variant_object& update )const;\n\n      void flood_network( const string& prefix, uint32_t number_of_transactions )const;\n\n      void network_add_nodes( const vector<string>& nodes )const;\n      vector< variant > network_get_connected_peers()const;\n\n      /**\n       *  Used to transfer from one set of blinded balances to another\n       */\n      blind_confirmation blind_transfer_help( const string& from_key_or_label,\n                                              const string& to_key_or_label,\n                                              const string& amount,\n                                              const string& symbol,\n                                              bool broadcast = false,\n                                              bool to_temp = false )const;\n\n\n      std::map< string, std::function< string( const variant&, const fc::variants& ) >, std::less<> >\n            get_result_formatters() const;\n\n      void encrypt_keys()const;\n\n      /**\n       * Manage account storage map(key->value) by using the custom operations plugin.\n       *\n       * Each account can optionally add random information in the form of a key-value map\n       * to be retrieved by any interested party.\n       *\n       * @param account The account name or ID that we are adding additional information to.\n       * @param catalog The name of the catalog the operation will insert data to.\n       * @param is_to_remove true if you want to remove stuff from a catalog.\n       * @param key_values The map to be inserted/removed to/from the catalog\n       * @param broadcast true if you wish to broadcast the transaction\n       *\n       * @return The signed transaction\n       */\n      signed_transaction account_store_map( const string& account, const string& catalog, bool is_to_remove,\n            const flat_map<string, optional<string>>& key_values, bool broadcast )const;\n\n      /**\n       * Get \\c account_storage_object of an account by using the custom operations plugin.\n       *\n       * Storage data added to the map with @ref account_store_map will be returned.\n       *\n       * @param account Account name or ID to get stored data from.\n       * @param catalog The catalog to retrieve.\n       *\n       * @return An \\c account_storage_object or empty.\n       */\n      vector<account_storage_object> get_account_storage( const string& account, const string& catalog )const;\n\n};\n\n} }\n\nextern template class fc::api<graphene::wallet::wallet_api>;\n\nFC_API( graphene::wallet::wallet_api,\n        (help)\n        (gethelp)\n        (info)\n        (about)\n        (begin_builder_transaction)\n        (add_operation_to_builder_transaction)\n        (replace_operation_in_builder_transaction)\n        (set_fees_on_builder_transaction)\n        (preview_builder_transaction)\n        (sign_builder_transaction)\n        (sign_builder_transaction2)\n        (broadcast_transaction)\n        (propose_builder_transaction)\n        (propose_builder_transaction2)\n        (remove_builder_transaction)\n        (is_new)\n        (is_locked)\n        (lock)(unlock)(set_password)\n        (dump_private_keys)\n        (list_my_accounts)\n        (list_accounts)\n        (list_account_balances)\n        (list_assets)\n        (get_asset_count)\n        (import_key)\n        (import_accounts)\n        (import_account_keys)\n        (import_balance)\n        (suggest_brain_key)\n        (derive_owner_keys_from_brain_key)\n        (register_account)\n        (upgrade_account)\n        (create_account_with_brain_key)\n        (sell_asset)\n        (borrow_asset)\n        (borrow_asset_ext)\n        (cancel_order)\n        (transfer)\n        (transfer2)\n        (get_transaction_id)\n        (create_asset)\n        (update_asset)\n        (update_asset_issuer)\n        (update_bitasset)\n        (get_htlc)\n        (update_asset_feed_producers)\n        (publish_asset_feed)\n        (issue_asset)\n        (get_asset)\n        (get_asset_id)\n        (get_asset_name)\n        (get_asset_symbol)\n        (get_bitasset_data)\n        (fund_asset_fee_pool)\n        (claim_asset_fee_pool)\n        (reserve_asset)\n        (global_settle_asset)\n        (settle_asset)\n        (bid_collateral)\n        (whitelist_account)\n        (create_committee_member)\n        (get_witness)\n        (get_committee_member)\n        (list_witnesses)\n        (list_committee_members)\n        (create_witness)\n        (update_witness)\n        (create_worker)\n        (update_worker_votes)\n        (htlc_create)\n        (htlc_redeem)\n        (htlc_extend)\n        (get_vesting_balances)\n        (withdraw_vesting)\n        (vote_for_committee_member)\n        (vote_for_witness)\n        (set_voting_proxy)\n        (set_desired_witness_and_committee_member_count)\n        (get_account)\n        (get_account_id)\n        (get_account_name)\n        (get_block)\n        (get_account_count)\n        (get_account_history)\n        (get_relative_account_history)\n        (get_account_history_by_operations)\n        (get_collateral_bids)\n        (is_public_key_registered)\n        (get_full_account)\n        (get_market_history)\n        (get_global_properties)\n        (get_dynamic_global_properties)\n        (get_object)\n        (get_private_key)\n        (load_wallet_file)\n        (normalize_brain_key)\n        (get_account_limit_orders)\n        (get_limit_orders)\n        (get_call_orders)\n        (get_settle_orders)\n        (save_wallet_file)\n        (serialize_transaction)\n        (sign_transaction)\n        (sign_transaction2)\n        (add_transaction_signature)\n        (get_transaction_signers)\n        (get_key_references)\n        (get_prototype_operation)\n        (propose_parameter_change)\n        (propose_fee_change)\n        (approve_proposal)\n        (dbg_make_uia)\n        (dbg_make_mia)\n        (dbg_push_blocks)\n        (dbg_generate_blocks)\n        (dbg_stream_json_objects)\n        (dbg_update_object)\n        (flood_network)\n        (network_add_nodes)\n        (network_get_connected_peers)\n        (sign_memo)\n        (read_memo)\n        (sign_message)\n        (verify_message)\n        (verify_signed_message)\n        (verify_encapsulated_message)\n        (set_key_label)\n        (get_key_label)\n        (get_public_key)\n        (get_blind_accounts)\n        (get_my_blind_accounts)\n        (get_blind_balances)\n        (create_blind_account)\n        (transfer_to_blind)\n        (transfer_from_blind)\n        (blind_transfer)\n        (blind_history)\n        (receive_blind_transfer)\n        (get_order_book)\n        (account_store_map)\n        (get_account_storage)\n        (quit)\n      )\n"
  },
  {
    "path": "libraries/wallet/include/graphene/wallet/wallet_structs.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <string>\n#include <vector>\n\nusing namespace graphene::app;\nusing namespace graphene::chain;\nusing namespace graphene::utilities;\nusing std::string;\nusing std::vector;\n\nnamespace graphene { namespace wallet {\n\nusing transaction_handle_type = uint32_t;\n\nstruct plain_keys\n{\n   map<public_key_type, string>  keys;\n   fc::sha512                    checksum;\n};\n\nstruct brain_key_info\n{\n   string brain_priv_key;\n   string wif_priv_key;\n   public_key_type pub_key;\n};\n\n\n/**\n *  Contains the confirmation receipt the sender must give the receiver and\n *  the meta data about the receipt that helps the sender identify which receipt is\n *  for the receiver and which is for the change address.\n */\nstruct blind_confirmation\n{\n   struct output\n   {\n      string                          label;\n      public_key_type                 pub_key;\n      stealth_confirmation::memo_data decrypted_memo;\n      stealth_confirmation            confirmation;\n      authority                       auth;\n      string                          confirmation_receipt;\n   };\n\n   signed_transaction     trx;\n   vector<output>         outputs;\n};\n\nstruct blind_balance\n{\n   asset                     amount;\n   public_key_type           from; ///< the account this balance came from\n   public_key_type           to; ///< the account this balance is logically associated with\n   public_key_type           one_time_key; ///< used to derive the authority key and blinding factor\n   fc::sha256                blinding_factor;\n   fc::ecc::commitment_type  commitment;\n   bool                      used = false;\n};\n\nstruct blind_receipt\n{\n   std::pair<public_key_type,fc::time_point>        from_date()const { return std::make_pair(from_key,date); }\n   std::pair<public_key_type,fc::time_point>        to_date()const   { return std::make_pair(to_key,date);   }\n   std::tuple<public_key_type,asset_id_type,bool>   to_asset_used()const\n   { return std::make_tuple(to_key,amount.asset_id,used);   }\n\n   const commitment_type& commitment()const        { return data.commitment; }\n\n   fc::time_point                  date;\n   public_key_type                 from_key;\n   string                          from_label;\n   public_key_type                 to_key;\n   string                          to_label;\n   asset                           amount;\n   string                          memo;\n   authority                       control_authority;\n   stealth_confirmation::memo_data data;\n   bool                            used = false;\n   stealth_confirmation            conf;\n};\n\nstruct by_from;\nstruct by_to;\nstruct by_to_asset_used;\nstruct by_commitment;\n\ntypedef multi_index_container< blind_receipt,\n   indexed_by<\n      ordered_unique< tag<by_commitment>,\n                      const_mem_fun< blind_receipt, const commitment_type&, &blind_receipt::commitment > >,\n      ordered_unique< tag<by_to>,\n                      const_mem_fun< blind_receipt,\n                                     std::pair<public_key_type,fc::time_point>,\n                                     &blind_receipt::to_date > >,\n      ordered_non_unique< tag<by_to_asset_used>,\n                          const_mem_fun< blind_receipt,\n                                         std::tuple<public_key_type,asset_id_type,bool>,\n                                         &blind_receipt::to_asset_used > >,\n      ordered_unique< tag<by_from>,\n                      const_mem_fun< blind_receipt,\n                                     std::pair<public_key_type,fc::time_point>,\n                                     &blind_receipt::from_date > >\n   >\n> blind_receipt_index_type;\n\n\nstruct key_label\n{\n   string          label;\n   public_key_type key;\n};\n\n\nstruct by_label;\nstruct by_key;\ntypedef multi_index_container<\n   key_label,\n   indexed_by<\n      ordered_unique< tag<by_label>, member< key_label, string, &key_label::label > >,\n      ordered_unique< tag<by_key>, member< key_label, public_key_type, &key_label::key > >\n   >\n> key_label_index_type;\n\n\nstruct wallet_data\n{\n   /** Chain ID this wallet is used with */\n   chain_id_type chain_id;\n   account_multi_index_type my_accounts;\n   /// @return IDs of all accounts in @ref my_accounts\n   vector<object_id_type> my_account_ids()const\n   {\n      vector<object_id_type> ids;\n      ids.reserve(my_accounts.size());\n      std::transform(my_accounts.begin(), my_accounts.end(), std::back_inserter(ids),\n                     [](const account_object& ao) { return ao.id; });\n      return ids;\n   }\n   /// Add acct to @ref my_accounts, or update it if it is already in @ref my_accounts\n   /// @return true if the account was newly inserted; false if it was only updated\n   bool update_account(const account_object& acct)\n   {\n      auto& idx = my_accounts.get<by_id>();\n      auto itr = idx.find(acct.id);\n      if( itr != idx.end() )\n      {\n         idx.replace(itr, acct);\n         return false;\n      } else {\n         idx.insert(acct);\n         return true;\n      }\n   }\n\n   /** encrypted keys */\n   vector<char>              cipher_keys;\n\n   /** map an account to a set of extra keys that have been imported for that account */\n   map<account_id_type, set<public_key_type> >  extra_keys;\n\n   // map of account_name -> base58_private_key for\n   //    incomplete account regs\n   map<string, vector<string> > pending_account_registrations;\n   map<string, string> pending_witness_registrations;\n\n   key_label_index_type                                              labeled_keys;\n   blind_receipt_index_type                                          blind_receipts;\n\n   string                    ws_server = \"ws://localhost:8090\";\n   string                    ws_user;\n   string                    ws_password;\n};\n\nstruct exported_account_keys\n{\n    string account_name;\n    vector<vector<char>> encrypted_private_keys;\n    vector<public_key_type> public_keys;\n};\n\nstruct exported_keys\n{\n    fc::sha512 password_checksum;\n    vector<exported_account_keys> account_keys;\n};\n\nstruct approval_delta\n{\n   vector<string> active_approvals_to_add;\n   vector<string> active_approvals_to_remove;\n   vector<string> owner_approvals_to_add;\n   vector<string> owner_approvals_to_remove;\n   vector<string> key_approvals_to_add;\n   vector<string> key_approvals_to_remove;\n};\n\nstruct worker_vote_delta\n{\n   flat_set<worker_id_type> vote_for;\n   flat_set<worker_id_type> vote_against;\n   flat_set<worker_id_type> vote_abstain;\n};\n\nstruct signed_block_with_info : public signed_block\n{\n   explicit signed_block_with_info( const signed_block& block );\n\n   block_id_type block_id;\n   public_key_type signing_key;\n   vector< transaction_id_type > transaction_ids;\n};\n\nstruct vesting_balance_object_with_info : public vesting_balance_object\n{\n   vesting_balance_object_with_info( const vesting_balance_object& vbo, const fc::time_point_sec& now );\n\n   /**\n    * How much is allowed to be withdrawn.\n    */\n   asset allowed_withdraw;\n\n   /**\n    * The time at which allowed_withdrawal was calculated.\n    */\n   fc::time_point_sec allowed_withdraw_time;\n};\n\nstruct signed_message_meta {\n   string account;\n   public_key_type memo_key;\n   uint32_t block;\n   string time;\n};\n\nclass signed_message {\npublic:\n   string message;\n   signed_message_meta meta;\n   fc::optional<fc::ecc::compact_signature> signature;\n\n   fc::sha256 digest()const;\n};\n\nnamespace detail {\nclass wallet_api_impl;\n}\n\n/***\n * A utility class for performing various state-less actions that are related to wallets\n */\nclass utility {\n   public:\n      /**\n       * Derive any number of *possible* owner keys from a given brain key.\n       *\n       * NOTE: These keys may or may not match with the owner keys of any account.\n       * This function is merely intended to assist with account or key recovery.\n       *\n       * @see suggest_brain_key()\n       *\n       * @param brain_key    Brain key\n       * @param number_of_desired_keys  Number of desired keys\n       * @return A list of keys that are deterministically derived from the brainkey\n       */\n      static vector<brain_key_info> derive_owner_keys_from_brain_key( const string& brain_key,\n                                                                      uint32_t number_of_desired_keys = 1 );\n\n      /** Suggests a safe brain key to use for creating your account.\n       * \\c create_account_with_brain_key() requires you to specify a 'brain key',\n       * a long passphrase that provides enough entropy to generate cyrptographic\n       * keys.  This function will suggest a suitably random string that should\n       * be easy to write down (and, with effort, memorize).\n       * @returns a suggested brain_key\n       */\n      static brain_key_info suggest_brain_key();\n};\n\nstruct operation_detail {\n   string                   memo;\n   string                   description;\n   operation_history_object op;\n};\n\nstruct operation_detail_ex {\n    string                   memo;\n    string                   description;\n    operation_history_object op;\n    transaction_id_type      transaction_id;\n};\n\nstruct account_history_operation_detail {\n   uint32_t                     total_count = 0;\n   uint32_t                     result_count = 0;\n   vector<operation_detail_ex>  details;\n};\n\n}} // namespace graphene::wallet\n\nFC_REFLECT( graphene::wallet::key_label, (label)(key) )\nFC_REFLECT( graphene::wallet::blind_balance, (amount)(from)(to)(one_time_key)(blinding_factor)(commitment)(used) )\nFC_REFLECT( graphene::wallet::blind_confirmation::output,\n            (label)(pub_key)(decrypted_memo)(confirmation)(auth)(confirmation_receipt) )\nFC_REFLECT( graphene::wallet::blind_confirmation, (trx)(outputs) )\n\nFC_REFLECT( graphene::wallet::plain_keys, (keys)(checksum) )\n\nFC_REFLECT( graphene::wallet::wallet_data,\n            (chain_id)\n            (my_accounts)\n            (cipher_keys)\n            (extra_keys)\n            (pending_account_registrations)(pending_witness_registrations)\n            (labeled_keys)\n            (blind_receipts)\n            (ws_server)\n            (ws_user)\n            (ws_password)\n          )\n\nFC_REFLECT( graphene::wallet::brain_key_info,\n            (brain_priv_key)\n            (wif_priv_key)\n            (pub_key)\n          )\n\nFC_REFLECT( graphene::wallet::exported_account_keys, (account_name)(encrypted_private_keys)(public_keys) )\n\nFC_REFLECT( graphene::wallet::exported_keys, (password_checksum)(account_keys) )\n\nFC_REFLECT( graphene::wallet::blind_receipt,\n            (date)(from_key)(from_label)(to_key)(to_label)(amount)(memo)(control_authority)(data)(used)(conf) )\n\nFC_REFLECT( graphene::wallet::approval_delta,\n   (active_approvals_to_add)\n   (active_approvals_to_remove)\n   (owner_approvals_to_add)\n   (owner_approvals_to_remove)\n   (key_approvals_to_add)\n   (key_approvals_to_remove)\n)\n\nFC_REFLECT( graphene::wallet::worker_vote_delta,\n   (vote_for)\n   (vote_against)\n   (vote_abstain)\n)\n\nFC_REFLECT_DERIVED( graphene::wallet::signed_block_with_info, (graphene::chain::signed_block),\n   (block_id)(signing_key)(transaction_ids) )\n\nFC_REFLECT_DERIVED( graphene::wallet::vesting_balance_object_with_info, (graphene::chain::vesting_balance_object),\n   (allowed_withdraw)(allowed_withdraw_time) )\n\nFC_REFLECT( graphene::wallet::operation_detail,\n            (memo)(description)(op) )\n\nFC_REFLECT(graphene::wallet::operation_detail_ex,\n            (memo)(description)(op)(transaction_id))\n\nFC_REFLECT( graphene::wallet::account_history_operation_detail,\n        (total_count)(result_count)(details))\n\nFC_REFLECT( graphene::wallet::signed_message_meta, (account)(memo_key)(block)(time) )\nFC_REFLECT( graphene::wallet::signed_message, (message)(meta)(signature) )\n"
  },
  {
    "path": "libraries/wallet/operation_printer.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"operation_printer.hpp\"\n#include <graphene/protocol/base.hpp>\n#include \"wallet_api_impl.hpp\"\n#include <graphene/utilities/key_conversion.hpp>\n\nnamespace graphene { namespace wallet { namespace detail {\n\nclass htlc_hash_to_string_visitor\n{\npublic:\n   typedef std::string result_type;\n\n   result_type operator()( const fc::ripemd160& hash )const\n   {\n      return \"RIPEMD160 \" + hash.str();\n   }\n\n   result_type operator()( const fc::sha1& hash )const\n   {\n      return \"SHA1 \" + hash.str();\n   }\n\n   result_type operator()( const fc::sha256& hash )const\n   {\n      return \"SHA256 \" + hash.str();\n   }\n   result_type operator()( const fc::hash160& hash )const\n   {\n      return \"HASH160 \" + hash.str();\n   }\n};\n\nstd::string operation_printer::format_asset(const graphene::protocol::asset& a)const\n{\n   return wallet.get_asset(a.asset_id).amount_to_pretty_string(a);\n}\n\nvoid operation_printer::print_fee(const graphene::protocol::asset& a)const\n{\n   out << \"   (Fee: \" << format_asset(a) << \")\";\n}\n\nvoid operation_printer::print_result()const\n{\n   operation_result_printer rprinter(wallet);\n   std::string str_result = result.visit(rprinter);\n   if( str_result != \"\" )\n   {\n      out << \"   result: \" << str_result;\n   }\n}\n\nstring operation_printer::print_memo( const fc::optional<graphene::protocol::memo_data>& memo )const\n{\n   std::string outstr;\n   if( memo )\n   {\n      if( wallet.is_locked() )\n      {\n         out << \" -- Unlock wallet to see memo.\";\n      } else {\n         try {\n            FC_ASSERT( wallet._keys.count(memo->to) > 0 || wallet._keys.count(memo->from) > 0,\n                       \"Memo is encrypted to a key ${to} or ${from} not in this wallet.\",\n                       (\"to\", memo->to)(\"from\",memo->from) );\n            if( wallet._keys.count(memo->to) > 0 ) {\n               auto my_key = wif_to_key(wallet._keys.at(memo->to));\n               FC_ASSERT(my_key, \"Unable to recover private key to decrypt memo. Wallet may be corrupted.\");\n               outstr = memo->get_message(*my_key, memo->from);\n               out << \" -- Memo: \" << outstr;\n            } else {\n               auto my_key = wif_to_key(wallet._keys.at(memo->from));\n               FC_ASSERT(my_key, \"Unable to recover private key to decrypt memo. Wallet may be corrupted.\");\n               outstr = memo->get_message(*my_key, memo->to);\n               out << \" -- Memo: \" << outstr;\n            }\n         } catch (const fc::exception& e) {\n            out << \" -- could not decrypt memo\";\n         }\n      }\n   }\n   return outstr;\n}\n\nvoid operation_printer::print_preimage(const std::vector<char>& preimage)const\n{\n   if (preimage.size() == 0)\n      return;\n   out << \" with preimage \\\"\";\n   // cut it at 300 bytes max\n   auto flags = out.flags();\n   out << std::hex << setw(2) << setfill('0');\n   for (size_t i = 0; i < std::min<size_t>(300, preimage.size()); i++)\n      out << +preimage[i];\n   out.flags(flags);\n   if (preimage.size() > 300)\n      out << \"...(truncated due to size)\";\n   out << \"\\\"\";\n}\n\nvoid operation_printer::print_redeem(const graphene::protocol::htlc_id_type& id,\n      const std::string& redeemer, const std::vector<char>& preimage,\n      const graphene::protocol::asset& op_fee)const\n{\n   out << redeemer << \" redeemed HTLC with id \"\n         << std::string( static_cast<object_id_type>(id));\n   print_preimage( preimage );\n   print_fee(op_fee);\n}\n\nstd::string operation_printer::operator()(const transfer_from_blind_operation& op)const\n{\n   auto receiver = wallet.get_account( op.to );\n\n   out <<  receiver.name\n       << \" received \" << format_asset( op.amount ) << \" from blinded balance\";\n   return \"\";\n}\nstd::string operation_printer::operator()(const transfer_to_blind_operation& op)const\n{\n   auto sender = wallet.get_account( op.from );\n\n   out <<  sender.name\n       << \" sent \" << format_asset( op.amount ) << \" to \" << op.outputs.size()\n       << \" blinded balance\" << (op.outputs.size()>1?\"s\":\"\");\n   print_fee( op.fee );\n   return \"\";\n}\n\nstring operation_printer::operator()(const transfer_operation& op) const\n{\n   out << \"Transfer \" << format_asset(op.amount)\n       << \" from \" << wallet.get_account(op.from).name << \" to \" << wallet.get_account(op.to).name;\n   std::string memo = print_memo( op.memo );\n   print_fee(op.fee);\n   return memo;\n}\n\nstring operation_printer::operator()(const override_transfer_operation& op) const\n{\n   out << wallet.get_account(op.issuer).name\n       << \" force-transfer \" << format_asset(op.amount)\n       << \" from \" << wallet.get_account(op.from).name << \" to \" << wallet.get_account(op.to).name;\n   std::string memo = print_memo( op.memo );\n   print_fee(op.fee);\n   return memo;\n}\n\nstd::string operation_printer::operator()(const account_create_operation& op) const\n{\n   out << \"Create Account '\" << op.name << \"' with registrar \"\n       << wallet.get_account(op.registrar).name << \" and referrer \"\n       << wallet.get_account(op.referrer).name;\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const account_update_operation& op) const\n{\n   out << \"Update Account '\" << wallet.get_account(op.account).name << \"'\";\n   print_fee(op.fee);\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_create_operation& op) const\n{\n   out << \"Create \";\n   if( op.bitasset_opts.valid() )\n      out << \"BitAsset \";\n   else\n      out << \"User-Issue Asset \";\n   out << \"'\" << op.symbol << \"' with issuer \" << wallet.get_account(op.issuer).name;\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_update_operation& op) const\n{\n   out << \"Update asset '\" << wallet.get_asset(op.asset_to_update).symbol << \"'\";\n   print_fee(op.fee);\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_update_bitasset_operation& op) const\n{\n   out << \"Update bitasset options of '\" << wallet.get_asset(op.asset_to_update).symbol << \"'\";\n   print_fee(op.fee);\n   return \"\";\n}\n\nstring operation_printer::operator()(const asset_issue_operation& op) const\n{\n   out << wallet.get_account(op.issuer).name\n       << \" issue \" << format_asset(op.asset_to_issue)\n       << \" to \" << wallet.get_account(op.issue_to_account).name;\n   std::string memo = print_memo( op.memo );\n   print_fee(op.fee);\n   return memo;\n}\n\nstring operation_printer::operator()(const asset_reserve_operation& op) const\n{\n   out << \"Reserve (burn) \" << format_asset(op.amount_to_reserve);\n   print_fee(op.fee);\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_settle_operation& op) const\n{\n   out << \"Force-settle \" << format_asset(op.amount);\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_fund_fee_pool_operation& op) const\n{\n   out << \"Fund \" << format_asset(op.amount) << \" into asset fee pool of \"\n       << wallet.get_asset(op.asset_id).symbol;\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_claim_pool_operation& op) const\n{\n   out << \"Claim \" << format_asset(op.amount_to_claim) << \" from asset fee pool of \"\n       << wallet.get_asset(op.asset_id).symbol;\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_update_feed_producers_operation& op) const\n{\n   out << \"Update price feed producers of asset \" << wallet.get_asset(op.asset_to_update).symbol\n       << \" to \";\n   vector<string> accounts;\n   accounts.reserve( op.new_feed_producers.size() );\n   for( const auto& account_id : op.new_feed_producers )\n   {\n      accounts.push_back( wallet.get_account( account_id ).name );\n   }\n   out << fc::json::to_string(accounts);\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const asset_publish_feed_operation& op) const\n{\n   // TODO prettify the price\n   out << \"Publish price feed \" << format_asset(op.feed.settlement_price.base)\n       << \" / \" << format_asset(op.feed.settlement_price.quote);\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const call_order_update_operation& op) const\n{\n   out << \"Adjust debt position with delta debt amount \" << format_asset(op.delta_debt)\n       << \" and delta collateral amount \" << format_asset(op.delta_collateral);\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const limit_order_create_operation& op) const\n{\n   out << \"Create limit order to sell \" << format_asset(op.amount_to_sell)\n       << \" for \" << format_asset(op.min_to_receive);\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const limit_order_cancel_operation& op) const\n{\n   out << \"Cancel limit order \" << std::string( static_cast<object_id_type>(op.order) );\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const fill_order_operation& op) const\n{\n   out << \"Pays \" << format_asset(op.pays) << \" for \" << format_asset(op.receives)\n       << \" for order \" << std::string( static_cast<object_id_type>(op.order_id) )\n       << \" as \" << ( op.is_maker ? \"maker\" : \"taker\" );\n   print_fee(op.fee);\n   print_result();\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const proposal_update_operation& op) const\n{\n   out << \"Update proposal \" << std::string( static_cast<object_id_type>(op.proposal) );\n   print_fee(op.fee);\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const htlc_redeem_operation& op) const\n{\n   print_redeem(op.htlc_id, wallet.get_account(op.redeemer).name, op.preimage, op.fee);\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const htlc_redeemed_operation& op) const\n{\n   print_redeem(op.htlc_id, wallet.get_account(op.redeemer).name, op.preimage, op.fee);\n   return \"\";\n}\n\nstd::string operation_printer::operator()(const htlc_create_operation& op) const\n{\n   static htlc_hash_to_string_visitor vtor;\n\n   auto fee_asset = wallet.get_asset( op.fee.asset_id );\n   auto to = wallet.get_account( op.to );\n   auto from = wallet.get_account( op.from );\n   operation_result_printer rprinter(wallet);\n   std::string database_id = result.visit(rprinter);\n\n   out << \"Create HTLC from \" << from.name << \" to \" << to.name\n         << \" with id \" << database_id\n         << \" preimage hash: [\" << op.preimage_hash.visit( vtor ) << \"] \";\n   print_memo( op.extensions.value.memo );\n   // determine if the block that the HTLC is in is before or after LIB\n   int32_t pending_blocks = hist.block_num - wallet.get_dynamic_global_properties().last_irreversible_block_num;\n   if (pending_blocks > 0)\n      out << \" (pending \" << std::to_string(pending_blocks) << \" blocks)\";\n   print_fee(op.fee);\n   return \"\";\n}\n\nstd::string operation_result_printer::operator()(const void_result& x) const\n{\n   return \"\";\n}\n\nstd::string operation_result_printer::operator()(const graphene::protocol::object_id_type& oid) const\n{\n   return std::string(oid);\n}\n\nstd::string operation_result_printer::operator()(const asset& a) const\n{\n   return _wallet.get_asset(a.asset_id).amount_to_pretty_string(a);\n}\n\nstd::string operation_result_printer::operator()(const generic_operation_result& r) const\n{\n   return fc::json::to_string(r);\n}\n\nstd::string operation_result_printer::operator()(const generic_exchange_operation_result& r) const\n{\n   // TODO show pretty amounts instead of raw json\n   return fc::json::to_string(r);\n}\n\nstd::string operation_result_printer::operator()(const extendable_operation_result& r) const\n{\n   // TODO show pretty amounts instead of raw json\n   return fc::json::to_string(r);\n}\n\n}}} // graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/operation_printer.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/app/api_objects.hpp>\n#include <graphene/protocol/base.hpp>\n#include <graphene/protocol/asset.hpp>\n#include <graphene/protocol/operations.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n\n#include <string>\n#include <ostream>\n\n#include \"wallet_api_impl.hpp\"\n\nnamespace graphene { namespace wallet { namespace detail {\n\nstruct operation_result_printer\n{\npublic:\n   explicit operation_result_printer( const wallet_api_impl& w )\n      : _wallet(w) {}\n   const wallet_api_impl& _wallet;\n   typedef std::string result_type;\n\n   std::string operator()(const graphene::protocol::void_result& x) const;\n   std::string operator()(const graphene::protocol::object_id_type& oid) const;\n   std::string operator()(const graphene::protocol::asset& a) const;\n   std::string operator()(const graphene::protocol::generic_operation_result& r) const;\n   std::string operator()(const graphene::protocol::generic_exchange_operation_result& r) const;\n   std::string operator()(const graphene::protocol::extendable_operation_result& r) const;\n};\n\n// BLOCK  TRX  OP  VOP\nstruct operation_printer\n{\nprivate:\n   std::ostream& out;\n   const wallet_api_impl& wallet;\n   graphene::protocol::operation_result result;\n   graphene::chain::operation_history_object hist;\n\n   std::string format_asset(const graphene::protocol::asset& a) const;\n   void print_fee(const graphene::protocol::asset& a) const;\n\npublic:\n   operation_printer( std::ostream& out, const wallet_api_impl& wallet,\n         const graphene::chain::operation_history_object& obj )\n      : out(out),\n        wallet(wallet),\n        result(obj.result),\n        hist(obj)\n   {}\n\n   /// Return the decrypted memo if a memo exists, otherwise return an empty string\n   typedef std::string result_type;\n\n   template<typename T>\n   std::string operator()(const T& op)const\n   {\n      auto payer = wallet.get_account( op.fee_payer() );\n\n      std::string op_name = fc::get_typename<T>::name();\n      if( op_name.find_last_of(':') != std::string::npos )\n         op_name.erase(0, op_name.find_last_of(':')+1);\n      out << op_name << \" \";\n      out << payer.name;\n      print_fee( op.fee );\n      print_result();\n      return \"\";\n   }\n\n   std::string operator()(const graphene::protocol::transfer_operation& op)const;\n   std::string operator()(const graphene::protocol::override_transfer_operation& op)const;\n   std::string operator()(const graphene::protocol::transfer_from_blind_operation& op)const;\n   std::string operator()(const graphene::protocol::transfer_to_blind_operation& op)const;\n   std::string operator()(const graphene::protocol::account_create_operation& op)const;\n   std::string operator()(const graphene::protocol::account_update_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_create_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_update_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_update_bitasset_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_update_feed_producers_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_publish_feed_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_fund_fee_pool_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_claim_pool_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_issue_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_reserve_operation& op)const;\n   std::string operator()(const graphene::protocol::asset_settle_operation& op)const;\n   std::string operator()(const graphene::protocol::call_order_update_operation& op)const;\n   std::string operator()(const graphene::protocol::limit_order_create_operation& op)const;\n   std::string operator()(const graphene::protocol::limit_order_cancel_operation& op)const;\n   std::string operator()(const graphene::protocol::fill_order_operation& op)const;\n   std::string operator()(const graphene::protocol::proposal_update_operation& op)const;\n   std::string operator()(const graphene::protocol::htlc_create_operation& op)const;\n   std::string operator()(const graphene::protocol::htlc_redeem_operation& op)const;\n   std::string operator()(const graphene::protocol::htlc_redeemed_operation& op)const;\n\nprotected:\n   std::string print_memo( const fc::optional<graphene::protocol::memo_data>& memo)const;\n   void print_preimage( const std::vector<char>& preimage)const;\n   void print_redeem(const graphene::protocol::htlc_id_type& id,\n         const std::string& redeemer, const std::vector<char>& preimage,\n         const graphene::protocol::asset& op_fee)const;\n   void print_result()const;\n};\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/reflect_util.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/wallet/reflect_util.hpp>\n\nnamespace graphene { namespace wallet { namespace impl {\n\nstd::string clean_name( const std::string& name )\n{\n   const static std::string prefix = \"graphene::protocol::\";\n   const static std::string suffix = \"_operation\";\n   // graphene::protocol::.*_operation\n   if(    (name.size() >= prefix.size() + suffix.size())\n       && (name.substr( 0, prefix.size() ) == prefix)\n       && (name.substr( name.size()-suffix.size(), suffix.size() ) == suffix )\n     )\n        return name.substr( prefix.size(), name.size() - prefix.size() - suffix.size() );\n\n   wlog( \"don't know how to clean name: ${name}\", (\"name\", name) );\n   return name;\n}\n\n}}} // namespace graphene::wallet::impl\n"
  },
  {
    "path": "libraries/wallet/wallet.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <algorithm>\n#include <cctype>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n#include <sstream>\n#include <string>\n#include <list>\n\n#include <boost/version.hpp>\n#include <boost/lexical_cast.hpp>\n#include <boost/algorithm/string/replace.hpp>\n\n#include <boost/range/adaptor/map.hpp>\n#include <boost/range/algorithm_ext/erase.hpp>\n#include <boost/range/algorithm/unique.hpp>\n#include <boost/range/algorithm/sort.hpp>\n\n#include <boost/multi_index_container.hpp>\n#include <boost/multi_index/ordered_index.hpp>\n#include <boost/multi_index/mem_fun.hpp>\n#include <boost/multi_index/member.hpp>\n#include <boost/multi_index/random_access_index.hpp>\n#include <boost/multi_index/tag.hpp>\n#include <boost/multi_index/sequenced_index.hpp>\n#include <boost/multi_index/hashed_index.hpp>\n\n#include <fc/git_revision.hpp>\n#include <fc/io/fstream.hpp>\n#include <fc/io/json.hpp>\n#include <fc/io/stdio.hpp>\n#include <fc/network/http/websocket.hpp>\n#include <fc/rpc/cli.hpp>\n#include <fc/rpc/websocket_api.hpp>\n#include <fc/crypto/hex.hpp>\n#include <fc/thread/mutex.hpp>\n#include <fc/thread/scoped_lock.hpp>\n#include <fc/crypto/base58.hpp>\n#include <fc/crypto/aes.hpp>\n\n#include <graphene/app/api.hpp>\n#include <graphene/app/util.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <graphene/protocol/pts_address.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/utilities/git_revision.hpp>\n#include <graphene/utilities/key_conversion.hpp>\n#include <graphene/utilities/words.hpp>\n#include <graphene/wallet/wallet.hpp>\n#include <graphene/wallet/api_documentation.hpp>\n#include \"wallet_api_impl.hpp\"\n#include <graphene/debug_witness/debug_api.hpp>\n\n#include \"operation_printer.hpp\"\n#include <graphene/wallet/reflect_util.hpp>\n\n#define BRAIN_KEY_WORD_COUNT 16\n#define RANGE_PROOF_MANTISSA 49 // Minimum mantissa bits to \"hide\" in the range proof.\n                                // If this number is set too low, then for large value\n                                // commitments the length of the range proof will hint\n                                // strongly at the value amount that is being hidden.\n\nnamespace graphene { namespace wallet {\n   fc::sha256 signed_message::digest()const\n   {\n      fc::stringstream to_sign;\n      to_sign << message << '\\n';\n      to_sign << \"account=\" << meta.account << '\\n';\n      to_sign << \"memokey=\" << string( meta.memo_key ) << '\\n';\n      to_sign << \"block=\" << meta.block << '\\n';\n      to_sign << \"timestamp=\" << meta.time;\n\n      return fc::sha256::hash( to_sign.str() );\n   }\n   vector<brain_key_info> utility::derive_owner_keys_from_brain_key( const string& brain_key,\n                                                                     uint32_t number_of_desired_keys )\n   {\n      // Safety-check\n      FC_ASSERT( number_of_desired_keys >= 1, \"number_of_desired_keys should be at least 1\" );\n\n      // Create as many derived owner keys as requested\n      vector<brain_key_info> results;\n      for( uint32_t i = 0; i < number_of_desired_keys; ++i )\n      {\n        fc::ecc::private_key priv_key = graphene::wallet::detail::derive_private_key( brain_key, i );\n\n        brain_key_info result;\n        result.brain_priv_key = brain_key;\n        result.wif_priv_key = key_to_wif( priv_key );\n        result.pub_key = priv_key.get_public_key();\n\n        results.push_back(result);\n      }\n\n      return results;\n   }\n\n   brain_key_info utility::suggest_brain_key()\n   {\n      brain_key_info result;\n      // create a private key for secure entropy\n      fc::sha256 sha_entropy1 = fc::ecc::private_key::generate().get_secret();\n      fc::sha256 sha_entropy2 = fc::ecc::private_key::generate().get_secret();\n      fc::bigint entropy1(sha_entropy1.data(), sha_entropy1.data_size());\n      fc::bigint entropy2(sha_entropy2.data(), sha_entropy2.data_size());\n      fc::bigint entropy(entropy1);\n      entropy <<= 8 * sha_entropy1.data_size();\n      entropy += entropy2;\n      string brain_key = \"\";\n\n      for( uint32_t i = 0; i < BRAIN_KEY_WORD_COUNT; ++i )\n      {\n         fc::bigint choice = entropy % graphene::words::word_list_size;\n         entropy /= graphene::words::word_list_size;\n         if (i > 0)\n            brain_key += \" \";\n         brain_key += graphene::words::word_list[choice.to_int64()];\n      }\n\n      brain_key = detail::normalize_brain_key(brain_key);\n      fc::ecc::private_key priv_key = detail::derive_private_key(brain_key, 0);\n      result.brain_priv_key = brain_key;\n      result.wif_priv_key = key_to_wif(priv_key);\n      result.pub_key = priv_key.get_public_key();\n      return result;\n   }\n}}\n\nnamespace graphene { namespace wallet {\n\nwallet_api::wallet_api( const wallet_data& initial_data, const fc::api<login_api>& rapi )\n   : my( std::make_unique<detail::wallet_api_impl>(*this, initial_data, rapi) )\n{\n}\n\nwallet_api::~wallet_api() = default;\n\nbool wallet_api::copy_wallet_file( const string& destination_filename )const\n{\n   return my->copy_wallet_file(destination_filename);\n}\n\noptional<signed_block_with_info> wallet_api::get_block(uint32_t num)const\n{\n   return my->_remote_db->get_block(num);\n}\n\nuint64_t wallet_api::get_account_count() const\n{\n   return my->_remote_db->get_account_count();\n}\n\nvector<account_object> wallet_api::list_my_accounts()const\n{\n   return vector<account_object>(my->_wallet.my_accounts.begin(), my->_wallet.my_accounts.end());\n}\n\nmap<string, account_id_type, std::less<>> wallet_api::list_accounts( const string& lowerbound, uint32_t limit )const\n{\n   return my->_remote_db->lookup_accounts(lowerbound, limit, {});\n}\n\nvector<asset> wallet_api::list_account_balances( const string& id )const\n{\n   return my->_remote_db->get_account_balances(id, flat_set<asset_id_type>());\n}\n\nvector<extended_asset_object> wallet_api::list_assets( const string& lowerbound, uint32_t limit )const\n{\n   return my->_remote_db->list_assets( lowerbound, limit );\n}\n\nuint64_t wallet_api::get_asset_count()const\n{\n   return my->_remote_db->get_asset_count();\n}\n\nsigned_transaction wallet_api::htlc_create( const string& source, const string& destination,\n         const string& amount, const string& asset_symbol, const string& hash_algorithm,\n         const string& preimage_hash, uint32_t preimage_size,\n         uint32_t claim_period_seconds, const string& memo, bool broadcast ) const\n{\n   return my->htlc_create(source, destination, amount, asset_symbol, hash_algorithm, preimage_hash, preimage_size,\n         claim_period_seconds, memo, broadcast);\n}\n\nfc::optional<fc::variant> wallet_api::get_htlc(const htlc_id_type& htlc_id) const\n{\n   fc::optional<htlc_object> optional_obj = my->get_htlc(htlc_id);\n   if ( optional_obj.valid() )\n   {\n      const htlc_object& obj = *optional_obj;\n      // convert to formatted variant\n      fc::mutable_variant_object transfer;\n      const auto& from = my->get_account( obj.transfer.from );\n      transfer[\"from\"] = from.name;\n      const auto& to = my->get_account( obj.transfer.to );\n      transfer[\"to\"] = to.name;\n      const auto& asset = my->get_asset( obj.transfer.asset_id );\n      transfer[\"asset\"] = asset.symbol;\n      transfer[\"amount\"] = graphene::app::uint128_amount_to_string( obj.transfer.amount.value, asset.precision );\n      if (obj.memo.valid())\n         transfer[\"memo\"] = my->read_memo( *obj.memo );\n      class htlc_hash_to_variant_visitor\n      {\n         public:\n         typedef fc::mutable_variant_object result_type;\n\n         result_type operator()(const fc::ripemd160& obj)const\n         { return convert(\"RIPEMD160\", obj.str()); }\n         result_type operator()(const fc::sha1& obj)const\n         { return convert(\"SHA1\", obj.str()); }\n         result_type operator()(const fc::sha256& obj)const\n         { return convert(\"SHA256\", obj.str()); }\n         result_type operator()(const fc::hash160& obj)const\n         { return convert(\"HASH160\", obj.str()); }\n         private:\n         result_type convert(const string& type, const string& hash)const\n         {\n            fc::mutable_variant_object ret_val;\n            ret_val[\"hash_algo\"] = type;\n            ret_val[\"preimage_hash\"] = hash;\n            return ret_val;\n         }\n      };\n      static htlc_hash_to_variant_visitor hash_visitor;\n      fc::mutable_variant_object htlc_lock = obj.conditions.hash_lock.preimage_hash.visit(hash_visitor);\n      htlc_lock[\"preimage_size\"] = obj.conditions.hash_lock.preimage_size;\n      fc::mutable_variant_object time_lock;\n      time_lock[\"expiration\"] = obj.conditions.time_lock.expiration;\n      time_lock[\"time_left\"] = fc::get_approximate_relative_time_string(obj.conditions.time_lock.expiration);\n      fc::mutable_variant_object conditions;\n      conditions[\"htlc_lock\"] = htlc_lock;\n      conditions[\"time_lock\"] = time_lock;\n      fc::mutable_variant_object result;\n      result[\"transfer\"] = transfer;\n      result[\"conditions\"] = conditions;\n      return fc::optional<fc::variant>(result);\n   }\n   return fc::optional<fc::variant>();\n}\n\nsigned_transaction wallet_api::htlc_redeem( const htlc_id_type& htlc_id, const string& issuer,\n      const string& preimage, bool broadcast ) const\n{\n\n   return my->htlc_redeem(htlc_id, issuer, std::vector<char>(preimage.begin(), preimage.end()), broadcast);\n}\n\nsigned_transaction wallet_api::htlc_extend( const htlc_id_type& htlc_id, const string& issuer,\n      uint32_t seconds_to_add, bool broadcast ) const\n{\n   return my->htlc_extend(htlc_id, issuer, seconds_to_add, broadcast);\n}\n\nvector<operation_detail> wallet_api::get_account_history( const string& name, uint32_t limit )const\n{\n   vector<operation_detail> result;\n\n   while( limit > 0 )\n   {\n      bool skip_first_row = false;\n      operation_history_id_type start;\n      if( result.size() )\n      {\n         start = result.back().op.id;\n         if( start == operation_history_id_type() ) // no more data\n            break;\n         start = start + (-1);\n         if( start == operation_history_id_type() ) // will return most recent history if\n                                                    // directly call remote API with this\n         {\n            start = start + 1;\n            skip_first_row = true;\n         }\n      }\n\n      uint32_t default_page_size = 100;\n      uint32_t page_limit = skip_first_row ? std::min<uint32_t>( default_page_size, limit + 1 )\n                                           : std::min<uint32_t>( default_page_size, limit );\n\n      vector<operation_history_object> current = my->_remote_hist->get_account_history(\n            name,\n            operation_history_id_type(),\n            page_limit,\n            start );\n      bool first_row = true;\n      for( auto& o : current )\n      {\n         if( first_row )\n         {\n            first_row = false;\n            if( skip_first_row )\n            {\n               continue;\n            }\n         }\n         std::stringstream ss;\n         auto memo = o.op.visit(detail::operation_printer(ss, *my, o));\n         result.push_back( operation_detail{ memo, ss.str(), o } );\n      }\n\n      if( current.size() < page_limit )\n         break;\n\n      limit -= current.size();\n      if( skip_first_row )\n         ++limit;\n   }\n\n   return result;\n}\n\nvector<operation_detail> wallet_api::get_relative_account_history(\n      const string& name,\n      uint32_t stop,\n      uint32_t limit,\n      uint32_t start )const\n{\n   vector<operation_detail> result;\n   auto account_id = get_account(name).get_id();\n\n   const account_object& account = my->get_account(account_id);\n   const account_statistics_object& stats = my->get_object(account.statistics);\n\n   if(start == 0)\n       start = stats.total_ops;\n   else\n      start = std::min<uint32_t>(start, stats.total_ops);\n\n   uint32_t default_page_size = 100;\n   while( limit > 0 )\n   {\n      uint32_t page_size = std::min<uint32_t>(default_page_size, limit);\n      vector <operation_history_object> current = my->_remote_hist->get_relative_account_history(\n            name,\n            stop,\n            page_size,\n            start);\n      for (auto &o : current) {\n         std::stringstream ss;\n         auto memo = o.op.visit(detail::operation_printer(ss, *my, o));\n         result.push_back(operation_detail{memo, ss.str(), o});\n      }\n      if (current.size() < page_size)\n         break;\n      limit -= page_size;\n      start -= page_size;\n      if( start == 0 ) break;\n   }\n   return result;\n}\n\naccount_history_operation_detail wallet_api::get_account_history_by_operations(\n      const string& name,\n      const flat_set<uint16_t>& operation_types,\n      uint32_t start,\n      uint32_t limit )const\n{\n    account_history_operation_detail result;\n\n    const auto& account = my->get_account(name);\n    const auto& stats = my->get_object(account.statistics);\n\n    // sequence of account_history_object start with 1\n    start = start == 0 ? 1 : start;\n\n    if (start <= stats.removed_ops) {\n        start = stats.removed_ops;\n        result.total_count =stats.removed_ops;\n    }\n\n    uint32_t default_page_size = 100;\n    while (limit > 0 && start <= stats.total_ops) {\n        uint32_t min_limit = std::min(default_page_size, limit);\n        auto current = my->_remote_hist->get_account_history_by_operations(name, operation_types, start, min_limit);\n        auto his_rend = current.operation_history_objs.rend();\n        for( auto it = current.operation_history_objs.rbegin(); it != his_rend; ++it )\n        {\n            auto& obj = *it;\n            std::stringstream ss;\n            auto memo = obj.op.visit(detail::operation_printer(ss, *my, obj));\n\n            transaction_id_type transaction_id;\n            auto block = get_block(obj.block_num);\n            if (block.valid() && obj.trx_in_block < block->transaction_ids.size()) {\n                transaction_id = block->transaction_ids[obj.trx_in_block];\n            }\n            result.details.push_back(operation_detail_ex{memo, ss.str(), obj, transaction_id});\n        }\n        result.result_count += current.operation_history_objs.size();\n        result.total_count += current.total_count;\n\n        start += current.total_count > 0 ? current.total_count : min_limit;\n        limit -= current.operation_history_objs.size();\n    }\n\n    return result;\n}\n\nfull_account wallet_api::get_full_account( const string& name_or_id )const\n{\n    return my->_remote_db->get_full_accounts({name_or_id}, false)[name_or_id];\n}\n\nvector<bucket_object> wallet_api::get_market_history(\n      const string& symbol1,\n      const string& symbol2,\n      uint32_t bucket,\n      const fc::time_point_sec& start,\n      const fc::time_point_sec& end )const\n{\n   return my->_remote_hist->get_market_history( symbol1, symbol2, bucket, start, end );\n}\n\nvector<limit_order_object> wallet_api::get_account_limit_orders(\n      const string& name_or_id,\n      const string& base,\n      const string& quote,\n      uint32_t limit,\n      const optional<limit_order_id_type>& ostart_id,\n      const optional<price>& ostart_price )const\n{\n   return my->_remote_db->get_account_limit_orders(name_or_id, base, quote, limit, ostart_id, ostart_price);\n}\n\nvector<limit_order_object> wallet_api::get_limit_orders( const string& a, const string& b, uint32_t limit )const\n{\n   return my->_remote_db->get_limit_orders(a, b, limit);\n}\n\nvector<call_order_object> wallet_api::get_call_orders( const string& a, uint32_t limit )const\n{\n   return my->_remote_db->get_call_orders(a, limit);\n}\n\nvector<force_settlement_object> wallet_api::get_settle_orders( const string& a, uint32_t limit )const\n{\n   return my->_remote_db->get_settle_orders(a, limit);\n}\n\nvector<collateral_bid_object> wallet_api::get_collateral_bids( const string& a, uint32_t limit, uint32_t start )const\n{\n   return my->_remote_db->get_collateral_bids(a, limit, start);\n}\n\nbrain_key_info wallet_api::suggest_brain_key()const\n{\n   return graphene::wallet::utility::suggest_brain_key();\n}\n\nvector<brain_key_info> wallet_api::derive_owner_keys_from_brain_key(\n      const string& brain_key,\n      uint32_t number_of_desired_keys ) const\n{\n   return graphene::wallet::utility::derive_owner_keys_from_brain_key(brain_key, number_of_desired_keys);\n}\n\nbool wallet_api::is_public_key_registered( const string& public_key ) const\n{\n   bool is_known = my->_remote_db->is_public_key_registered(public_key);\n   return is_known;\n}\n\n\nstring wallet_api::serialize_transaction( const signed_transaction& tx )const\n{\n   return fc::to_hex(fc::raw::pack(tx));\n}\n\nvariant wallet_api::get_object( const object_id_type& id ) const\n{\n   return my->_remote_db->get_objects({id}, {});\n}\n\nstring wallet_api::get_wallet_filename() const\n{\n   return my->get_wallet_filename();\n}\n\ntransaction_handle_type wallet_api::begin_builder_transaction()const\n{\n   return my->begin_builder_transaction();\n}\n\nvoid wallet_api::add_operation_to_builder_transaction(\n      transaction_handle_type transaction_handle,\n      const operation& op )const\n{\n   my->add_operation_to_builder_transaction(transaction_handle, op);\n}\n\nvoid wallet_api::replace_operation_in_builder_transaction(\n      transaction_handle_type handle,\n      uint32_t operation_index,\n      const operation& new_op )const\n{\n   my->replace_operation_in_builder_transaction(handle, operation_index, new_op);\n}\n\nasset wallet_api::set_fees_on_builder_transaction( transaction_handle_type handle, const string& fee_asset )const\n{\n   return my->set_fees_on_builder_transaction(handle, fee_asset);\n}\n\ntransaction wallet_api::preview_builder_transaction(transaction_handle_type handle)const\n{\n   return my->preview_builder_transaction(handle);\n}\n\nsigned_transaction wallet_api::sign_builder_transaction(transaction_handle_type transaction_handle,\n                                                        bool broadcast)const\n{\n   return my->sign_builder_transaction(transaction_handle, broadcast);\n}\n\nsigned_transaction wallet_api::sign_builder_transaction2(transaction_handle_type transaction_handle,\n                                                        const vector<public_key_type>& explicit_keys,\n                                                        bool broadcast)const\n{\n   return my->sign_builder_transaction2(transaction_handle, explicit_keys, broadcast);\n}\n\npair<transaction_id_type,signed_transaction> wallet_api::broadcast_transaction( const signed_transaction& tx )const\n{\n    return my->broadcast_transaction(tx);\n}\n\nsigned_transaction wallet_api::propose_builder_transaction(\n      transaction_handle_type handle,\n      const time_point_sec& expiration,\n      uint32_t review_period_seconds,\n      bool broadcast )const\n{\n   return my->propose_builder_transaction(handle, expiration, review_period_seconds, broadcast);\n}\n\nsigned_transaction wallet_api::propose_builder_transaction2(\n      transaction_handle_type handle,\n      const string& account_name_or_id,\n      const time_point_sec& expiration,\n      uint32_t review_period_seconds,\n      bool broadcast )const\n{\n   return my->propose_builder_transaction2(handle, account_name_or_id, expiration, review_period_seconds, broadcast);\n}\n\nvoid wallet_api::remove_builder_transaction(transaction_handle_type handle)const\n{\n   return my->remove_builder_transaction(handle);\n}\n\naccount_object wallet_api::get_account( const string& account_name_or_id ) const\n{\n   return my->get_account(account_name_or_id);\n}\n\nextended_asset_object wallet_api::get_asset( const string& asset_name_or_id ) const\n{\n   auto found_asset = my->find_asset(asset_name_or_id);\n   FC_ASSERT( found_asset, \"Unable to find asset '${a}'\", (\"a\",asset_name_or_id) );\n   return *found_asset;\n}\n\nasset_bitasset_data_object wallet_api::get_bitasset_data( const string& asset_name_or_id ) const\n{\n   auto asset = get_asset(asset_name_or_id);\n   FC_ASSERT(asset.is_market_issued() && asset.bitasset_data_id);\n   return my->get_object(*asset.bitasset_data_id);\n}\n\naccount_id_type wallet_api::get_account_id( const string& account_name_or_id ) const\n{\n   return my->get_account_id(account_name_or_id);\n}\n\nasset_id_type wallet_api::get_asset_id( const string& asset_symbol_or_id ) const\n{\n   return my->get_asset_id(asset_symbol_or_id);\n}\n\nbool wallet_api::import_key( const string& account_name_or_id, const string& wif_key )const\n{\n   FC_ASSERT(!is_locked());\n   // backup wallet\n   fc::optional<fc::ecc::private_key> optional_private_key = wif_to_key(wif_key);\n   if (!optional_private_key)\n      FC_THROW(\"Invalid private key\");\n   string shorthash = detail::address_to_shorthash(address(optional_private_key->get_public_key()));\n   copy_wallet_file( \"before-import-key-\" + shorthash );\n\n   if( my->import_key(account_name_or_id, wif_key) )\n   {\n      save_wallet_file();\n      copy_wallet_file( \"after-import-key-\" + shorthash );\n      return true;\n   }\n   return false;\n}\n\nmap<string, bool, std::less<>> wallet_api::import_accounts( const string& filename, const string& password )const\n{\n   FC_ASSERT( !is_locked() );\n   FC_ASSERT( fc::exists( filename ) );\n\n   const auto imported_keys = fc::json::from_file<exported_keys>( filename );\n\n   const auto password_hash = fc::sha512::hash( password );\n   FC_ASSERT( fc::sha512::hash( password_hash ) == imported_keys.password_checksum );\n\n   map<string, bool, std::less<>> result;\n   for( const auto& item : imported_keys.account_keys )\n   {\n       const auto import_this_account = [ & ]() -> bool\n       {\n           try\n           {\n               const account_object account = get_account( item.account_name );\n               const auto& owner_keys = account.owner.get_keys();\n               const auto& active_keys = account.active.get_keys();\n\n               for( const auto& public_key : item.public_keys )\n               {\n                   if( std::find( owner_keys.begin(), owner_keys.end(), public_key ) != owner_keys.end() )\n                       return true;\n\n                   if( std::find( active_keys.begin(), active_keys.end(), public_key ) != active_keys.end() )\n                       return true;\n               }\n           }\n           catch( ... )\n           {\n           }\n\n           return false;\n       };\n\n       const auto should_proceed = import_this_account();\n       result[ item.account_name ] = should_proceed;\n\n       if( should_proceed )\n       {\n           uint32_t import_successes = 0;\n           uint32_t import_failures = 0;\n           // TODO: First check that all private keys match public keys\n           for( const auto& encrypted_key : item.encrypted_private_keys )\n           {\n               try\n               {\n                  const auto plain_text = fc::aes_decrypt( password_hash, encrypted_key );\n                  const auto private_key = fc::raw::unpack<private_key_type>( plain_text );\n\n                  import_key( item.account_name, string( graphene::utilities::key_to_wif( private_key ) ) );\n                  ++import_successes;\n               }\n               catch( const fc::exception& e )\n               {\n                  elog( \"Couldn't import key due to exception ${e}\", (\"e\", e.to_detail_string()) );\n                  ++import_failures;\n               }\n           }\n           ilog( \"successfully imported ${n} keys for account ${name}\",\n                 (\"n\", import_successes)(\"name\", item.account_name) );\n           if( import_failures > 0 )\n              elog( \"failed to import ${n} keys for account ${name}\",\n                    (\"n\", import_failures)(\"name\", item.account_name) );\n       }\n   }\n\n   return result;\n}\n\nbool wallet_api::import_account_keys(\n      const string& filename,\n      const string& password,\n      const string& src_account_name,\n      const string& dest_account_name )const\n{\n   FC_ASSERT( !is_locked() );\n   FC_ASSERT( fc::exists( filename ) );\n\n   bool is_my_account = false;\n   const auto accounts = list_my_accounts();\n   for( const auto& account : accounts )\n   {\n       if( account.name == dest_account_name )\n       {\n           is_my_account = true;\n           break;\n       }\n   }\n   FC_ASSERT( is_my_account );\n\n   const auto imported_keys = fc::json::from_file<exported_keys>( filename );\n\n   const auto password_hash = fc::sha512::hash( password );\n   FC_ASSERT( fc::sha512::hash( password_hash ) == imported_keys.password_checksum );\n\n   bool found_account = false;\n   for( const auto& item : imported_keys.account_keys )\n   {\n       if( item.account_name != src_account_name )\n           continue;\n\n       found_account = true;\n\n       for( const auto& encrypted_key : item.encrypted_private_keys )\n       {\n           const auto plain_text = fc::aes_decrypt( password_hash, encrypted_key );\n           const auto private_key = fc::raw::unpack<private_key_type>( plain_text );\n\n           my->import_key( dest_account_name, string( graphene::utilities::key_to_wif( private_key ) ) );\n       }\n\n       return true;\n   }\n   save_wallet_file();\n\n   FC_ASSERT( found_account );\n\n   return false;\n}\n\nstring wallet_api::normalize_brain_key( const string& s ) const\n{\n   return detail::normalize_brain_key( s );\n}\n\nvariant wallet_api::info()const\n{\n   return my->info();\n}\n\nvariant_object wallet_api::about() const\n{\n    return my->about();\n}\n\nfc::ecc::private_key wallet_api::derive_private_key( const string& prefix_string, uint32_t sequence_number ) const\n{\n   return detail::derive_private_key( prefix_string, sequence_number );\n}\n\nsigned_transaction wallet_api::register_account( const string& name,\n                                                 const public_key_type& owner_pubkey,\n                                                 const public_key_type& active_pubkey,\n                                                 const string&  registrar_account,\n                                                 const string&  referrer_account,\n                                                 uint32_t referrer_percent,\n                                                 bool broadcast )const\n{\n   return my->register_account( name, owner_pubkey, active_pubkey,\n                                registrar_account, referrer_account, referrer_percent, broadcast );\n}\nsigned_transaction wallet_api::create_account_with_brain_key( const string& brain_key, const string& account_name,\n      const string& registrar_account, const string& referrer_account,\n      bool broadcast /* = false */ )const\n{\n   return my->create_account_with_brain_key(\n            brain_key, account_name, registrar_account,\n            referrer_account, broadcast\n            );\n}\nsigned_transaction wallet_api::issue_asset( const string& to_account, const string& amount, const string& symbol,\n                                            const string& memo, bool broadcast )const\n{\n   return my->issue_asset(to_account, amount, symbol, memo, broadcast);\n}\n\nsigned_transaction wallet_api::transfer( const string& from, const string& to, const string& amount,\n                                         const string& asset_symbol, const string& memo,\n                                         bool broadcast /* = false */ )const\n{\n   return my->transfer(from, to, amount, asset_symbol, memo, broadcast);\n}\nsigned_transaction wallet_api::create_asset( const string& issuer,\n                                             const string& symbol,\n                                             uint8_t precision,\n                                             const asset_options& common,\n                                             const optional<bitasset_options>& bitasset_opts,\n                                             bool broadcast )const\n{\n   return my->create_asset(issuer, symbol, precision, common, bitasset_opts, broadcast);\n}\n\nsigned_transaction wallet_api::update_asset( const string& symbol,\n                                             const optional<string>& new_issuer,\n                                             const asset_options& new_options,\n                                             bool broadcast /* = false */ )const\n{\n   return my->update_asset(symbol, new_issuer, new_options, broadcast);\n}\n\nsigned_transaction wallet_api::update_asset_issuer( const string& symbol,\n                                                    const string& new_issuer,\n                                                    bool broadcast /* = false */ )const\n{\n   return my->update_asset_issuer(symbol, new_issuer, broadcast);\n}\n\nsigned_transaction wallet_api::update_bitasset( const string& symbol,\n                                                const bitasset_options& new_options,\n                                                bool broadcast /* = false */ )const\n{\n   return my->update_bitasset(symbol, new_options, broadcast);\n}\n\nsigned_transaction wallet_api::update_asset_feed_producers( const string& symbol,\n                                                            const flat_set<string>& new_feed_producers,\n                                                            bool broadcast /* = false */ )const\n{\n   return my->update_asset_feed_producers(symbol, new_feed_producers, broadcast);\n}\n\nsigned_transaction wallet_api::publish_asset_feed( const string& publishing_account,\n                                                   const string& symbol,\n                                                   const price_feed& feed,\n                                                   bool broadcast /* = false */ )const\n{\n   return my->publish_asset_feed(publishing_account, symbol, feed, broadcast);\n}\n\nsigned_transaction wallet_api::fund_asset_fee_pool( const string& from,\n                                                    const string& symbol,\n                                                    const string& amount,\n                                                    bool broadcast /* = false */ )const\n{\n   return my->fund_asset_fee_pool(from, symbol, amount, broadcast);\n}\n\nsigned_transaction wallet_api::claim_asset_fee_pool( const string& symbol,\n                                                     const string& amount,\n                                                     bool broadcast /* = false */ )const\n{\n   return my->claim_asset_fee_pool(symbol, amount, broadcast);\n}\n\nsigned_transaction wallet_api::reserve_asset( const string& from,\n                                              const string& amount,\n                                              const string& symbol,\n                                              bool broadcast /* = false */ )const\n{\n   return my->reserve_asset(from, amount, symbol, broadcast);\n}\n\nsigned_transaction wallet_api::global_settle_asset( const string& symbol,\n                                                    const price& settle_price,\n                                                    bool broadcast /* = false */ )const\n{\n   return my->global_settle_asset(symbol, settle_price, broadcast);\n}\n\nsigned_transaction wallet_api::settle_asset( const string& account_to_settle,\n                                             const string& amount_to_settle,\n                                             const string& symbol,\n                                             bool broadcast /* = false */ )const\n{\n   return my->settle_asset(account_to_settle, amount_to_settle, symbol, broadcast);\n}\n\nsigned_transaction wallet_api::bid_collateral( const string& bidder_name,\n                                               const string& debt_amount, const string& debt_symbol,\n                                               const string& additional_collateral,\n                                               bool broadcast )const\n{\n   return my->bid_collateral(bidder_name, debt_amount, debt_symbol, additional_collateral, broadcast);\n}\n\nsigned_transaction wallet_api::whitelist_account( const string& authorizing_account,\n                                                  const string& account_to_list,\n                                                  account_whitelist_operation::account_listing new_listing_status,\n                                                  bool broadcast /* = false */ )const\n{\n   return my->whitelist_account(authorizing_account, account_to_list, new_listing_status, broadcast);\n}\n\nsigned_transaction wallet_api::create_committee_member( const string& owner_account, const string& url,\n                                                        bool broadcast /* = false */ )const\n{\n   return my->create_committee_member(owner_account, url, broadcast);\n}\n\nmap<string, witness_id_type, std::less<>> wallet_api::list_witnesses( const string& lowerbound, uint32_t limit )const\n{\n   return my->_remote_db->lookup_witness_accounts(lowerbound, limit);\n}\n\nmap<string,committee_member_id_type, std::less<>> wallet_api::list_committee_members(\n      const string& lowerbound, uint32_t limit )const\n{\n   return my->_remote_db->lookup_committee_member_accounts(lowerbound, limit);\n}\n\nwitness_object wallet_api::get_witness( const string& owner_account )const\n{\n   return my->get_witness(owner_account);\n}\n\ncommittee_member_object wallet_api::get_committee_member( const string& owner_account )const\n{\n   return my->get_committee_member(owner_account);\n}\n\nsigned_transaction wallet_api::create_witness( const string& owner_account,\n                                               const string& url,\n                                               bool broadcast /* = false */ )const\n{\n   return my->create_witness(owner_account, url, broadcast);\n}\n\nsigned_transaction wallet_api::create_worker(\n   const string& owner_account,\n   const time_point_sec& work_begin_date,\n   const time_point_sec& work_end_date,\n   const share_type& daily_pay,\n   const string& name,\n   const string& url,\n   const variant& worker_settings,\n   bool broadcast /* = false */ )const\n{\n   return my->create_worker( owner_account, work_begin_date, work_end_date,\n      daily_pay, name, url, worker_settings, broadcast );\n}\n\nsigned_transaction wallet_api::update_worker_votes(\n   const string& owner_account,\n   const worker_vote_delta& delta,\n   bool broadcast /* = false */ )const\n{\n   return my->update_worker_votes( owner_account, delta, broadcast );\n}\n\nsigned_transaction wallet_api::update_witness(\n   const string& witness_name,\n   const string& url,\n   const string& block_signing_key,\n   bool broadcast /* = false */ )const\n{\n   return my->update_witness(witness_name, url, block_signing_key, broadcast);\n}\n\nvector< vesting_balance_object_with_info > wallet_api::get_vesting_balances( const string& account_name )const\n{\n   return my->get_vesting_balances( account_name );\n}\n\nsigned_transaction wallet_api::withdraw_vesting(\n   const string& witness_name,\n   const string& amount,\n   const string& asset_symbol,\n   bool broadcast /* = false */ )const\n{\n   return my->withdraw_vesting( witness_name, amount, asset_symbol, broadcast );\n}\n\nsigned_transaction wallet_api::vote_for_committee_member( const string& voting_account,\n                                                          const string& witness,\n                                                          bool approve,\n                                                          bool broadcast /* = false */ )const\n{\n   return my->vote_for_committee_member(voting_account, witness, approve, broadcast);\n}\n\nsigned_transaction wallet_api::vote_for_witness( const string& voting_account,\n                                                 const string& witness,\n                                                 bool approve,\n                                                 bool broadcast /* = false */ )const\n{\n   return my->vote_for_witness(voting_account, witness, approve, broadcast);\n}\n\nsigned_transaction wallet_api::set_voting_proxy( const string& account_to_modify,\n                                                 const optional<string>& voting_account,\n                                                 bool broadcast /* = false */ )const\n{\n   return my->set_voting_proxy(account_to_modify, voting_account, broadcast);\n}\n\nsigned_transaction wallet_api::set_desired_witness_and_committee_member_count( const string& account_to_modify,\n                                                                      uint16_t desired_number_of_witnesses,\n                                                                      uint16_t desired_number_of_committee_members,\n                                                                      bool broadcast /* = false */ )const\n{\n   return my->set_desired_witness_and_committee_member_count(account_to_modify, desired_number_of_witnesses,\n                                                     desired_number_of_committee_members, broadcast);\n}\n\nvoid wallet_api::set_wallet_filename( const string& wallet_filename )const\n{\n   my->_wallet_filename = wallet_filename;\n}\n\nsigned_transaction wallet_api::sign_transaction( const signed_transaction& tx, bool broadcast /* = false */ )const\n{ try {\n   return my->sign_transaction( tx, broadcast);\n} FC_CAPTURE_AND_RETHROW( (tx) ) }\n\nsigned_transaction wallet_api::sign_transaction2( const signed_transaction& tx,\n                                                  const vector<public_key_type>& signing_keys,\n                                                  bool broadcast /* = false */ )const\n{ try {\n   return my->sign_transaction2( tx, signing_keys, broadcast);\n} FC_CAPTURE_AND_RETHROW( (tx) ) }\n\nflat_set<public_key_type> wallet_api::get_transaction_signers( const signed_transaction& tx ) const\n{ try {\n   return my->get_transaction_signers(tx);\n} FC_CAPTURE_AND_RETHROW( (tx) ) }\n\nvector<flat_set<account_id_type>> wallet_api::get_key_references( const vector<public_key_type>& keys ) const\n{ try {\n   return my->get_key_references(keys);\n} FC_CAPTURE_AND_RETHROW( (keys) ) }\n\noperation wallet_api::get_prototype_operation( const string& operation_name )const\n{\n   return my->get_prototype_operation( operation_name );\n}\n\nvoid wallet_api::dbg_make_uia( const string& creator, const string& symbol )const\n{\n   FC_ASSERT(!is_locked());\n   my->dbg_make_uia(creator, symbol);\n}\n\nvoid wallet_api::dbg_make_mia( const string& creator, const string& symbol )const\n{\n   FC_ASSERT(!is_locked());\n   my->dbg_make_mia(creator, symbol);\n}\n\nvoid wallet_api::dbg_push_blocks( const string& src_filename, uint32_t count )const\n{\n   my->dbg_push_blocks( src_filename, count );\n}\n\nvoid wallet_api::dbg_generate_blocks( const string& debug_wif_key, uint32_t count )const\n{\n   my->dbg_generate_blocks( debug_wif_key, count );\n}\n\nvoid wallet_api::dbg_stream_json_objects( const string& filename )const\n{\n   my->dbg_stream_json_objects( filename );\n}\n\nvoid wallet_api::dbg_update_object( const fc::variant_object& update )const\n{\n   my->dbg_update_object( update );\n}\n\nvoid wallet_api::network_add_nodes( const vector<string>& nodes )const\n{\n   my->network_add_nodes( nodes );\n}\n\nvector< variant > wallet_api::network_get_connected_peers()const\n{\n   return my->network_get_connected_peers();\n}\n\nvoid wallet_api::flood_network( const string& prefix, uint32_t number_of_transactions )const\n{\n   FC_ASSERT(!is_locked());\n   my->flood_network(prefix, number_of_transactions);\n}\n\nsigned_transaction wallet_api::propose_parameter_change(\n   const string& proposing_account,\n   const fc::time_point_sec& expiration_time,\n   const variant_object& changed_values,\n   bool broadcast /* = false */\n   )const\n{\n   return my->propose_parameter_change( proposing_account, expiration_time, changed_values, broadcast );\n}\n\nsigned_transaction wallet_api::propose_fee_change(\n   const string& proposing_account,\n   const fc::time_point_sec& expiration_time,\n   const variant_object& changed_fees,\n   bool broadcast /* = false */\n   )const\n{\n   return my->propose_fee_change( proposing_account, expiration_time, changed_fees, broadcast );\n}\n\nsigned_transaction wallet_api::approve_proposal(\n   const string& fee_paying_account,\n   const string& proposal_id,\n   const approval_delta& delta,\n   bool broadcast /* = false */\n   )const\n{\n   return my->approve_proposal( fee_paying_account, proposal_id, delta, broadcast );\n}\n\nglobal_property_object wallet_api::get_global_properties() const\n{\n   return my->get_global_properties();\n}\n\ndynamic_global_property_object wallet_api::get_dynamic_global_properties() const\n{\n   return my->get_dynamic_global_properties();\n}\n\nsigned_transaction wallet_api::add_transaction_signature( const signed_transaction& tx,\n                                                          bool broadcast )const\n{\n   return my->add_transaction_signature( tx, broadcast );\n}\n\nstring wallet_api::help()const\n{\n   std::vector<string> method_names = my->method_documentation.get_method_names();\n   std::stringstream ss;\n   for (const string& method_name : method_names)\n   {\n      try\n      {\n         ss << my->method_documentation.get_brief_description(method_name);\n      }\n      catch (const fc::key_not_found_exception&)\n      {\n         ss << method_name << \" (no help available)\\n\";\n      }\n   }\n   return ss.str();\n}\n\nstring wallet_api::gethelp( const string& method )const\n{\n   fc::api<wallet_api> tmp;\n   std::stringstream ss;\n   ss << \"\\n\";\n\n   string doxygenHelpString = my->method_documentation.get_detailed_description(method);\n   if (!doxygenHelpString.empty())\n      ss << doxygenHelpString << \"\\n\";\n\n   if( method == \"import_key\" )\n   {\n      ss << \"usage: import_key ACCOUNT_NAME_OR_ID  WIF_PRIVATE_KEY\\n\\n\";\n      ss << \"example: import_key \\\"1.3.11\\\" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\\n\";\n      ss << \"example: import_key \\\"usera\\\" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\\n\";\n   }\n   else if( method == \"transfer\" )\n   {\n      ss << \"usage: transfer FROM TO AMOUNT SYMBOL \\\"memo\\\" BROADCAST\\n\\n\";\n      ss << \"example: transfer \\\"1.3.11\\\" \\\"1.3.4\\\" 1000.03 CORE \\\"memo\\\" true\\n\";\n      ss << \"example: transfer \\\"usera\\\" \\\"userb\\\" 1000.123 CORE \\\"memo\\\" true\\n\";\n   }\n   else if( method == \"create_account_with_brain_key\" )\n   {\n      ss << \"usage: create_account_with_brain_key BRAIN_KEY ACCOUNT_NAME REGISTRAR REFERRER BROADCAST\\n\\n\";\n      ss << \"example: create_account_with_brain_key \"\n         << \"\\\"my really long brain key\\\" \\\"newaccount\\\" \\\"1.3.11\\\" \\\"1.3.11\\\" true\\n\";\n      ss << \"example: create_account_with_brain_key \"\n         << \"\\\"my really long brain key\\\" \\\"newaccount\\\" \\\"someaccount\\\" \\\"otheraccount\\\" true\\n\";\n      ss << \"\\n\";\n      ss << \"This method should be used if you would like the wallet to generate new keys derived from the \"\n         << \"brain key.\\n\";\n      ss << \"The BRAIN_KEY will be used as the owner key, and the active key will be derived from the BRAIN_KEY.  \"\n         << \"Use\\n\";\n      ss << \"register_account if you already know the keys you know the public keys that you would like to \"\n         << \"register.\\n\";\n\n   }\n   else if( method == \"register_account\" )\n   {\n      ss << \"usage: register_account ACCOUNT_NAME OWNER_PUBLIC_KEY ACTIVE_PUBLIC_KEY REGISTRAR \"\n         << \"REFERRER REFERRER_PERCENT BROADCAST\\n\\n\";\n      ss << \"example: register_account \\\"newaccount\\\" \\\"CORE6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\\\" \"\n         << \"\\\"CORE6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\\\" \\\"1.3.11\\\" \\\"1.3.11\\\" 50 true\\n\";\n      ss << \"\\n\";\n      ss << \"Use this method to register an account for which you do not know the private keys.\";\n   }\n   else if( method == \"create_asset\" )\n   {\n      ss << \"usage: ISSUER SYMBOL PRECISION_DIGITS OPTIONS BITASSET_OPTIONS BROADCAST\\n\\n\";\n      ss << \"PRECISION_DIGITS: the number of digits after the decimal point\\n\\n\";\n      ss << \"Example value of OPTIONS: \\n\";\n      ss << fc::json::to_pretty_string( graphene::chain::asset_options() );\n      ss << \"\\nExample value of BITASSET_OPTIONS: \\n\";\n      ss << fc::json::to_pretty_string( graphene::chain::bitasset_options() );\n      ss << \"\\nBITASSET_OPTIONS may be null\\n\";\n   }\n   else if (doxygenHelpString.empty())\n      ss << \"No help defined for method \" << method << \"\\n\";\n\n   return ss.str();\n}\n\nbool wallet_api::load_wallet_file( const string& wallet_filename )const\n{\n   return my->load_wallet_file( wallet_filename );\n}\n\nvoid wallet_api::quit()const\n{\n   my->quit();\n}\n\nvoid wallet_api::save_wallet_file( const string& wallet_filename )const\n{\n   my->save_wallet_file( wallet_filename );\n}\n\nstd::map< string, std::function< string( const fc::variant&, const fc::variants& ) >, std::less<> >\n      wallet_api::get_result_formatters() const\n{\n   return my->get_result_formatters();\n}\n\nbool wallet_api::is_locked()const\n{\n   return my->is_locked();\n}\nbool wallet_api::is_new()const\n{\n   return my->_wallet.cipher_keys.size() == 0;\n}\n\nvoid wallet_api::encrypt_keys()const\n{\n   my->encrypt_keys();\n}\n\nvoid wallet_api::lock()const\n{ try {\n   FC_ASSERT( !is_locked() );\n   encrypt_keys();\n   for( auto key : my->_keys )\n      key.second = key_to_wif(fc::ecc::private_key());\n   my->_keys.clear();\n   my->_checksum = fc::sha512();\n   my->self.lock_changed(true);\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid wallet_api::unlock( const string& password )const\n{ try {\n   FC_ASSERT(password.size() > 0);\n   auto pw = fc::sha512::hash(password.c_str(), password.size());\n   vector<char> decrypted = fc::aes_decrypt(pw, my->_wallet.cipher_keys);\n   auto pk = fc::raw::unpack<plain_keys>(decrypted);\n   FC_ASSERT(pk.checksum == pw);\n   my->_keys = std::move(pk.keys);\n   my->_checksum = pk.checksum;\n   my->self.lock_changed(false);\n} FC_CAPTURE_AND_RETHROW() }\n\nvoid wallet_api::set_password( const string& password )const\n{\n   if( !is_new() )\n      FC_ASSERT( !is_locked(), \"The wallet must be unlocked before the password can be set\" );\n   my->_checksum = fc::sha512::hash( password.c_str(), password.size() );\n   lock();\n}\n\nvector< signed_transaction > wallet_api::import_balance(\n      const string& name_or_id,\n      const vector<string>& wif_keys,\n      bool broadcast )const\n{\n   return my->import_balance( name_or_id, wif_keys, broadcast );\n}\n\nmap<public_key_type, string> wallet_api::dump_private_keys()const\n{\n   FC_ASSERT(!is_locked());\n   return my->_keys;\n}\n\nsigned_transaction wallet_api::upgrade_account( const string& name, bool broadcast )const\n{\n   return my->upgrade_account(name,broadcast);\n}\n\nsigned_transaction wallet_api::sell_asset( const string& seller_account,\n                                           const string& amount_to_sell,\n                                           const string& symbol_to_sell,\n                                           const string& min_to_receive,\n                                           const string& symbol_to_receive,\n                                           uint32_t expiration,\n                                           bool   fill_or_kill,\n                                           bool   broadcast )const\n{\n   return my->sell_asset(seller_account, amount_to_sell, symbol_to_sell, min_to_receive,\n                         symbol_to_receive, expiration, fill_or_kill, broadcast);\n}\n\nsigned_transaction wallet_api::borrow_asset( const string& seller_name, const string& amount_to_sell,\n                                             const string& asset_symbol, const string& amount_of_collateral,\n                                             bool broadcast )const\n{\n   FC_ASSERT(!is_locked());\n   return my->borrow_asset_ext( seller_name, amount_to_sell, asset_symbol, amount_of_collateral, {}, broadcast );\n}\n\nsigned_transaction wallet_api::borrow_asset_ext( const string& seller_name, const string& amount_to_sell,\n                                                 const string& asset_symbol, const string& amount_of_collateral,\n                                                 const call_order_update_operation::extensions_type& extensions,\n                                                 bool broadcast )const\n{\n   FC_ASSERT(!is_locked());\n   return my->borrow_asset_ext(seller_name, amount_to_sell, asset_symbol,\n                               amount_of_collateral, extensions, broadcast);\n}\n\nsigned_transaction wallet_api::cancel_order( const limit_order_id_type& order_id, bool broadcast ) const\n{\n   FC_ASSERT(!is_locked());\n   return my->cancel_order(order_id, broadcast);\n}\n\nmemo_data wallet_api::sign_memo( const string& from, const string& to, const string& memo )const\n{\n   FC_ASSERT(!is_locked());\n   return my->sign_memo(from, to, memo);\n}\n\nstring wallet_api::read_memo( const memo_data& memo )const\n{\n   FC_ASSERT(!is_locked());\n   return my->read_memo(memo);\n}\n\nsigned_message wallet_api::sign_message( const string& signer, const string& message )const\n{\n   FC_ASSERT(!is_locked());\n   return my->sign_message(signer, message);\n}\n\nbool wallet_api::verify_message( const string& message, const string& account, int32_t block, const string& msg_time,\n                                 const fc::ecc::compact_signature& sig )const\n{\n   return my->verify_message( message, account, block, msg_time, sig );\n}\n\n/** Verify a message signed with sign_message\n *\n * @param message the signed_message structure containing message, meta data and signature\n * @return true if signature matches\n */\nbool wallet_api::verify_signed_message( const signed_message& message )const\n{\n   return my->verify_signed_message( message );\n}\n\n/** Verify a message signed with sign_message, in its encapsulated form.\n *\n * @param message the complete encapsulated message string including separators and line feeds\n * @return true if signature matches\n */\nbool wallet_api::verify_encapsulated_message( const string& message )const\n{\n   return my->verify_encapsulated_message( message );\n}\n\n\nstring wallet_api::get_key_label( const public_key_type& key )const\n{\n   auto key_itr   = my->_wallet.labeled_keys.get<by_key>().find(key);\n   if( key_itr != my->_wallet.labeled_keys.get<by_key>().end() )\n      return key_itr->label;\n   return string();\n}\n\nstring wallet_api::get_private_key( const public_key_type& pubkey )const\n{\n   return key_to_wif( my->get_private_key( pubkey ) );\n}\n\npublic_key_type  wallet_api::get_public_key( const string& label )const\n{\n   try { return fc::variant(label, 1).as<public_key_type>( 1 ); } catch ( ... ){}\n\n   auto key_itr   = my->_wallet.labeled_keys.get<by_label>().find(label);\n   if( key_itr != my->_wallet.labeled_keys.get<by_label>().end() )\n      return key_itr->key;\n   return public_key_type();\n}\n\nbool               wallet_api::set_key_label( const public_key_type& key, const string& label )const\n{\n   auto result = my->_wallet.labeled_keys.insert( key_label{label,key} );\n   if( result.second  ) return true;\n\n   auto key_itr   = my->_wallet.labeled_keys.get<by_key>().find(key);\n   auto label_itr = my->_wallet.labeled_keys.get<by_label>().find(label);\n   if( label_itr == my->_wallet.labeled_keys.get<by_label>().end() )\n   {\n      if( key_itr != my->_wallet.labeled_keys.get<by_key>().end() )\n         return my->_wallet.labeled_keys.get<by_key>().modify( key_itr, [&]( key_label& obj ){ obj.label = label; } );\n   }\n   return false;\n}\nmap<string, public_key_type, std::less<>> wallet_api::get_blind_accounts()const\n{\n   map<string, public_key_type, std::less<>> result;\n   for( const auto& item : my->_wallet.labeled_keys )\n      result[item.label] = item.key;\n   return result;\n}\nmap<string, public_key_type, std::less<>> wallet_api::get_my_blind_accounts()const\n{\n   FC_ASSERT( !is_locked() );\n   map<string, public_key_type, std::less<>> result;\n   for( const auto& item : my->_wallet.labeled_keys )\n   {\n      if( my->_keys.find(item.key) != my->_keys.end() )\n         result[item.label] = item.key;\n   }\n   return result;\n}\n\npublic_key_type    wallet_api::create_blind_account( const string& label, const string& p_brain_key  )const\n{\n   FC_ASSERT( !is_locked() );\n\n   auto label_itr = my->_wallet.labeled_keys.get<by_label>().find(label);\n   if( label_itr !=  my->_wallet.labeled_keys.get<by_label>().end() )\n      FC_ASSERT( !\"Key with label already exists\" );\n   auto brain_key = fc::trim_and_normalize_spaces( p_brain_key );\n   auto secret = fc::sha256::hash( brain_key.c_str(), brain_key.size() );\n   auto priv_key = fc::ecc::private_key::regenerate( secret );\n   public_key_type pub_key  = priv_key.get_public_key();\n\n   FC_ASSERT( set_key_label( pub_key, label ) );\n\n   my->_keys[pub_key] = graphene::utilities::key_to_wif( priv_key );\n\n   save_wallet_file();\n   return pub_key;\n}\n\nvector<asset>   wallet_api::get_blind_balances( const string& key_or_label )const\n{\n   vector<asset> result;\n   map<asset_id_type, share_type> balances;\n\n   vector<commitment_type> used;\n\n   auto pub_key = get_public_key( key_or_label );\n   auto& to_asset_used_idx = my->_wallet.blind_receipts.get<by_to_asset_used>();\n   auto start = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,asset_id_type(0),false)  );\n   auto end = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,asset_id_type(uint32_t(0xffffffff)),true)  );\n   while( start != end )\n   {\n      if( !start->used  )\n      {\n         auto answer = my->_remote_db->get_blinded_balances( {start->commitment()} );\n         if( answer.size() )\n            balances[start->amount.asset_id] += start->amount.amount;\n         else\n            used.push_back( start->commitment() );\n      }\n      ++start;\n   }\n   for( const auto& u : used )\n   {\n      auto itr = my->_wallet.blind_receipts.get<by_commitment>().find( u );\n      my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } );\n   }\n   for( auto item : balances )\n      result.push_back( asset( item.second, item.first ) );\n   return result;\n}\n\nblind_confirmation wallet_api::transfer_from_blind( const string& from_blind_account_key_or_label,\n                                                    const string& to_account_id_or_name,\n                                                    const string& amount_in,\n                                                    const string& symbol,\n                                                    bool broadcast )const\n{ try {\n   transfer_from_blind_operation from_blind;\n\n\n   auto fees  = my->_remote_db->get_global_properties().parameters.get_current_fees();\n   fc::optional<asset_object> asset_obj = get_asset(symbol);\n   FC_ASSERT(asset_obj.valid(), \"Could not find asset matching ${asset}\", (\"asset\", symbol));\n   auto amount = asset_obj->amount_from_string(amount_in);\n\n   from_blind.fee  = fees.calculate_fee( from_blind, asset_obj->options.core_exchange_rate );\n\n   auto blind_in = asset_obj->amount_to_string( from_blind.fee + amount );\n\n\n   auto conf = blind_transfer_help( from_blind_account_key_or_label,\n                               from_blind_account_key_or_label,\n                               blind_in, symbol, false, true/*to_temp*/ );\n   FC_ASSERT( conf.outputs.size() > 0 );\n\n   auto to_account = my->get_account( to_account_id_or_name );\n   from_blind.to = to_account.id;\n   from_blind.amount = amount;\n   from_blind.blinding_factor = conf.outputs.back().decrypted_memo.blinding_factor;\n   from_blind.inputs.push_back( {conf.outputs.back().decrypted_memo.commitment, authority() } );\n   from_blind.fee  = fees.calculate_fee( from_blind, asset_obj->options.core_exchange_rate );\n\n   idump( (from_blind) );\n   conf.trx.operations.push_back(from_blind);\n   ilog( \"about to validate\" );\n   conf.trx.validate();\n\n   ilog( \"about to broadcast\" );\n   conf.trx = sign_transaction( conf.trx, broadcast );\n\n   if( broadcast && conf.outputs.size() == 2 ) {\n\n       // Save the change\n       blind_confirmation::output conf_output;\n       blind_confirmation::output change_output = conf.outputs[0];\n\n       // The wallet must have a private key for confirmation.to, this is used to decrypt the memo\n       public_key_type from_key = get_public_key(from_blind_account_key_or_label);\n       conf_output.confirmation.to = from_key;\n       conf_output.confirmation.one_time_key = change_output.confirmation.one_time_key;\n       conf_output.confirmation.encrypted_memo = change_output.confirmation.encrypted_memo;\n       conf_output.confirmation_receipt = conf_output.confirmation;\n       //try {\n       receive_blind_transfer( conf_output.confirmation_receipt,\n                               from_blind_account_key_or_label,\n                               \"@\"+to_account.name );\n       //} catch ( ... ){}\n   }\n\n   return conf;\n} FC_CAPTURE_AND_RETHROW( (from_blind_account_key_or_label)(to_account_id_or_name)(amount_in)(symbol) ) }\n\nblind_confirmation wallet_api::blind_transfer( const string& from_key_or_label,\n                                               const string& to_key_or_label,\n                                               const string& amount_in,\n                                               const string& symbol,\n                                               bool broadcast )const\n{\n   return blind_transfer_help( from_key_or_label, to_key_or_label, amount_in, symbol, broadcast, false );\n}\nblind_confirmation wallet_api::blind_transfer_help( const string& from_key_or_label,\n                                                    const string& to_key_or_label,\n                                                    const string& amount_in,\n                                                    const string& symbol,\n                                                    bool broadcast,\n                                                    bool to_temp )const\n{\n   blind_confirmation confirm;\n   try {\n\n   FC_ASSERT( !is_locked() );\n   public_key_type from_key = get_public_key(from_key_or_label);\n   public_key_type to_key   = get_public_key(to_key_or_label);\n\n   fc::optional<asset_object> asset_obj = get_asset(symbol);\n   FC_ASSERT(asset_obj.valid(), \"Could not find asset matching ${asset}\", (\"asset\", symbol));\n\n   blind_transfer_operation blind_tr;\n   blind_tr.outputs.resize(2);\n\n   auto fees  = my->_remote_db->get_global_properties().parameters.get_current_fees();\n\n   auto amount = asset_obj->amount_from_string(amount_in);\n\n   asset total_amount = asset_obj->amount(0);\n\n   vector<fc::sha256> blinding_factors;\n\n   //auto from_priv_key = my->get_private_key( from_key );\n\n   blind_tr.fee  = fees.calculate_fee( blind_tr, asset_obj->options.core_exchange_rate );\n\n   vector<commitment_type> used;\n\n   auto& to_asset_used_idx = my->_wallet.blind_receipts.get<by_to_asset_used>();\n   auto start = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,false)  );\n   auto end = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,true)  );\n   while( start != end )\n   {\n      auto result = my->_remote_db->get_blinded_balances( {start->commitment() } );\n      if( result.size() == 0 )\n      {\n         used.push_back( start->commitment() );\n      }\n      else\n      {\n         blind_tr.inputs.push_back({start->commitment(), start->control_authority});\n         blinding_factors.push_back( start->data.blinding_factor );\n         total_amount += start->amount;\n\n         if( total_amount >= amount + blind_tr.fee )\n            break;\n      }\n      ++start;\n   }\n   for( const auto& u : used )\n   {\n      auto itr = my->_wallet.blind_receipts.get<by_commitment>().find( u );\n      my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } );\n   }\n\n   FC_ASSERT( total_amount >= amount+blind_tr.fee,\n              \"Insufficient Balance\",\n              (\"available\",total_amount)(\"amount\",amount)(\"fee\",blind_tr.fee) );\n\n   auto one_time_key = fc::ecc::private_key::generate();\n   auto secret       = one_time_key.get_shared_secret( to_key );\n   auto child        = fc::sha256::hash( secret );\n   auto nonce        = fc::sha256::hash( one_time_key.get_secret() );\n   auto blind_factor = fc::sha256::hash( child );\n\n   auto from_secret  = one_time_key.get_shared_secret( from_key );\n   auto from_child   = fc::sha256::hash( from_secret );\n   auto from_nonce   = fc::sha256::hash( nonce );\n\n   auto change = total_amount - amount - blind_tr.fee;\n   fc::sha256 change_blind_factor;\n   fc::sha256 to_blind_factor;\n   if( change.amount > 0 )\n   {\n      idump((\"to_blind_factor\")(blind_factor) );\n      blinding_factors.push_back( blind_factor );\n      change_blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() - 1 );\n      wdump((\"change_blind_factor\")(change_blind_factor) );\n   }\n   else // change == 0\n   {\n      blind_tr.outputs.resize(1);\n      blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() );\n      idump((\"to_sum_blind_factor\")(blind_factor) );\n      blinding_factors.push_back( blind_factor );\n      idump((\"nochange to_blind_factor\")(blind_factor) );\n   }\n   fc::ecc::public_key from_pub_key = from_key;\n   fc::ecc::public_key to_pub_key = to_key;\n\n   blind_output to_out;\n   to_out.owner       = to_temp ? authority() : authority( 1, public_key_type( to_pub_key.child( child ) ), 1 );\n   to_out.commitment  = fc::ecc::blind( blind_factor, amount.amount.value );\n   idump((\"to_out.blind\")(blind_factor)(to_out.commitment) );\n\n\n   if( blind_tr.outputs.size() > 1 )\n   {\n      to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce,\n                                                      0, RANGE_PROOF_MANTISSA, amount.amount.value );\n\n      blind_output change_out;\n      change_out.owner       = authority( 1, public_key_type( from_pub_key.child( from_child ) ), 1 );\n      change_out.commitment  = fc::ecc::blind( change_blind_factor, change.amount.value );\n      change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce,\n                                                          0, RANGE_PROOF_MANTISSA, change.amount.value );\n      blind_tr.outputs[1] = change_out;\n\n\n      blind_confirmation::output conf_output;\n      conf_output.label = from_key_or_label;\n      conf_output.pub_key = from_key;\n      conf_output.decrypted_memo.from = from_key;\n      conf_output.decrypted_memo.amount = change;\n      conf_output.decrypted_memo.blinding_factor = change_blind_factor;\n      conf_output.decrypted_memo.commitment = change_out.commitment;\n      conf_output.decrypted_memo.check   = from_secret._hash[0].value();\n      conf_output.confirmation.one_time_key = one_time_key.get_public_key();\n      conf_output.confirmation.to = from_key;\n      conf_output.confirmation.encrypted_memo =\n            fc::aes_encrypt( from_secret, fc::raw::pack( conf_output.decrypted_memo ) );\n      conf_output.auth = change_out.owner;\n      conf_output.confirmation_receipt = conf_output.confirmation;\n\n      confirm.outputs.push_back( conf_output );\n   }\n   blind_tr.outputs[0] = to_out;\n\n   blind_confirmation::output conf_output;\n   conf_output.label = to_key_or_label;\n   conf_output.pub_key = to_key;\n   conf_output.decrypted_memo.from = from_key;\n   conf_output.decrypted_memo.amount = amount;\n   conf_output.decrypted_memo.blinding_factor = blind_factor;\n   conf_output.decrypted_memo.commitment = to_out.commitment;\n   conf_output.decrypted_memo.check   = secret._hash[0].value();\n   conf_output.confirmation.one_time_key = one_time_key.get_public_key();\n   conf_output.confirmation.to = to_key;\n   conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) );\n   conf_output.auth = to_out.owner;\n   conf_output.confirmation_receipt = conf_output.confirmation;\n\n   confirm.outputs.push_back( conf_output );\n\n   /** commitments must be in sorted order */\n   std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(),\n              [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } );\n   std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(),\n              [&]( const blind_input& a, const blind_input& b ){ return a.commitment < b.commitment; } );\n\n   confirm.trx.operations.emplace_back( std::move(blind_tr) );\n   ilog( \"validate before\" );\n   confirm.trx.validate();\n   confirm.trx = sign_transaction(confirm.trx, broadcast);\n\n   if( broadcast )\n   {\n      for( const auto& out : confirm.outputs )\n      {\n         try { receive_blind_transfer( out.confirmation_receipt, from_key_or_label, \"\" ); } catch ( ... ){}\n      }\n   }\n\n   return confirm;\n} FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast)(confirm) ) }\n\n\n\n/*\n *  Transfers a public balance from @from to one or more blinded balances using a\n *  stealth transfer.\n */\nblind_confirmation wallet_api::transfer_to_blind( const string& from_account_id_or_name,\n                                                  const string& asset_symbol,\n                                                  /* map from key or label to amount */\n                                                  const vector<pair<string, string>>& to_amounts,\n                                                  bool broadcast )const\n{ try {\n   FC_ASSERT( !is_locked() );\n   idump((to_amounts));\n\n   blind_confirmation confirm;\n   account_object from_account = my->get_account(from_account_id_or_name);\n\n   fc::optional<asset_object> asset_obj = get_asset(asset_symbol);\n   FC_ASSERT(asset_obj, \"Could not find asset matching ${asset}\", (\"asset\", asset_symbol));\n\n   transfer_to_blind_operation bop;\n   bop.from   = from_account.id;\n\n   vector<fc::sha256> blinding_factors;\n\n   asset total_amount = asset_obj->amount(0);\n\n   for( auto item : to_amounts )\n   {\n      auto one_time_key = fc::ecc::private_key::generate();\n      auto to_key       = get_public_key( item.first );\n      auto secret       = one_time_key.get_shared_secret( to_key );\n      auto child        = fc::sha256::hash( secret );\n      auto nonce        = fc::sha256::hash( one_time_key.get_secret() );\n      auto blind_factor = fc::sha256::hash( child );\n\n      blinding_factors.push_back( blind_factor );\n\n      auto amount = asset_obj->amount_from_string(item.second);\n      total_amount += amount;\n\n\n      fc::ecc::public_key to_pub_key = to_key;\n      blind_output out;\n      out.owner       = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 );\n      out.commitment  = fc::ecc::blind( blind_factor, amount.amount.value );\n      if( to_amounts.size() > 1 )\n         out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce,\n                                                      0, RANGE_PROOF_MANTISSA, amount.amount.value );\n\n      blind_confirmation::output conf_output;\n      conf_output.label = item.first;\n      conf_output.pub_key = to_key;\n      conf_output.decrypted_memo.amount = amount;\n      conf_output.decrypted_memo.blinding_factor = blind_factor;\n      conf_output.decrypted_memo.commitment = out.commitment;\n      conf_output.decrypted_memo.check   = secret._hash[0].value();\n      conf_output.confirmation.one_time_key = one_time_key.get_public_key();\n      conf_output.confirmation.to = to_key;\n      conf_output.confirmation.encrypted_memo =\n            fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) );\n      conf_output.confirmation_receipt = conf_output.confirmation;\n\n      confirm.outputs.push_back( conf_output );\n\n      bop.outputs.push_back(out);\n   }\n   bop.amount          = total_amount;\n   bop.blinding_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() );\n\n   /** commitments must be in sorted order */\n   std::sort( bop.outputs.begin(), bop.outputs.end(),\n              [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } );\n\n   confirm.trx.operations.push_back( bop );\n   my->set_operation_fees( confirm.trx, my->_remote_db->get_global_properties().parameters.get_current_fees());\n   confirm.trx.validate();\n   confirm.trx = sign_transaction(confirm.trx, broadcast);\n\n   if( broadcast )\n   {\n      for( const auto& out : confirm.outputs )\n      {\n         try {\n            receive_blind_transfer( out.confirmation_receipt, \"@\"+from_account.name, \"from @\"+from_account.name );\n         } catch ( ... ){}\n      }\n   }\n\n   return confirm;\n} FC_CAPTURE_AND_RETHROW( (from_account_id_or_name)(asset_symbol)(to_amounts) ) }\n\nblind_receipt wallet_api::receive_blind_transfer( const string& confirmation_receipt,\n                                                  const string& opt_from, const string& opt_memo )const\n{\n   FC_ASSERT( !is_locked() );\n   stealth_confirmation conf(confirmation_receipt);\n   FC_ASSERT( conf.to );\n\n   blind_receipt result;\n   result.conf = conf;\n\n   auto to_priv_key_itr = my->_keys.find( *conf.to );\n   FC_ASSERT( to_priv_key_itr != my->_keys.end(), \"No private key for receiver\", (\"conf\",conf) );\n\n\n   auto to_priv_key = wif_to_key( to_priv_key_itr->second );\n   FC_ASSERT( to_priv_key );\n\n   auto secret       = to_priv_key->get_shared_secret( conf.one_time_key );\n   auto child        = fc::sha256::hash( secret );\n\n   auto child_priv_key = to_priv_key->child( child );\n   //auto blind_factor = fc::sha256::hash( child );\n\n   auto plain_memo = fc::aes_decrypt( secret, conf.encrypted_memo );\n   auto memo = fc::raw::unpack<stealth_confirmation::memo_data>( plain_memo );\n\n   result.to_key   = *conf.to;\n   result.to_label = get_key_label( result.to_key );\n   if( memo.from )\n   {\n      result.from_key = *memo.from;\n      result.from_label = get_key_label( result.from_key );\n      if( result.from_label == string() )\n      {\n         result.from_label = opt_from;\n         set_key_label( result.from_key, result.from_label );\n      }\n   }\n   else\n   {\n      result.from_label = opt_from;\n   }\n   result.amount = memo.amount;\n   result.memo = opt_memo;\n\n   // confirm the amount matches the commitment (verify the blinding factor)\n   auto commitment_test = fc::ecc::blind( memo.blinding_factor, memo.amount.amount.value );\n   FC_ASSERT( fc::ecc::verify_sum( {commitment_test}, {memo.commitment}, 0 ) );\n\n   blind_balance bal;\n   bal.amount = memo.amount;\n   bal.to     = *conf.to;\n   if( memo.from ) bal.from   = *memo.from;\n   bal.one_time_key = conf.one_time_key;\n   bal.blinding_factor = memo.blinding_factor;\n   bal.commitment = memo.commitment;\n   bal.used = false;\n\n   auto child_pubkey = child_priv_key.get_public_key();\n   auto owner = authority(1, public_key_type(child_pubkey), 1);\n   result.control_authority = owner;\n   result.data = memo;\n\n   auto child_key_itr = owner.key_auths.find( child_pubkey );\n   if( child_key_itr != owner.key_auths.end() )\n      my->_keys[child_key_itr->first] = key_to_wif( child_priv_key );\n\n   // my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal );\n\n   result.date = fc::time_point::now();\n   my->_wallet.blind_receipts.insert( result );\n   my->_keys[child_pubkey] = key_to_wif( child_priv_key );\n\n   save_wallet_file();\n\n   return result;\n}\n\nvector<blind_receipt> wallet_api::blind_history( const string& key_or_account )const\n{\n   vector<blind_receipt> result;\n   auto pub_key = get_public_key( key_or_account );\n\n   if( pub_key == public_key_type() )\n      return vector<blind_receipt>();\n\n   for( auto& r : my->_wallet.blind_receipts )\n   {\n      if( r.from_key == pub_key || r.to_key == pub_key )\n         result.push_back( r );\n   }\n   std::sort( result.begin(), result.end(),\n              [&]( const blind_receipt& a, const blind_receipt& b ){ return a.date > b.date; } );\n   return result;\n}\n\norder_book wallet_api::get_order_book( const string& base, const string& quote, uint32_t limit )const\n{\n   return( my->_remote_db->get_order_book( base, quote, limit ) );\n}\n\n// custom operations\nsigned_transaction wallet_api::account_store_map( const string& account, const string& catalog, bool is_to_remove,\n      const flat_map<string, optional<string>>& key_values, bool broadcast )const\n{\n   return my->account_store_map(account, catalog, is_to_remove, key_values, broadcast);\n}\n\nvector<account_storage_object> wallet_api::get_account_storage( const string& account, const string& catalog )const\n{ try {\n   return my->_custom_operations->get_storage_info(account, catalog);\n} FC_CAPTURE_AND_RETHROW( (account)(catalog) ) }\n\nsigned_block_with_info::signed_block_with_info( const signed_block& block )\n   : signed_block( block ),\n     block_id { id() },\n     signing_key { signee() }\n{\n   transaction_ids.reserve( transactions.size() );\n   for( const processed_transaction& tx : transactions )\n      transaction_ids.push_back( tx.id() );\n}\n\nvesting_balance_object_with_info::vesting_balance_object_with_info(\n      const vesting_balance_object& vbo,\n      const fc::time_point_sec& now )\n   : vesting_balance_object( vbo ),\n     allowed_withdraw { get_allowed_withdraw( now ) },\n     allowed_withdraw_time { now }\n{\n}\n\n} } // graphene::wallet\n\nnamespace fc {\n   void to_variant( const account_multi_index_type& accts, variant& vo, uint32_t max_depth )\n   {\n      to_variant( std::vector<account_object>(accts.begin(), accts.end()), vo, max_depth );\n   }\n\n   void from_variant( const variant& var, account_multi_index_type& vo, uint32_t max_depth )\n   {\n      const std::vector<account_object>& v = var.as<std::vector<account_object>>( max_depth );\n      vo = account_multi_index_type(v.begin(), v.end());\n   }\n}\n"
  },
  {
    "path": "libraries/wallet/wallet_account.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <boost/range/algorithm_ext/erase.hpp>\n#include <boost/range/algorithm/unique.hpp>\n#include <boost/range/algorithm/sort.hpp>\n\n#include \"wallet_api_impl.hpp\"\n#include <graphene/wallet/wallet.hpp>\n#include <graphene/protocol/pts_address.hpp>\n\n/****\n * Wallet API methods to handle accounts\n */\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   signed_transaction wallet_api_impl::register_account(string name, public_key_type owner,\n         public_key_type active, string  registrar_account, string  referrer_account,\n         uint32_t referrer_percent, bool broadcast )\n   { try {\n      FC_ASSERT( !self.is_locked() );\n      FC_ASSERT( is_valid_name(name) );\n      account_create_operation account_create_op;\n\n      // #449 referrer_percent is on 0-100 scale, if user has larger\n      // number it means their script is using GRAPHENE_100_PERCENT scale\n      // instead of 0-100 scale.\n      FC_ASSERT( referrer_percent <= 100 );\n      // TODO:  process when pay_from_account is ID\n\n      account_object registrar_account_object =\n            this->get_account( registrar_account );\n      FC_ASSERT( registrar_account_object.is_lifetime_member() );\n\n      account_id_type registrar_account_id = registrar_account_object.get_id();\n\n      account_object referrer_account_object =\n            this->get_account( referrer_account );\n      account_create_op.referrer = referrer_account_object.id;\n      account_create_op.referrer_percent = uint16_t( referrer_percent * GRAPHENE_1_PERCENT );\n\n      account_create_op.registrar = registrar_account_id;\n      account_create_op.name = name;\n      account_create_op.owner = authority(1, owner, 1);\n      account_create_op.active = authority(1, active, 1);\n      account_create_op.options.memo_key = active;\n\n      signed_transaction tx;\n      tx.operations.push_back( account_create_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n      tx.validate();\n\n      return sign_transaction(tx, broadcast);\n   } FC_CAPTURE_AND_RETHROW( (name)(owner)(active)(registrar_account)\n                              (referrer_account)(referrer_percent)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::upgrade_account(string name, bool broadcast)\n   { try {\n      FC_ASSERT( !self.is_locked() );\n      account_object account_obj = get_account(name);\n      FC_ASSERT( !account_obj.is_lifetime_member() );\n\n      signed_transaction tx;\n      account_upgrade_operation op;\n      op.account_to_upgrade = account_obj.get_id();\n      op.upgrade_to_lifetime_member = true;\n      tx.operations = {op};\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (name) ) }\n\n   signed_transaction wallet_api_impl::create_account_with_brain_key(string brain_key,\n                                                      string account_name,\n                                                      string registrar_account,\n                                                      string referrer_account,\n                                                      bool broadcast,\n                                                      bool save_wallet )\n   { try {\n      FC_ASSERT( !self.is_locked() );\n      string normalized_brain_key = normalize_brain_key( brain_key );\n      // TODO:  scan blockchain for accounts that exist with same brain key\n      fc::ecc::private_key owner_privkey = derive_private_key( normalized_brain_key, 0 );\n      return create_account_with_private_key(owner_privkey, account_name, registrar_account,\n                                             referrer_account, broadcast, save_wallet);\n   } FC_CAPTURE_AND_RETHROW( (account_name)(registrar_account)(referrer_account) ) }\n\n   signed_transaction wallet_api_impl::account_store_map(string account, string catalog, bool remove,\n         flat_map<string, optional<string>> key_values, bool broadcast)\n   {\n      try\n      {\n         FC_ASSERT( !self.is_locked() );\n\n         account_id_type account_id = get_account(account).get_id();\n\n         custom_operation op;\n         account_storage_map store;\n         store.catalog = catalog;\n         store.remove = remove;\n         store.key_values = key_values;\n\n         custom_plugin_operation custom_plugin_op(store);\n         auto packed = fc::raw::pack(custom_plugin_op);\n\n         op.payer = account_id;\n         op.data = packed;\n\n         signed_transaction tx;\n         tx.operations.push_back(op);\n         set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n         tx.validate();\n\n         return sign_transaction(tx, broadcast);\n\n      } FC_CAPTURE_AND_RETHROW( (account)(remove)(catalog)(key_values)(broadcast) )\n   }\n\n   void wallet_api_impl::claim_registered_account(const graphene::chain::account_object& account)\n   {\n      bool import_keys = false;\n      auto it = _wallet.pending_account_registrations.find( account.name );\n      FC_ASSERT( it != _wallet.pending_account_registrations.end() );\n      for (const std::string& wif_key : it->second) {\n         if( !import_key( account.name, wif_key ) )\n         {\n            // somebody else beat our pending registration, there is\n            //    nothing we can do except log it and move on\n            elog( \"account ${name} registered by someone else first!\",\n                  (\"name\", account.name) );\n            // might as well remove it from pending regs,\n            //    because there is now no way this registration\n            //    can become valid (even in the extremely rare\n            //    possibility of migrating to a fork where the\n            //    name is available, the user can always\n            //    manually re-register)\n         } else {\n            import_keys = true;\n         }\n      }\n      _wallet.pending_account_registrations.erase( it );\n\n      if( import_keys ) {\n         save_wallet_file();\n      }\n   }\n\n   // after a witness registration succeeds, this saves the private key in the wallet permanently\n   //\n   void wallet_api_impl::claim_registered_witness(const std::string& witness_name)\n   {\n      auto iter = _wallet.pending_witness_registrations.find(witness_name);\n      FC_ASSERT(iter != _wallet.pending_witness_registrations.end());\n      std::string wif_key = iter->second;\n\n      // get the list key id this key is registered with in the chain\n      fc::optional<fc::ecc::private_key> witness_private_key = wif_to_key(wif_key);\n      FC_ASSERT(witness_private_key);\n\n      auto pub_key = witness_private_key->get_public_key();\n      _keys[pub_key] = wif_key;\n      _wallet.pending_witness_registrations.erase(iter);\n   }\n\n   account_object wallet_api_impl::get_account(account_id_type id) const\n   {\n      auto account_id = std::string(id);\n\n      auto rec = _remote_db->get_accounts({account_id}, {}).front();\n      FC_ASSERT(rec);\n      return *rec;\n   }\n\n   account_object wallet_api_impl::get_account(string account_name_or_id) const\n   {\n      FC_ASSERT( account_name_or_id.size() > 0 );\n\n      if( auto id = maybe_id<account_id_type>(account_name_or_id) )\n      {\n         // It's an ID\n         return get_account(*id);\n      } else {\n         auto rec = _remote_db->lookup_account_names({account_name_or_id}).front();\n         FC_ASSERT( rec && rec->name == account_name_or_id );\n         return *rec;\n      }\n   }\n\n   account_id_type wallet_api_impl::get_account_id(string account_name_or_id) const\n   {\n      return get_account(account_name_or_id).get_id();\n   }\n\n   signed_transaction wallet_api_impl::create_account_with_private_key(fc::ecc::private_key owner_privkey,\n         string account_name, string registrar_account, string referrer_account, bool broadcast,\n         bool save_wallet )\n   { try {\n         int active_key_index = find_first_unused_derived_key_index(owner_privkey);\n         fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), active_key_index);\n\n         int memo_key_index = find_first_unused_derived_key_index(active_privkey);\n         fc::ecc::private_key memo_privkey = derive_private_key( key_to_wif(active_privkey), memo_key_index);\n\n         graphene::chain::public_key_type owner_pubkey = owner_privkey.get_public_key();\n         graphene::chain::public_key_type active_pubkey = active_privkey.get_public_key();\n         graphene::chain::public_key_type memo_pubkey = memo_privkey.get_public_key();\n\n         account_create_operation account_create_op;\n\n         // TODO:  process when pay_from_account is ID\n\n         account_object registrar_account_object = get_account( registrar_account );\n\n         account_id_type registrar_account_id = registrar_account_object.get_id();\n\n         account_object referrer_account_object = get_account( referrer_account );\n         account_create_op.referrer = referrer_account_object.id;\n         account_create_op.referrer_percent = referrer_account_object.referrer_rewards_percentage;\n\n         account_create_op.registrar = registrar_account_id;\n         account_create_op.name = account_name;\n         account_create_op.owner = authority(1, owner_pubkey, 1);\n         account_create_op.active = authority(1, active_pubkey, 1);\n         account_create_op.options.memo_key = memo_pubkey;\n\n         // current_fee_schedule()\n         // find_account(pay_from_account)\n\n         // account_create_op.fee = account_create_op.calculate_fee(db.current_fee_schedule());\n\n         signed_transaction tx;\n         tx.operations.push_back( account_create_op );\n         set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n         tx.validate();\n\n         // we do not insert owner_privkey here because\n         //    it is intended to only be used for key recovery\n         _wallet.pending_account_registrations[account_name].push_back(key_to_wif( active_privkey ));\n         _wallet.pending_account_registrations[account_name].push_back(key_to_wif( memo_privkey ));\n         if( save_wallet )\n            save_wallet_file();\n         return sign_transaction(tx, broadcast);\n   } FC_CAPTURE_AND_RETHROW( (account_name)(registrar_account)(referrer_account)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::whitelist_account(string authorizing_account, string account_to_list, \n         account_whitelist_operation::account_listing new_listing_status, bool broadcast )\n   { try {\n      account_whitelist_operation whitelist_op;\n      whitelist_op.authorizing_account = get_account_id(authorizing_account);\n      whitelist_op.account_to_list = get_account_id(account_to_list);\n      whitelist_op.new_listing = new_listing_status;\n\n      signed_transaction tx;\n      tx.operations.push_back( whitelist_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (authorizing_account)(account_to_list)(new_listing_status)(broadcast) ) }\n\n   vector< vesting_balance_object_with_info > wallet_api_impl::get_vesting_balances( string account_name )\n   { try {\n      fc::optional<vesting_balance_id_type> vbid = maybe_id<vesting_balance_id_type>( account_name );\n      std::vector<vesting_balance_object_with_info> result;\n      fc::time_point_sec now = _remote_db->get_dynamic_global_properties().time;\n\n      if( vbid )\n      {\n         result.emplace_back( get_object(*vbid), now );\n         return result;\n      }\n\n      vector< vesting_balance_object > vbos = _remote_db->get_vesting_balances( account_name );\n      if( vbos.size() == 0 )\n         return result;\n\n      for( const vesting_balance_object& vbo : vbos )\n         result.emplace_back( vbo, now );\n\n      return result;\n   } FC_CAPTURE_AND_RETHROW( (account_name) )\n   }\n\n   vector< signed_transaction > wallet_api_impl::import_balance( string name_or_id, \n         const vector<string>& wif_keys, bool broadcast )\n   { try {\n      FC_ASSERT(!is_locked());\n      const dynamic_global_property_object& dpo = _remote_db->get_dynamic_global_properties();\n      account_object claimer = get_account( name_or_id );\n      uint32_t max_ops_per_tx = 30;\n\n      map< address, private_key_type > keys;  // local index of address -> private key\n      vector< address > addrs;\n      bool has_wildcard = false;\n      addrs.reserve( wif_keys.size() );\n      for( const string& wif_key : wif_keys )\n      {\n         if( wif_key == \"*\" )\n         {\n            if( has_wildcard )\n               continue;\n            for( const public_key_type& pub : _wallet.extra_keys[ claimer.get_id() ] )\n            {\n               addrs.push_back( address(pub) );\n               auto it = _keys.find( pub );\n               if( it != _keys.end() )\n               {\n                  fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second );\n                  FC_ASSERT( privkey );\n                  keys[ addrs.back() ] = *privkey;\n               }\n               else\n               {\n                  wlog( \"Somehow _keys has no private key for extra_keys public key ${k}\", (\"k\", pub) );\n               }\n            }\n            has_wildcard = true;\n         }\n         else\n         {\n            optional< private_key_type > key = wif_to_key( wif_key );\n            FC_ASSERT( key.valid(), \"Invalid private key\" );\n            fc::ecc::public_key pk = key->get_public_key();\n            addrs.push_back( address(pk) );\n            keys[addrs.back()] = *key;\n            // see chain/balance_evaluator.cpp\n            addrs.push_back( address( pts_address( pk, false ) ) ); // version = 56 (default)\n            keys[addrs.back()] = *key;\n            addrs.push_back( address( pts_address( pk, true ) ) ); // version = 56 (default)\n            keys[addrs.back()] = *key;\n            addrs.push_back( address( pts_address( pk, false, 0 ) ) );\n            keys[addrs.back()] = *key;\n            addrs.push_back( address( pts_address( pk, true, 0 ) ) );\n            keys[addrs.back()] = *key;\n         }\n      }\n\n      vector< balance_object > balances = _remote_db->get_balance_objects( addrs );\n      addrs.clear();\n\n      set<asset_id_type> bal_types;\n      for( auto b : balances ) bal_types.insert( b.balance.asset_id );\n\n      struct claim_tx\n      {\n         vector< balance_claim_operation > ops;\n         set< address > addrs;\n      };\n      vector< claim_tx > claim_txs;\n\n      for( const asset_id_type& a : bal_types )\n      {\n         balance_claim_operation op;\n         op.deposit_to_account = claimer.id;\n         for( const balance_object& b : balances )\n         {\n            if( b.balance.asset_id == a )\n            {\n               op.total_claimed = b.available( dpo.time );\n               if( op.total_claimed.amount == 0 )\n                  continue;\n               op.balance_to_claim = b.id;\n               op.balance_owner_key = keys[b.owner].get_public_key();\n               if( (claim_txs.empty()) || (claim_txs.back().ops.size() >= max_ops_per_tx) )\n                  claim_txs.emplace_back();\n               claim_txs.back().ops.push_back(op);\n               claim_txs.back().addrs.insert(b.owner);\n            }\n         }\n      }\n\n      vector< signed_transaction > result;\n\n      for( const claim_tx& ctx : claim_txs )\n      {\n         signed_transaction tx;\n         tx.operations.reserve( ctx.ops.size() );\n         for( const balance_claim_operation& op : ctx.ops )\n            tx.operations.emplace_back( op );\n         set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n         tx.validate();\n         signed_transaction signed_tx = sign_transaction( tx, false );\n         for( const address& addr : ctx.addrs )\n            signed_tx.sign( keys[addr], _chain_id );\n         // if the key for a balance object was the same as a key for the account we're importing it into,\n         // we may end up with duplicate signatures, so remove those\n         boost::erase(signed_tx.signatures, boost::unique<boost::return_found_end>(boost::sort(signed_tx.signatures)));\n         result.push_back( signed_tx );\n         if( broadcast )\n            _remote_net_broadcast->broadcast_transaction(signed_tx);\n      }\n\n      return result;\n   } FC_CAPTURE_AND_RETHROW( (name_or_id) ) }\n\n   vector<flat_set<account_id_type>> wallet_api_impl::get_key_references(const vector<public_key_type> &keys) const\n   {\n       return _remote_db->get_key_references(keys);\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_api_impl.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <boost/algorithm/string/replace.hpp>\n#include <boost/range/adaptors.hpp>\n\n#include <fc/rpc/api_connection.hpp>\n#include <fc/popcount.hpp>\n#include <fc/git_revision.hpp>\n#include <fc/thread/scoped_lock.hpp>\n#include <fc/io/fstream.hpp>\n\n#include <graphene/wallet/wallet.hpp>\n#include \"wallet_api_impl.hpp\"\n#include <graphene/utilities/git_revision.hpp>\n\n#ifndef WIN32\n# include <sys/types.h>\n# include <sys/stat.h>\n#endif\n\n// explicit instantiation for later use\nnamespace fc {\n\ttemplate class api<graphene::wallet::wallet_api, identity_member_with_optionals>;\n}\n\n/****\n * General methods for wallet impl object (ctor, info, about, wallet file, etc.)\n */\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   wallet_api_impl::wallet_api_impl( wallet_api& s, const wallet_data& initial_data, fc::api<login_api> rapi )\n      : self(s),\n        _chain_id(initial_data.chain_id),\n        _remote_api(rapi),\n        _remote_db(rapi->database()),\n        _remote_net_broadcast(rapi->network_broadcast()),\n        _remote_hist(rapi->history())\n   {\n      try {\n         _custom_operations = rapi->custom_operations();\n      }\n      catch(const fc::exception& e)\n      {\n         wlog(\"Custom operations API is not active on server.\");\n      }\n      chain_id_type remote_chain_id = _remote_db->get_chain_id();\n      if( remote_chain_id != _chain_id )\n      {\n         FC_THROW( \"Remote server gave us an unexpected chain_id\",\n            (\"remote_chain_id\", remote_chain_id)\n            (\"chain_id\", _chain_id) );\n      }\n      init_prototype_ops();\n\n      _remote_db->set_block_applied_callback( [this](const variant& block_id )\n      {\n         on_block_applied( block_id );\n      } );\n\n      _wallet.chain_id = _chain_id;\n      _wallet.ws_server = initial_data.ws_server;\n      _wallet.ws_user = initial_data.ws_user;\n      _wallet.ws_password = initial_data.ws_password;\n   }\n\n   wallet_api_impl::~wallet_api_impl()\n   {\n      try\n      {\n         _remote_db->cancel_all_subscriptions();\n      }\n      catch (const fc::exception& e)\n      {\n         // Right now the wallet_api has no way of knowing if the connection to the\n         // witness has already disconnected (via the witness node exiting first).\n         // If it has exited, cancel_all_subscriptsions() will throw and there's\n         // nothing we can do about it.\n         // dlog(\"Caught exception ${e} while canceling database subscriptions\", (\"e\", e));\n      }\n   }\n\n   fc::variant wallet_api_impl::info() const\n   {\n      auto chain_props = get_chain_properties();\n      auto global_props = get_global_properties();\n      auto dynamic_props = get_dynamic_global_properties();\n      fc::mutable_variant_object result;\n      result[\"head_block_num\"] = dynamic_props.head_block_number;\n      result[\"head_block_id\"] = fc::variant(dynamic_props.head_block_id, 1);\n      result[\"head_block_age\"] = fc::get_approximate_relative_time_string(dynamic_props.time,\n                                                                          time_point_sec(time_point::now()),\n                                                                          \" old\");\n      result[\"next_maintenance_time\"] =\n            fc::get_approximate_relative_time_string(dynamic_props.next_maintenance_time);\n      result[\"chain_id\"] = chain_props.chain_id;\n      stringstream participation;\n      participation << fixed << std::setprecision(2) << (100.0*fc::popcount(dynamic_props.recent_slots_filled)) / 128.0;\n      result[\"participation\"] = participation.str();\n      result[\"active_witnesses\"] = fc::variant(global_props.active_witnesses, GRAPHENE_MAX_NESTED_OBJECTS);\n      result[\"active_committee_members\"] =\n            fc::variant(global_props.active_committee_members, GRAPHENE_MAX_NESTED_OBJECTS);\n      return result;\n   }\n\n   /***\n    * @brief return basic information about this program\n    */\n   fc::variant_object wallet_api_impl::about() const\n   {\n      string client_version( graphene::utilities::git_revision_description );\n      const size_t pos = client_version.find( '/' );\n      if( pos != string::npos && client_version.size() > pos )\n         client_version = client_version.substr( pos + 1 );\n\n      fc::mutable_variant_object result;\n      //result[\"blockchain_name\"]        = BLOCKCHAIN_NAME;\n      //result[\"blockchain_description\"] = BTS_BLOCKCHAIN_DESCRIPTION;\n      result[\"client_version\"]           = client_version;\n      result[\"graphene_revision\"]        = graphene::utilities::git_revision_sha;\n      result[\"graphene_revision_age\"]    = fc::get_approximate_relative_time_string( fc::time_point_sec(\n                                                 graphene::utilities::git_revision_unix_timestamp ) );\n      result[\"fc_revision\"]              = fc::git_revision_sha;\n      result[\"fc_revision_age\"]          = fc::get_approximate_relative_time_string( fc::time_point_sec(\n                                                 fc::git_revision_unix_timestamp ) );\n      result[\"compile_date\"]             = \"compiled on \" __DATE__ \" at \" __TIME__;\n      result[\"boost_version\"]            = boost::replace_all_copy(std::string(BOOST_LIB_VERSION), \"_\", \".\");\n      result[\"openssl_version\"]          = OPENSSL_VERSION_TEXT;\n\n      std::string bitness = boost::lexical_cast<std::string>(8 * sizeof(int*)) + \"-bit\";\n#if defined(__APPLE__)\n      std::string os = \"osx\";\n#elif defined(__linux__)\n      std::string os = \"linux\";\n#elif defined(_MSC_VER)\n      std::string os = \"win32\";\n#else\n      std::string os = \"other\";\n#endif\n      result[\"build\"] = os + \" \" + bitness;\n\n      return result;\n   }\n\n   void wallet_api_impl::quit()\n   {\n      ilog( \"Quitting Cli Wallet ...\" );\n\n      throw fc::canceled_exception();\n   }\n\n   chain_property_object wallet_api_impl::get_chain_properties() const\n   {\n      return _remote_db->get_chain_properties();\n   }\n   global_property_object wallet_api_impl::get_global_properties() const\n   {\n      return _remote_db->get_global_properties();\n   }\n   dynamic_global_property_object wallet_api_impl::get_dynamic_global_properties() const\n   {\n      return _remote_db->get_dynamic_global_properties();\n   }\n\n   void wallet_api_impl::on_block_applied( const variant& block_id )\n   {\n      fc::async([this]{resync();}, \"Resync after block\");\n   }\n\n   void wallet_api_impl::set_operation_fees( signed_transaction& tx, const fee_schedule& s ) const\n   {\n      for( auto& op : tx.operations )\n         s.set_fee(op);\n   }\n\n   operation wallet_api_impl::get_prototype_operation( string operation_name )\n   {\n      auto it = _prototype_ops.find( operation_name );\n      if( it == _prototype_ops.end() )\n         FC_THROW(\"Unsupported operation: \\\"${operation_name}\\\"\", (\"operation_name\", operation_name));\n      return it->second;\n   }\n\n   void wallet_api_impl::init_prototype_ops()\n   {\n      operation op;\n      int64_t op_count = op.count();\n      for( int64_t t=0; t<op_count; t++ )\n      {\n         op.set_which( t );\n         op.visit( op_prototype_visitor(t, _prototype_ops) );\n      }\n      return;\n   }\n\n   int wallet_api_impl::find_first_unused_derived_key_index(const fc::ecc::private_key& parent_key)\n   {\n      int first_unused_index = 0;\n      int number_of_consecutive_unused_keys = 0;\n      for (int key_index = 0; ; ++key_index)\n      {\n         fc::ecc::private_key derived_private_key = derive_private_key(key_to_wif(parent_key), key_index);\n         graphene::chain::public_key_type derived_public_key = derived_private_key.get_public_key();\n         if( _keys.find(derived_public_key) == _keys.end() )\n         {\n            if (number_of_consecutive_unused_keys)\n            {\n               ++number_of_consecutive_unused_keys;\n               if (number_of_consecutive_unused_keys > 5)\n                  return first_unused_index;\n            }\n            else\n            {\n               first_unused_index = key_index;\n               number_of_consecutive_unused_keys = 1;\n            }\n         }\n         else\n         {\n            // key_index is used\n            first_unused_index = 0;\n            number_of_consecutive_unused_keys = 0;\n         }\n      }\n   }\n\n   void wallet_api_impl::enable_umask_protection()\n   {\n#ifdef __unix__\n      _old_umask = umask( S_IRWXG | S_IRWXO );\n#endif\n   }\n\n   void wallet_api_impl::disable_umask_protection()\n   {\n#ifdef __unix__\n      umask( _old_umask );\n#endif\n   }\n\n   bool wallet_api_impl::copy_wallet_file( string destination_filename )\n   {\n      fc::path src_path = get_wallet_filename();\n      if( !fc::exists( src_path ) )\n         return false;\n      fc::path dest_path = destination_filename + _wallet_filename_extension;\n      int suffix = 0;\n      while( fc::exists(dest_path) )\n      {\n         ++suffix;\n         dest_path = destination_filename + \"-\" + to_string( suffix ) + _wallet_filename_extension;\n      }\n      wlog( \"backing up wallet ${src} to ${dest}\",\n            (\"src\", src_path)\n            (\"dest\", dest_path) );\n\n      fc::path dest_parent = fc::absolute(dest_path).parent_path();\n      try\n      {\n         enable_umask_protection();\n         if( !fc::exists( dest_parent ) )\n            fc::create_directories( dest_parent );\n         fc::copy( src_path, dest_path );\n         disable_umask_protection();\n      }\n      catch(...)\n      {\n         disable_umask_protection();\n         throw;\n      }\n      return true;\n   }\n\n   /***\n    * @brief returns true if the wallet is unlocked\n    */\n   bool wallet_api_impl::is_locked()const\n   {\n      return _checksum == fc::sha512();\n   }\n\n   void wallet_api_impl::resync()\n   {\n      fc::scoped_lock<fc::mutex> lock(_resync_mutex);\n      // this method is used to update wallet_data annotations\n      //   e.g. wallet has been restarted and was not notified\n      //   of events while it was down\n      //\n      // everything that is done \"incremental style\" when a push\n      //   notification is received, should also be done here\n      //   \"batch style\" by querying the blockchain\n\n      if( !_wallet.pending_account_registrations.empty() )\n      {\n         // make a vector of the account names pending registration\n         std::vector<string> pending_account_names =\n               boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_account_registrations));\n\n         // look those up on the blockchain\n         std::vector<fc::optional<graphene::chain::account_object >>\n               pending_account_objects = _remote_db->lookup_account_names( pending_account_names );\n\n         // if any of them exist, claim them\n         for( const fc::optional<graphene::chain::account_object>& optional_account : pending_account_objects )\n            if( optional_account )\n               claim_registered_account(*optional_account);\n      }\n\n      if (!_wallet.pending_witness_registrations.empty())\n      {\n         // make a vector of the owner accounts for witnesses pending registration\n         std::vector<string> pending_witness_names =\n               boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_witness_registrations));\n\n         // look up the owners on the blockchain\n         std::vector<fc::optional<graphene::chain::account_object>> owner_account_objects =\n               _remote_db->lookup_account_names(pending_witness_names);\n\n         // if any of them have registered witnesses, claim them\n         for( const fc::optional<graphene::chain::account_object>& optional_account : owner_account_objects )\n            if (optional_account)\n            {\n               auto account_id = std::string(optional_account->id);\n               fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(account_id);\n               if (witness_obj)\n                  claim_registered_witness(optional_account->name);\n            }\n      }\n   }\n\n   string wallet_api_impl::get_wallet_filename() const\n   {\n      return _wallet_filename;\n   }\n\n   bool wallet_api_impl::load_wallet_file(string wallet_filename)\n   {\n      // TODO:  Merge imported wallet with existing wallet,\n      //        instead of replacing it\n      if( wallet_filename == \"\" )\n         wallet_filename = _wallet_filename;\n\n      if( ! fc::exists( wallet_filename ) )\n         return false;\n\n      _wallet = fc::json::from_file( wallet_filename ).as< wallet_data >( 2 * GRAPHENE_MAX_NESTED_OBJECTS );\n      if( _wallet.chain_id != _chain_id )\n         FC_THROW( \"Wallet chain ID does not match\",\n            (\"wallet.chain_id\", _wallet.chain_id)\n            (\"chain_id\", _chain_id) );\n\n      size_t account_pagination = 100;\n      vector< std::string > account_ids_to_send;\n      size_t n = _wallet.my_accounts.size();\n      account_ids_to_send.reserve( std::min( account_pagination, n ) );\n      auto it = _wallet.my_accounts.begin();\n\n      for( size_t start=0; start<n; start+=account_pagination )\n      {\n         size_t end = std::min( start+account_pagination, n );\n         assert( end > start );\n         account_ids_to_send.clear();\n         std::vector< account_object > old_accounts;\n         for( size_t i=start; i<end; i++ )\n         {\n            assert( it != _wallet.my_accounts.end() );\n            old_accounts.push_back( *it );\n            auto account_id = std::string(old_accounts.back().id);\n            account_ids_to_send.push_back( account_id );\n            ++it;\n         }\n         std::vector< optional< account_object > > accounts = _remote_db->get_accounts(account_ids_to_send, {});\n         // server response should be same length as request\n         FC_ASSERT( accounts.size() == account_ids_to_send.size() );\n         size_t i = 0;\n         for( const optional< account_object >& acct : accounts )\n         {\n            account_object& old_acct = old_accounts[i];\n            if( !acct.valid() )\n            {\n               elog( \"Could not find account ${id} : \\\"${name}\\\" does not exist on the chain!\",\n                     (\"id\", old_acct.id)(\"name\", old_acct.name) );\n               i++;\n               continue;\n            }\n            // this check makes sure the server didn't send results\n            // in a different order, or accounts we didn't request\n            FC_ASSERT( acct->id == old_acct.id );\n            if( fc::json::to_string(*acct) != fc::json::to_string(old_acct) )\n            {\n               wlog( \"Account ${id} : \\\"${name}\\\" updated on chain\", (\"id\", acct->id)(\"name\", acct->name) );\n            }\n            _wallet.update_account( *acct );\n            i++;\n         }\n      }\n\n      return true;\n   }\n\n   void wallet_api_impl::save_wallet_file(string wallet_filename)\n   {\n      //\n      // Serialize in memory, then save to disk\n      //\n      // This approach lessens the risk of a partially written wallet\n      // if exceptions are thrown in serialization\n      //\n\n      encrypt_keys();\n\n      if( wallet_filename == \"\" )\n         wallet_filename = _wallet_filename;\n\n      wlog( \"saving wallet to file ${fn}\", (\"fn\", wallet_filename) );\n\n      string data = fc::json::to_pretty_string( _wallet );\n\n      try\n      {\n         enable_umask_protection();\n         //\n         // Parentheses on the following declaration fails to compile,\n         // due to the Most Vexing Parse.  Thanks, C++\n         //\n         // http://en.wikipedia.org/wiki/Most_vexing_parse\n         //\n         std::string tmp_wallet_filename = wallet_filename + \".tmp\";\n         fc::ofstream outfile{ fc::path( tmp_wallet_filename ) };\n         outfile.write( data.c_str(), data.length() );\n         outfile.flush();\n         outfile.close();\n\n         wlog( \"saved successfully wallet to tmp file ${fn}\", (\"fn\", tmp_wallet_filename) );\n\n         std::string wallet_file_content;\n         fc::read_file_contents(tmp_wallet_filename, wallet_file_content);\n\n         if (wallet_file_content == data) {\n            wlog( \"validated successfully tmp wallet file ${fn}\", (\"fn\", tmp_wallet_filename) );\n\n            fc::rename( tmp_wallet_filename, wallet_filename );\n\n            wlog( \"renamed successfully tmp wallet file ${fn}\", (\"fn\", tmp_wallet_filename) );\n         }\n         else\n         {\n            FC_THROW(\"tmp wallet file cannot be validated ${fn}\", (\"fn\", tmp_wallet_filename) );\n         }\n\n         wlog( \"successfully saved wallet to file ${fn}\", (\"fn\", wallet_filename) );\n\n         disable_umask_protection();\n      }\n      catch(...)\n      {\n         string ws_password = _wallet.ws_password;\n         _wallet.ws_password = \"\";\n         wlog(\"wallet file content is next: ${data}\", (\"data\", fc::json::to_pretty_string( _wallet ) ) );\n         _wallet.ws_password = ws_password;\n\n         disable_umask_protection();\n         throw;\n      }\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_api_impl.hpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/thread/mutex.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include <graphene/wallet/api_documentation.hpp>\n#include <graphene/wallet/wallet_structs.hpp>\n#include <graphene/wallet/reflect_util.hpp>\n\nnamespace graphene { namespace wallet { \n\nclass wallet_api;\n\nnamespace detail {\n\nusing namespace graphene::protocol;\nusing namespace graphene::chain;\nusing namespace graphene::app;\n\nstatic const string ENC_HEADER( \"-----BEGIN BITSHARES SIGNED MESSAGE-----\\n\" );\nstatic const string ENC_META(   \"-----BEGIN META-----\\n\" );\nstatic const string ENC_SIG(    \"-----BEGIN SIGNATURE-----\\n\" );\nstatic const string ENC_FOOTER( \"-----END BITSHARES SIGNED MESSAGE-----\" );\n\ntemplate<class T>\nfc::optional<T> maybe_id( const string& name_or_id )\n{\n   if( std::isdigit( name_or_id.front() ) )\n   {\n      try\n      {\n         return fc::variant(name_or_id, 1).as<T>(1);\n      }\n      catch (const fc::exception&)\n      { // not an ID\n      }\n   }\n   return fc::optional<T>();\n}\n\nstring address_to_shorthash( const graphene::protocol::address& addr );\n\nfc::ecc::private_key derive_private_key( const std::string& prefix_string, int sequence_number );\n\nstring normalize_brain_key( string s );\n\nstruct op_prototype_visitor\n{\n   typedef void result_type;\n\n   int t = 0;\n   flat_map< std::string, graphene::protocol::operation >& name2op;\n\n   op_prototype_visitor(\n      int _t,\n      flat_map< std::string, graphene::protocol::operation >& _prototype_ops\n      ):t(_t), name2op(_prototype_ops) {}\n\n   template<typename Type>\n   result_type operator()( const Type& op )const\n   {\n      string name = fc::get_typename<Type>::name();\n      size_t p = name.rfind(':');\n      if( p != string::npos )\n         name = name.substr( p+1 );\n      name2op[ name ] = Type();\n   }\n};\n\nclass wallet_api_impl\n{\npublic:\n   api_documentation method_documentation;\n   wallet_api& self;\n\n   wallet_api_impl( wallet_api& s, const wallet_data& initial_data, fc::api<login_api> rapi );\n\n   virtual ~wallet_api_impl();\n\n   /***\n    * @brief encrypt the keys\n    * This is normally done before saving the wallet file\n    */\n   void encrypt_keys();\n\n   /***\n    * @brief called when a block is applied\n    */\n   void on_block_applied( const variant& block_id );\n\n   /**\n    * @brief make a copy of the wallet file\n    * Note: this will not overwrite. It simply adds a version suffix.\n    * \n    * @param destination_filename the filename to save it to\n    */\n   bool copy_wallet_file( string destination_filename );\n\n   /***\n    * @brief returns true if the wallet is unlocked\n    */\n   bool is_locked()const;\n\n   template<typename ID>\n   graphene::db::object_downcast_t<const ID&> get_object(const ID& id)const\n   {\n      auto ob = _remote_db->get_objects({object_id_type(id)}, {}).front();\n      return ob.template as<graphene::db::object_downcast_t<const ID&>>( GRAPHENE_MAX_NESTED_OBJECTS );\n   }\n\n   /***\n    * @brief set fees for each operation in a transaction\n    * @param tx the transaction\n    * @param s the fee schedule\n    */\n   void set_operation_fees( signed_transaction& tx, const fee_schedule& s  ) const;\n\n   /***\n    * @brief return basic info about the chain\n    */\n   variant info() const;\n\n   /***\n    * @brief return basic information about this program\n    */\n   variant_object about() const;\n\n   chain_property_object get_chain_properties() const;\n   global_property_object get_global_properties() const;\n   dynamic_global_property_object get_dynamic_global_properties() const;\n\n   account_object get_account(account_id_type id) const;\n   account_object get_account(string account_name_or_id) const;\n   account_id_type get_account_id(string account_name_or_id) const;\n\n   std::string asset_id_to_string(asset_id_type id) const;\n   \n   optional<extended_asset_object> find_asset(asset_id_type id)const;\n\n   optional<extended_asset_object> find_asset(string asset_symbol_or_id)const;\n\n   extended_asset_object get_asset(asset_id_type id)const;\n\n   extended_asset_object get_asset(string asset_symbol_or_id)const;\n\n   fc::optional<htlc_object> get_htlc(const htlc_id_type& htlc_id) const;\n\n   asset_id_type get_asset_id(const string& asset_symbol_or_id) const;\n\n   string get_wallet_filename() const;\n\n   fc::ecc::private_key get_private_key(const public_key_type& id)const;\n\n   fc::ecc::private_key get_private_key_for_account(const account_object& account)const;\n\n   // imports the private key into the wallet, and associate it in some way (?) with the\n   // given account name.\n   // @returns true if the key matches a current active/owner/memo key for the named\n   //          account, false otherwise (but it is stored either way)\n   bool import_key(string account_name_or_id, string wif_key);\n\n   vector< signed_transaction > import_balance( string name_or_id, const vector<string>& wif_keys, bool broadcast );\n\n   bool load_wallet_file(string wallet_filename = \"\");\n\n   /**\n    * Get the required public keys to sign the transaction which had been\n    * owned by us\n    *\n    * NOTE, if `erase_existing_sigs` set to true, the original trasaction's\n    * signatures will be erased\n    *\n    * @param tx           The transaction to be signed\n    * @param erase_existing_sigs\n    *        The transaction could have been partially signed already,\n    *        if set to false, the corresponding public key of existing\n    *        signatures won't be returned.\n    *        If set to true, the existing signatures will be erased and\n    *        all required keys returned.\n   */\n   set<public_key_type> get_owned_required_keys( signed_transaction &tx,\n         bool erase_existing_sigs = true);\n\n   signed_transaction add_transaction_signature( signed_transaction tx,\n         bool broadcast );\n\n   void quit();\n\n   void save_wallet_file(string wallet_filename = \"\");\n\n   transaction_handle_type begin_builder_transaction();\n   void add_operation_to_builder_transaction(transaction_handle_type transaction_handle, const operation& op);\n   void replace_operation_in_builder_transaction(transaction_handle_type handle,\n         uint32_t operation_index, const operation& new_op);\n   asset set_fees_on_builder_transaction(transaction_handle_type handle, string fee_asset = GRAPHENE_SYMBOL);\n   transaction preview_builder_transaction(transaction_handle_type handle);\n   signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true);\n   signed_transaction sign_builder_transaction2(transaction_handle_type transaction_handle,\n         const vector<public_key_type>& signing_keys = vector<public_key_type>(), bool broadcast = true);\n\n   pair<transaction_id_type,signed_transaction> broadcast_transaction(signed_transaction tx);\n\n   signed_transaction propose_builder_transaction( transaction_handle_type handle,\n         time_point_sec expiration = time_point::now() + fc::minutes(1),\n         uint32_t review_period_seconds = 0, bool broadcast = true);\n\n   signed_transaction propose_builder_transaction2( transaction_handle_type handle,\n         string account_name_or_id, time_point_sec expiration = time_point::now() + fc::minutes(1),\n         uint32_t review_period_seconds = 0, bool broadcast = true);\n\n   void remove_builder_transaction(transaction_handle_type handle);\n\n   signed_transaction register_account(string name, public_key_type owner, public_key_type active,\n         string  registrar_account, string  referrer_account, uint32_t referrer_percent,\n         bool broadcast = false);\n   \n   signed_transaction upgrade_account(string name, bool broadcast);\n\n   signed_transaction create_account_with_private_key(fc::ecc::private_key owner_privkey,\n         string account_name, string registrar_account, string referrer_account,\n         bool broadcast = false, bool save_wallet = true);\n\n   signed_transaction create_account_with_brain_key(string brain_key, string account_name, string registrar_account,\n         string referrer_account, bool broadcast = false, bool save_wallet = true);\n\n   signed_transaction create_asset(string issuer, string symbol, uint8_t precision, asset_options common,\n         fc::optional<bitasset_options> bitasset_opts, bool broadcast = false);\n\n   signed_transaction update_asset(string symbol, optional<string> new_issuer, asset_options new_options,\n         bool broadcast );\n\n   signed_transaction update_asset_issuer(string symbol, string new_issuer, bool broadcast );\n\n   signed_transaction update_bitasset(string symbol, bitasset_options new_options, bool broadcast );\n\n   signed_transaction update_asset_feed_producers(string symbol, flat_set<string> new_feed_producers,\n         bool broadcast );\n\n   signed_transaction publish_asset_feed(string publishing_account, string symbol, price_feed feed,\n         bool broadcast );\n\n   signed_transaction fund_asset_fee_pool(string from, string symbol, string amount, bool broadcast );\n\n   signed_transaction claim_asset_fee_pool(string symbol, string amount, bool broadcast );\n\n   signed_transaction reserve_asset(string from, string amount, string symbol, bool broadcast );\n\n   signed_transaction global_settle_asset(string symbol, price settle_price, bool broadcast );\n\n   signed_transaction settle_asset(string account_to_settle, string amount_to_settle, string symbol,\n         bool broadcast );\n\n   signed_transaction bid_collateral(string bidder_name, string debt_amount, string debt_symbol,\n         string additional_collateral, bool broadcast );\n\n   signed_transaction whitelist_account(string authorizing_account, string account_to_list,\n         account_whitelist_operation::account_listing new_listing_status, bool broadcast );\n\n   signed_transaction create_committee_member(string owner_account, string url, bool broadcast );\n\n   witness_object get_witness( string owner_account );\n\n   committee_member_object get_committee_member( string owner_account );\n\n   signed_transaction create_witness(string owner_account, string url, bool broadcast );\n\n   signed_transaction update_witness(string witness_name, string url, string block_signing_key,\n         bool broadcast );\n\n   signed_transaction create_worker( string owner_account, time_point_sec work_begin_date,\n         time_point_sec work_end_date, share_type daily_pay, string name, string url,\n         variant worker_settings, bool broadcast );\n\n   signed_transaction update_worker_votes( string account, worker_vote_delta delta, bool broadcast );\n\n   signed_transaction htlc_create( const string& source, const string& destination,\n         const string& amount, const string& asset_symbol, const string& hash_algorithm,\n         const string& preimage_hash, uint32_t preimage_size,\n         uint32_t claim_period_seconds, const string& memo, bool broadcast = false);\n\n   signed_transaction htlc_redeem( const htlc_id_type& htlc_id, const string& issuer,\n         const std::vector<char>& preimage, bool broadcast );\n\n   signed_transaction htlc_extend( const htlc_id_type& htlc_id, const string& issuer,\n         uint32_t seconds_to_add, bool broadcast);\n\n   signed_transaction account_store_map(string account, string catalog, bool remove,\n         flat_map<string, optional<string>> key_values, bool broadcast);\n\n   vector< vesting_balance_object_with_info > get_vesting_balances( string account_name );\n\n   signed_transaction withdraw_vesting( string witness_name, string amount, string asset_symbol,\n         bool broadcast = false );\n\n   signed_transaction vote_for_committee_member(string voting_account, string committee_member,\n         bool approve, bool broadcast );\n\n   signed_transaction vote_for_witness(string voting_account, string witness, bool approve,\n         bool broadcast );\n\n   signed_transaction set_voting_proxy(string account_to_modify, optional<string> voting_account,\n         bool broadcast );\n\n   signed_transaction set_desired_witness_and_committee_member_count(string account_to_modify,\n         uint16_t desired_number_of_witnesses, uint16_t desired_number_of_committee_members,\n         bool broadcast );\n\n   signed_transaction sign_transaction(signed_transaction tx, bool broadcast = false);\n   signed_transaction sign_transaction2(signed_transaction tx,\n                                        const vector<public_key_type>& signing_keys = vector<public_key_type>(),\n                                        bool broadcast = false);\n\n   flat_set<public_key_type> get_transaction_signers(const signed_transaction &tx) const;\n\n   vector<flat_set<account_id_type>> get_key_references(const vector<public_key_type> &keys) const;\n\n   memo_data sign_memo(string from, string to, string memo);\n\n   string read_memo(const memo_data& md);\n\n   signed_message sign_message(string signer, string message);\n\n   bool verify_message( const string& message, const string& account, int32_t block, const string& msg_time,\n         const fc::ecc::compact_signature& sig );\n\n   bool verify_signed_message( const signed_message& message );\n\n   bool verify_encapsulated_message( const string& message );\n\n   signed_transaction sell_asset(string seller_account, string amount_to_sell, string symbol_to_sell,\n         string min_to_receive, string symbol_to_receive, uint32_t timeout_sec = 0,\n         bool fill_or_kill = false, bool broadcast = false);\n\n   signed_transaction borrow_asset_ext( string seller_name, string amount_to_borrow, string asset_symbol,\n         string amount_of_collateral, call_order_update_operation::extensions_type extensions,\n         bool broadcast = false);\n\n   signed_transaction cancel_order(const limit_order_id_type& order_id, bool broadcast = false);\n\n   signed_transaction transfer(string from, string to, string amount,\n         string asset_symbol, string memo, bool broadcast = false);\n\n   signed_transaction issue_asset(string to_account, string amount, string symbol,\n         string memo, bool broadcast = false);\n\n   std::map< string, std::function< string( const fc::variant&, const fc::variants& ) >, std::less<> >\n         get_result_formatters() const;\n\n   signed_transaction propose_parameter_change( const string& proposing_account, fc::time_point_sec expiration_time,\n         const variant_object& changed_values, bool broadcast = false);\n\n   signed_transaction propose_fee_change( const string& proposing_account, fc::time_point_sec expiration_time,\n         const variant_object& changed_fees, bool broadcast = false);\n\n   signed_transaction approve_proposal( const string& fee_paying_account, const string& proposal_id,\n         const approval_delta& delta, bool broadcast = false);\n\n   void dbg_make_uia(string creator, string symbol);\n\n   void dbg_make_mia(string creator, string symbol);\n\n   void dbg_push_blocks( const std::string& src_filename, uint32_t count );\n\n   void dbg_generate_blocks( const std::string& debug_wif_key, uint32_t count );\n\n   void dbg_stream_json_objects( const std::string& filename );\n\n   void dbg_update_object( const fc::variant_object& update );\n\n   void use_network_node_api();\n\n   void use_debug_api();\n\n   void network_add_nodes( const vector<string>& nodes );\n\n   vector< variant > network_get_connected_peers();\n\n   void flood_network(string prefix, uint32_t number_of_transactions);\n\n   operation get_prototype_operation( string operation_name );\n\n   string                  _wallet_filename;\n   wallet_data             _wallet;\n\n   map<public_key_type,string> _keys;\n   fc::sha512                  _checksum;\n\n   chain_id_type           _chain_id;\n   fc::api<login_api>      _remote_api;\n   fc::api<database_api>   _remote_db;\n   fc::api<network_broadcast_api>   _remote_net_broadcast;\n   fc::api<history_api>    _remote_hist;\n   fc::api<custom_operations_api>    _custom_operations;\n   optional< fc::api<network_node_api> > _remote_net_node;\n   optional< fc::api<graphene::debug_witness::debug_api> > _remote_debug;\n\n   flat_map<string, operation> _prototype_ops;\n\n   static_variant_map _operation_which_map = create_static_variant_map< operation >();\n\nprivate:\n   static htlc_hash do_hash( const string& algorithm, const std::string& hash );\n\n   void enable_umask_protection();\n\n   void disable_umask_protection();\n\n   // This function generates derived keys starting with index 0 and keeps incrementing\n   // the index until it finds a key that isn't registered in the block chain.  To be\n   // safer, it continues checking for a few more keys to make sure there wasn't a short gap\n   // caused by a failed registration or the like.\n   int find_first_unused_derived_key_index(const fc::ecc::private_key& parent_key);\n\n   void claim_registered_account(const graphene::chain::account_object& account);\n\n   // after a witness registration succeeds, this saves the private key in the wallet permanently\n   //\n   void claim_registered_witness(const std::string& witness_name);\n\n   fc::mutex _resync_mutex;\n   void resync();\n\n   void init_prototype_ops();\n\n   map<transaction_handle_type, signed_transaction> _builder_transactions;\n\n   // if the user executes the same command twice in quick succession,\n   // we might generate the same transaction id, and cause the second\n   // transaction to be rejected.  This can be avoided by altering the\n   // second transaction slightly (bumping up the expiration time by\n   // a second).  Keep track of recent transaction ids we've generated\n   // so we can know if we need to do this\n   struct recently_generated_transaction_record\n   {\n      fc::time_point_sec generation_time;\n      graphene::chain::transaction_id_type transaction_id;\n   };\n   struct timestamp_index{};\n   typedef boost::multi_index_container<\n         recently_generated_transaction_record,\n         boost::multi_index::indexed_by<\n            boost::multi_index::hashed_unique<\n               boost::multi_index::member<\n                  recently_generated_transaction_record,\n                  graphene::chain::transaction_id_type,\n                  &recently_generated_transaction_record::transaction_id\n               >,\n               std::hash<graphene::chain::transaction_id_type>\n            >,\n            boost::multi_index::ordered_non_unique<\n               boost::multi_index::tag<timestamp_index>,\n               boost::multi_index::member<\n                  recently_generated_transaction_record,\n                  fc::time_point_sec,\n                  &recently_generated_transaction_record::generation_time\n            >\n         >\n       >\n     > recently_generated_transaction_set_type;\n   recently_generated_transaction_set_type _recently_generated_transactions;\n\n#ifdef __unix__\n   mode_t                  _old_umask;\n#endif\n   const string _wallet_filename_extension = \".wallet\";\n};\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_asset.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"wallet_api_impl.hpp\"\n#include <graphene/wallet/wallet.hpp>\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   std::string wallet_api_impl::asset_id_to_string(asset_id_type id) const\n   {\n      std::string asset_id = fc::to_string(id.space_id) +\n                             \".\" + fc::to_string(id.type_id) +\n                             \".\" + fc::to_string(id.instance.value);\n      return asset_id;\n   }\n\n   optional<extended_asset_object> wallet_api_impl::find_asset(asset_id_type id)const\n   {\n      auto rec = _remote_db->get_assets({asset_id_to_string(id)}, {}).front();\n      return rec;\n   }\n\n   optional<extended_asset_object> wallet_api_impl::find_asset(string asset_symbol_or_id)const\n   {\n      FC_ASSERT( asset_symbol_or_id.size() > 0 );\n\n      if( auto id = maybe_id<asset_id_type>(asset_symbol_or_id) )\n      {\n         // It's an ID\n         return find_asset(*id);\n      } else {\n         // It's a symbol\n         auto rec = _remote_db->lookup_asset_symbols({asset_symbol_or_id}).front();\n         if( rec )\n         {\n            if( rec->symbol != asset_symbol_or_id )\n               return optional<asset_object>();\n         }\n         return rec;\n      }\n   }\n\n   extended_asset_object wallet_api_impl::get_asset(asset_id_type id)const\n   {\n      auto opt = find_asset(id);\n      FC_ASSERT(opt);\n      return *opt;\n   }\n\n   extended_asset_object wallet_api_impl::get_asset(string asset_symbol_or_id)const\n   {\n      auto opt = find_asset(asset_symbol_or_id);\n      FC_ASSERT(opt);\n      return *opt;\n   }\n\n   asset_id_type wallet_api_impl::get_asset_id(const string& asset_symbol_or_id) const\n   {\n      FC_ASSERT( asset_symbol_or_id.size() > 0 );\n      vector<optional<extended_asset_object>> opt_asset;\n      if( std::isdigit( asset_symbol_or_id.front() ) )\n         return fc::variant(asset_symbol_or_id, 1).as<asset_id_type>( 1 );\n      opt_asset = _remote_db->lookup_asset_symbols( {asset_symbol_or_id} );\n      FC_ASSERT( (opt_asset.size() > 0) && (opt_asset[0].valid()) );\n      return opt_asset[0]->get_id();\n   }\n\n   signed_transaction wallet_api_impl::create_asset(string issuer, string symbol,\n         uint8_t precision, asset_options common, fc::optional<bitasset_options> bitasset_opts,\n         bool broadcast )\n   { try {\n      account_object issuer_account = get_account( issuer );\n      FC_ASSERT(!find_asset(symbol).valid(), \"Asset with that symbol already exists!\");\n\n      asset_create_operation create_op;\n      create_op.issuer = issuer_account.id;\n      create_op.symbol = symbol;\n      create_op.precision = precision;\n      create_op.common_options = common;\n      create_op.bitasset_opts = bitasset_opts;\n\n      signed_transaction tx;\n      tx.operations.push_back( create_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (issuer)(symbol)(precision)(common)(bitasset_opts)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::update_asset(string symbol, optional<string> new_issuer,\n         asset_options new_options, bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_update = find_asset(symbol);\n      if (!asset_to_update)\n         FC_THROW(\"No asset with that symbol exists!\");\n      optional<account_id_type> new_issuer_account_id;\n      if (new_issuer)\n      {\n         FC_THROW( \"The use of 'new_issuer' is no longer supported. Please use `update_asset_issuer' instead!\" );\n      }\n\n      asset_update_operation update_op;\n      update_op.issuer = asset_to_update->issuer;\n      update_op.asset_to_update = asset_to_update->id;\n      update_op.new_issuer = new_issuer_account_id;\n      update_op.new_options = new_options;\n\n      signed_transaction tx;\n      tx.operations.push_back( update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (symbol)(new_issuer)(new_options)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::update_asset_issuer(string symbol, string new_issuer,\n         bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_update = find_asset(symbol);\n      if (!asset_to_update)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      account_object new_issuer_account = get_account(new_issuer);\n\n      asset_update_issuer_operation update_issuer;\n      update_issuer.issuer = asset_to_update->issuer;\n      update_issuer.asset_to_update = asset_to_update->id;\n      update_issuer.new_issuer = new_issuer_account.id;\n\n      signed_transaction tx;\n      tx.operations.push_back( update_issuer );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (symbol)(new_issuer)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::update_bitasset(string symbol, bitasset_options new_options,\n         bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_update = find_asset(symbol);\n      if (!asset_to_update)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      asset_update_bitasset_operation update_op;\n      update_op.issuer = asset_to_update->issuer;\n      update_op.asset_to_update = asset_to_update->id;\n      update_op.new_options = new_options;\n\n      signed_transaction tx;\n      tx.operations.push_back( update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::update_asset_feed_producers(string symbol, \n         flat_set<string> new_feed_producers, bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_update = find_asset(symbol);\n      if (!asset_to_update)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      asset_update_feed_producers_operation update_op;\n      update_op.issuer = asset_to_update->issuer;\n      update_op.asset_to_update = asset_to_update->id;\n      update_op.new_feed_producers.reserve(new_feed_producers.size());\n      std::transform(new_feed_producers.begin(), new_feed_producers.end(),\n                     std::inserter(update_op.new_feed_producers, update_op.new_feed_producers.end()),\n                     [this](const std::string& account_name_or_id){ return get_account_id(account_name_or_id); });\n\n      signed_transaction tx;\n      tx.operations.push_back( update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (symbol)(new_feed_producers)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::publish_asset_feed(string publishing_account, string symbol,\n         price_feed feed, bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_update = find_asset(symbol);\n      if (!asset_to_update)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      asset_publish_feed_operation publish_op;\n      publish_op.publisher = get_account_id(publishing_account);\n      publish_op.asset_id = asset_to_update->id;\n      publish_op.feed = feed;\n\n      signed_transaction tx;\n      tx.operations.push_back( publish_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (publishing_account)(symbol)(feed)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::fund_asset_fee_pool(string from, string symbol, string amount,\n         bool broadcast /* = false */)\n   { try {\n      account_object from_account = get_account(from);\n      optional<asset_object> asset_to_fund = find_asset(symbol);\n      if (!asset_to_fund)\n        FC_THROW(\"No asset with that symbol exists!\");\n      auto core_asset = get_asset(asset_id_type());\n\n      asset_fund_fee_pool_operation fund_op;\n      fund_op.from_account = from_account.id;\n      fund_op.asset_id = asset_to_fund->id;\n      fund_op.amount = core_asset.amount_from_string(amount).amount;\n\n      signed_transaction tx;\n      tx.operations.push_back( fund_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (from)(symbol)(amount)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::claim_asset_fee_pool(string symbol, string amount,\n         bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_pool_to_claim = find_asset(symbol);\n      if (!asset_pool_to_claim)\n        FC_THROW(\"No asset with that symbol exists!\");\n      auto core_asset = get_asset(asset_id_type());\n\n      asset_claim_pool_operation claim_op;\n      claim_op.issuer = asset_pool_to_claim->issuer;\n      claim_op.asset_id = asset_pool_to_claim->id;\n      claim_op.amount_to_claim = core_asset.amount_from_string(amount).amount;\n\n      signed_transaction tx;\n      tx.operations.push_back( claim_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (symbol)(amount)(broadcast) ) }\n\n\n   signed_transaction wallet_api_impl::reserve_asset(string from, string amount, string symbol,\n         bool broadcast /* = false */)\n   { try {\n      account_object from_account = get_account(from);\n      optional<asset_object> asset_to_reserve = find_asset(symbol);\n      if (!asset_to_reserve)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      asset_reserve_operation reserve_op;\n      reserve_op.payer = from_account.id;\n      reserve_op.amount_to_reserve = asset_to_reserve->amount_from_string(amount);\n\n      signed_transaction tx;\n      tx.operations.push_back( reserve_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (from)(amount)(symbol)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::global_settle_asset(string symbol, price settle_price,\n         bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_settle = find_asset(symbol);\n      if (!asset_to_settle)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      asset_global_settle_operation settle_op;\n      settle_op.issuer = asset_to_settle->issuer;\n      settle_op.asset_to_settle = asset_to_settle->id;\n      settle_op.settle_price = settle_price;\n\n      signed_transaction tx;\n      tx.operations.push_back( settle_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (symbol)(settle_price)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::settle_asset(string account_to_settle, string amount_to_settle,\n         string symbol, bool broadcast /* = false */)\n   { try {\n      optional<asset_object> asset_to_settle = find_asset(symbol);\n      if (!asset_to_settle)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      asset_settle_operation settle_op;\n      settle_op.account = get_account_id(account_to_settle);\n      settle_op.amount = asset_to_settle->amount_from_string(amount_to_settle);\n\n      signed_transaction tx;\n      tx.operations.push_back( settle_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (account_to_settle)(amount_to_settle)(symbol)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::issue_asset(string to_account, string amount, string symbol,\n         string memo, bool broadcast )\n   {\n      auto asset_obj = get_asset(symbol);\n\n      account_object to = get_account(to_account);\n      account_object issuer = get_account(asset_obj.issuer);\n\n      asset_issue_operation issue_op;\n      issue_op.issuer           = asset_obj.issuer;\n      issue_op.asset_to_issue   = asset_obj.amount_from_string(amount);\n      issue_op.issue_to_account = to.id;\n\n      if( memo.size() )\n      {\n         issue_op.memo = memo_data();\n         issue_op.memo->from = issuer.options.memo_key;\n         issue_op.memo->to = to.options.memo_key;\n         issue_op.memo->set_message(get_private_key(issuer.options.memo_key),\n                                    to.options.memo_key, memo);\n      }\n\n      signed_transaction tx;\n      tx.operations.push_back(issue_op);\n      set_operation_fees(tx,_remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction(tx, broadcast);\n   }\n\n   signed_transaction wallet_api_impl::bid_collateral(string bidder_name, string debt_amount, string debt_symbol,\n         string additional_collateral, bool broadcast )\n   { try {\n      optional<asset_object> debt_asset = find_asset(debt_symbol);\n      if (!debt_asset)\n        FC_THROW(\"No asset with that symbol exists!\");\n\n      FC_ASSERT(debt_asset->bitasset_data_id.valid(), \"Not a bitasset, bidding not possible.\");\n      const asset_object& collateral =\n            get_asset(get_object(*debt_asset->bitasset_data_id).options.short_backing_asset);\n\n      bid_collateral_operation op;\n      op.bidder = get_account_id(bidder_name);\n      op.debt_covered = debt_asset->amount_from_string(debt_amount);\n      op.additional_collateral = collateral.amount_from_string(additional_collateral);\n\n      signed_transaction tx;\n      tx.operations.push_back( op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (bidder_name)(debt_amount)(debt_symbol)(additional_collateral)(broadcast) ) }\n\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_builder.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"wallet_api_impl.hpp\"\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   transaction_handle_type wallet_api_impl::begin_builder_transaction()\n   {\n      transaction_handle_type trx_handle = _builder_transactions.empty() ? 0\n                                                    : (--_builder_transactions.end())->first + 1;\n      _builder_transactions[trx_handle] = {}; // Reset if exists already\n      return trx_handle;\n   }\n\n   void wallet_api_impl::add_operation_to_builder_transaction(transaction_handle_type transaction_handle,\n         const operation& op)\n   {\n      FC_ASSERT(_builder_transactions.count(transaction_handle) > 0);\n      _builder_transactions[transaction_handle].operations.emplace_back(op);\n   }\n\n   void wallet_api_impl::replace_operation_in_builder_transaction(transaction_handle_type handle,\n         uint32_t operation_index, const operation& new_op)\n   {\n      FC_ASSERT(_builder_transactions.count(handle) > 0);\n      signed_transaction& trx = _builder_transactions[handle];\n      FC_ASSERT( operation_index < trx.operations.size());\n      trx.operations[operation_index] = new_op;\n   }\n\n   asset wallet_api_impl::set_fees_on_builder_transaction(transaction_handle_type handle, string fee_asset)\n   {\n      FC_ASSERT(_builder_transactions.count(handle) > 0);\n\n      auto fee_asset_obj = get_asset(fee_asset);\n      asset total_fee = fee_asset_obj.amount(0);\n\n      auto gprops = _remote_db->get_global_properties().parameters;\n      if( fee_asset_obj.get_id() != asset_id_type() )\n      {\n         for( auto& op : _builder_transactions[handle].operations )\n            total_fee += gprops.get_current_fees().set_fee( op, fee_asset_obj.options.core_exchange_rate );\n\n         FC_ASSERT((total_fee * fee_asset_obj.options.core_exchange_rate).amount <=\n                   get_object(fee_asset_obj.dynamic_asset_data_id).fee_pool,\n                   \"Cannot pay fees in ${asset}, as this asset's fee pool is insufficiently funded.\",\n                   (\"asset\", fee_asset_obj.symbol));\n      } else {\n         for( auto& op : _builder_transactions[handle].operations )\n            total_fee += gprops.get_current_fees().set_fee( op );\n      }\n\n      return total_fee;\n   }\n\n   transaction wallet_api_impl::preview_builder_transaction(transaction_handle_type handle)\n   {\n      FC_ASSERT(_builder_transactions.count(handle) > 0);\n      return _builder_transactions[handle];\n   }\n\n   signed_transaction wallet_api_impl::sign_builder_transaction(transaction_handle_type \n         transaction_handle, bool broadcast )\n   {\n      FC_ASSERT(_builder_transactions.count(transaction_handle) > 0);\n\n      return _builder_transactions[transaction_handle] =\n            sign_transaction(_builder_transactions[transaction_handle], broadcast);\n   }\n\n   signed_transaction wallet_api_impl::sign_builder_transaction2(transaction_handle_type\n         transaction_handle, const vector<public_key_type>& signing_keys, bool broadcast)\n   {\n      FC_ASSERT(_builder_transactions.count(transaction_handle) > 0);\n\n      return _builder_transactions[transaction_handle] =\n            sign_transaction2(_builder_transactions[transaction_handle], signing_keys, broadcast);\n   }\n\n   signed_transaction wallet_api_impl::propose_builder_transaction( transaction_handle_type handle,\n         time_point_sec expiration, uint32_t review_period_seconds, bool broadcast)\n   {\n      return propose_builder_transaction2( handle, \"1.2.0\", expiration, review_period_seconds, broadcast );\n   }\n\n   signed_transaction wallet_api_impl::propose_builder_transaction2( transaction_handle_type handle,\n      string account_name_or_id, time_point_sec expiration, uint32_t review_period_seconds, bool broadcast )\n   {\n      FC_ASSERT(_builder_transactions.count(handle) > 0);\n      proposal_create_operation pcop;\n      pcop.fee_paying_account = get_account(account_name_or_id).get_id();\n      pcop.expiration_time = expiration;\n      signed_transaction& trx = _builder_transactions[handle];\n      std::transform(trx.operations.begin(), trx.operations.end(), std::back_inserter(pcop.proposed_ops),\n                     [](const operation& op) { return op_wrapper(op); });\n      if( review_period_seconds )\n         pcop.review_period_seconds = review_period_seconds;\n      trx.operations = {pcop};\n      _remote_db->get_global_properties().parameters.get_current_fees().set_fee( trx.operations.front() );\n\n      return trx = sign_transaction(trx, broadcast);\n   }\n\n   void wallet_api_impl::remove_builder_transaction(transaction_handle_type handle)\n   {\n      _builder_transactions.erase(handle);\n   }\n\n}}} // namespace graphene::wallet::detail1\n"
  },
  {
    "path": "libraries/wallet/wallet_debug.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"wallet_api_impl.hpp\"\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   void wallet_api_impl::dbg_make_uia(string creator, string symbol)\n   {\n      asset_options opts;\n      opts.flags &= (uint16_t)( ~(white_list | disable_force_settle | global_settle) );\n      opts.issuer_permissions = opts.flags;\n      opts.core_exchange_rate = price(asset(1), asset(1,asset_id_type(1)));\n      create_asset(get_account(creator).name, symbol, 2, opts, {}, true);\n   }\n\n   void wallet_api_impl::dbg_make_mia(string creator, string symbol)\n   {\n      asset_options opts;\n      opts.flags &= (uint16_t)(~white_list);\n      opts.issuer_permissions = opts.flags;\n      opts.core_exchange_rate = price(asset(1), asset(1,asset_id_type(1)));\n      bitasset_options bopts;\n      create_asset(get_account(creator).name, symbol, 2, opts, bopts, true);\n   }\n\n   void wallet_api_impl::dbg_push_blocks( const std::string& src_filename, uint32_t count )\n   {\n      use_debug_api();\n      (*_remote_debug)->debug_push_blocks( src_filename, count );\n      (*_remote_debug)->debug_stream_json_objects_flush();\n   }\n\n   void wallet_api_impl::dbg_generate_blocks( const std::string& debug_wif_key, uint32_t count )\n   {\n      use_debug_api();\n      (*_remote_debug)->debug_generate_blocks( debug_wif_key, count );\n      (*_remote_debug)->debug_stream_json_objects_flush();\n   }\n\n   void wallet_api_impl::dbg_stream_json_objects( const std::string& filename )\n   {\n      use_debug_api();\n      (*_remote_debug)->debug_stream_json_objects( filename );\n      (*_remote_debug)->debug_stream_json_objects_flush();\n   }\n\n   void wallet_api_impl::dbg_update_object( const fc::variant_object& update )\n   {\n      use_debug_api();\n      (*_remote_debug)->debug_update_object( update );\n      (*_remote_debug)->debug_stream_json_objects_flush();\n   }\n\n   void wallet_api_impl::use_debug_api()\n   {\n      if( _remote_debug )\n         return;\n      try\n      {\n        _remote_debug = _remote_api->debug();\n      }\n      catch( const fc::exception& e )\n      {\n         std::cerr << \"\\nCouldn't get debug node API.  You probably are not configured\\n\"\n         \"to access the debug API on the node you are connecting to.\\n\"\n         \"\\n\"\n         \"To fix this problem:\\n\"\n         \"- Please ensure you are running debug_node, not witness_node.\\n\"\n         \"- Please follow the instructions in README.md to set up an apiaccess file.\\n\"\n         \"\\n\";\n      }\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_network.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"wallet_api_impl.hpp\"\n\n/****\n * Methods to handle network / debug / debug_node\n */\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   void wallet_api_impl::network_add_nodes( const vector<string>& nodes )\n   {\n      use_network_node_api();\n      for( const string& node_address : nodes )\n      {\n         (*_remote_net_node)->add_node( fc::ip::endpoint::from_string( node_address ) );\n      }\n   }\n\n   vector< variant > wallet_api_impl::network_get_connected_peers()\n   {\n      use_network_node_api();\n      const auto peers = (*_remote_net_node)->get_connected_peers();\n      vector< variant > result;\n      result.reserve( peers.size() );\n      for( const auto& peer : peers )\n      {\n         variant v;\n         fc::to_variant( peer, v, GRAPHENE_MAX_NESTED_OBJECTS );\n         result.push_back( v );\n      }\n      return result;\n   }\n\n   void wallet_api_impl::flood_network(string prefix, uint32_t number_of_transactions)\n   {\n      try\n      {\n         const account_object& master = *_wallet.my_accounts.get<by_name>().lower_bound(\"import\");\n         int number_of_accounts = number_of_transactions / 3;\n         number_of_transactions -= number_of_accounts;\n         try {\n            dbg_make_uia(master.name, \"SHILL\");\n         } catch(...) {/* Ignore; the asset probably already exists.*/}\n\n         fc::time_point start = fc::time_point::now();\n         for( int i = 0; i < number_of_accounts; ++i )\n         {\n            std::ostringstream brain_key;\n            brain_key << \"brain key for account \" << prefix << i;\n            signed_transaction trx = create_account_with_brain_key(\n                  brain_key.str(), prefix + fc::to_string(i), master.name, master.name,\n                  /* broadcast = */ true, /* save wallet = */ false);\n         }\n         fc::time_point end = fc::time_point::now();\n         ilog(\"Created ${n} accounts in ${time} milliseconds\",\n              (\"n\", number_of_accounts)(\"time\", (end - start).count() / 1000));\n\n         start = fc::time_point::now();\n         for( int i = 0; i < number_of_accounts; ++i )\n         {\n            signed_transaction trx = transfer(master.name, prefix + fc::to_string(i), \"10\", \"CORE\", \"\", true);\n            trx = transfer(master.name, prefix + fc::to_string(i), \"1\", \"CORE\", \"\", true);\n         }\n         end = fc::time_point::now();\n         ilog(\"Transferred to ${n} accounts in ${time} milliseconds\",\n              (\"n\", number_of_accounts*2)(\"time\", (end - start).count() / 1000));\n\n         start = fc::time_point::now();\n         for( int i = 0; i < number_of_accounts; ++i )\n         {\n            signed_transaction trx = issue_asset(prefix + fc::to_string(i), \"1000\", \"SHILL\", \"\", true);\n         }\n         end = fc::time_point::now();\n         ilog(\"Issued to ${n} accounts in ${time} milliseconds\",\n              (\"n\", number_of_accounts)(\"time\", (end - start).count() / 1000));\n      }\n      catch (...)\n      {\n         throw;\n      }\n\n   }\n\n   pair<transaction_id_type,signed_transaction> wallet_api_impl::broadcast_transaction(signed_transaction tx)\n   {\n       try {\n           _remote_net_broadcast->broadcast_transaction(tx);\n       }\n       catch (const fc::exception& e) {\n           elog(\"Caught exception while broadcasting tx ${id}:  ${e}\",\n                (\"id\", tx.id().str())(\"e\", e.to_detail_string()));\n           throw;\n       }\n       return std::make_pair(tx.id(),tx);\n   }   \n\n   void wallet_api_impl::use_network_node_api()\n   {\n      if( _remote_net_node )\n         return;\n      try\n      {\n         _remote_net_node = _remote_api->network_node();\n      }\n      catch( const fc::exception& e )\n      {\n         std::cerr << \"\\nCouldn't get network node API.  You probably are not configured\\n\"\n         \"to access the network API on the witness_node you are\\n\"\n         \"connecting to.  Please follow the instructions in README.md to set up an apiaccess file.\\n\"\n         \"\\n\";\n         throw;\n      }\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_results.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <fc/io/sstream.hpp>\n#include \"wallet_api_impl.hpp\"\n#include \"operation_printer.hpp\"\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   std::map< string, std::function< string( const fc::variant&, const fc::variants& ) >, std::less<> >\n         wallet_api_impl::get_result_formatters() const\n   {\n      std::map< string, std::function< string( const fc::variant&, const fc::variants& ) >, std::less<> > m;\n\n      m[\"help\"] = [](const variant& result, const fc::variants&)\n      {\n         return result.get_string();\n      };\n\n      m[\"gethelp\"] = [](const variant& result, const fc::variants&)\n      {\n         return result.get_string();\n      };\n\n      auto format_account_history = [this](const variant& result, const fc::variants&)\n      {\n         auto r = result.as<vector<operation_detail>>( GRAPHENE_MAX_NESTED_OBJECTS );\n         std::stringstream ss;\n\n         for( operation_detail& d : r )\n         {\n            operation_history_object& i = d.op;\n            ss << i.block_num << \" \";\n            ss << i.block_time.to_iso_string() << \" \";\n            ss << string(i.id) << \" \";\n            i.op.visit(operation_printer(ss, *this, i));\n            ss << \" \\n\";\n         }\n\n         return ss.str();\n      };\n\n      m[\"get_account_history\"] = format_account_history;\n      m[\"get_relative_account_history\"] = format_account_history;\n\n      m[\"get_account_history_by_operations\"] = [this](const variant& result, const fc::variants&) {\n          auto r = result.as<account_history_operation_detail>( GRAPHENE_MAX_NESTED_OBJECTS );\n          std::stringstream ss;\n          ss << \"total_count : \" << r.total_count << \" \\n\";\n          ss << \"result_count : \" << r.result_count << \" \\n\";\n          for (operation_detail_ex& d : r.details) {\n              operation_history_object& i = d.op;\n              ss << i.block_num << \" \";\n              ss << i.block_time.to_iso_string() << \" \";\n              ss << string(i.id) << \" \";\n              i.op.visit(operation_printer(ss, *this, i));\n              ss << \" transaction_id : \";\n              ss << d.transaction_id.str();\n              ss << \" \\n\";\n          }\n\n          return ss.str();\n      };\n\n      auto format_balances = [this](const variant& result, const fc::variants&)\n      {\n         auto r = result.as<vector<asset>>( GRAPHENE_MAX_NESTED_OBJECTS );\n         vector<asset_object> asset_recs;\n         std::transform(r.begin(), r.end(), std::back_inserter(asset_recs), [this](const asset& a) {\n            return get_asset(a.asset_id);\n         });\n\n         std::stringstream ss;\n         for( unsigned i = 0; i < asset_recs.size(); ++i )\n            ss << asset_recs[i].amount_to_pretty_string(r[i]) << \"\\n\";\n\n         return ss.str();\n      };\n\n      m[\"list_account_balances\"] = format_balances;\n      m[\"get_blind_balances\"] = format_balances;\n\n      auto format_blind_transfers  = [this](const variant& result, const fc::variants&)\n      {\n         auto r = result.as<blind_confirmation>( GRAPHENE_MAX_NESTED_OBJECTS );\n         std::stringstream ss;\n         r.trx.operations[0].visit( operation_printer( ss, *this, operation_history_object() ) );\n         ss << \"\\n\";\n         for( const auto& out : r.outputs )\n         {\n            auto a = get_asset( out.decrypted_memo.amount.asset_id );\n            ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << \" to  \" << out.label\n               << \"\\n\\t  receipt: \" << out.confirmation_receipt << \"\\n\\n\";\n         }\n         return ss.str();\n      };\n\n      m[\"transfer_to_blind\"] = format_blind_transfers;\n      m[\"blind_transfer\"] = format_blind_transfers;\n\n      m[\"receive_blind_transfer\"] = [this](const variant& result, const fc::variants&)\n      {\n         auto r = result.as<blind_receipt>( GRAPHENE_MAX_NESTED_OBJECTS );\n         std::stringstream ss;\n         auto as = get_asset( r.amount.asset_id );\n         ss << as.amount_to_pretty_string( r.amount ) << \"  \" << r.from_label << \"  =>  \"\n            << r.to_label  << \"  \" << r.memo <<\"\\n\";\n         return ss.str();\n      };\n\n      m[\"blind_history\"] = [this](const variant& result, const fc::variants&)\n      {\n         auto records = result.as<vector<blind_receipt>>( GRAPHENE_MAX_NESTED_OBJECTS );\n         std::stringstream ss;\n         ss << \"WHEN         \"\n            << \"  \" << \"AMOUNT\"  << \"  \" << \"FROM\" << \"  =>  \" << \"TO\" << \"  \" << \"MEMO\" <<\"\\n\";\n         ss << \"====================================================================================\\n\";\n         for( auto& r : records )\n         {\n            auto as = get_asset( r.amount.asset_id );\n            ss << fc::get_approximate_relative_time_string( r.date )\n               << \"  \" << as.amount_to_pretty_string( r.amount ) << \"  \" << r.from_label << \"  =>  \" << r.to_label\n               << \"  \" << r.memo <<\"\\n\";\n         }\n         return ss.str();\n      };\n\n      m[\"get_order_book\"] = [](const variant& result, const fc::variants&)\n      {\n         auto orders = result.as<order_book>( GRAPHENE_MAX_NESTED_OBJECTS );\n         auto bids = orders.bids;\n         auto asks = orders.asks;\n         std::stringstream ss;\n         std::stringstream sum_stream;\n         sum_stream << \"Sum(\" << orders.base << ')';\n         double bid_sum = 0;\n         double ask_sum = 0;\n         const int spacing = 20;\n\n         auto prettify_num = [&ss]( double n )\n         {\n            if (abs( round( n ) - n ) < 0.00000000001 )\n            {\n               ss << (int) n;\n            }\n            else if (n - floor(n) < 0.000001)\n            {\n               ss << setiosflags( ios::fixed ) << setprecision(10) << n;\n            }\n            else\n            {\n               ss << setiosflags( ios::fixed ) << setprecision(6) << n;\n            }\n         };\n         auto prettify_num_string = [&]( string& num_string )\n         {\n            double n = fc::to_double( num_string );\n            prettify_num( n );\n         };\n\n         ss << setprecision( 8 ) << setiosflags( ios::fixed ) << setiosflags( ios::left );\n\n         ss << ' ' << setw( (spacing * 4) + 6 ) << \"BUY ORDERS\" << \"SELL ORDERS\\n\"\n            << ' ' << setw( spacing + 1 ) << \"Price\" << setw( spacing ) << orders.quote << ' ' << setw( spacing )\n            << orders.base << ' ' << setw( spacing ) << sum_stream.str()\n            << \"   \" << setw( spacing + 1 ) << \"Price\" << setw( spacing ) << orders.quote << ' ' << setw( spacing )\n            << orders.base << ' ' << setw( spacing ) << sum_stream.str()\n            << \"\\n=====================================================================================\"\n            << \"|=====================================================================================\\n\";\n\n         for (unsigned int i = 0; i < bids.size() || i < asks.size() ; i++)\n         {\n            if ( i < bids.size() )\n            {\n                bid_sum += fc::to_double( bids[i].base );\n                ss << ' ' << setw( spacing );\n                prettify_num_string( bids[i].price );\n                ss << ' ' << setw( spacing );\n                prettify_num_string( bids[i].quote );\n                ss << ' ' << setw( spacing );\n                prettify_num_string( bids[i].base );\n                ss << ' ' << setw( spacing );\n                prettify_num( bid_sum );\n                ss << ' ';\n            }\n            else\n            {\n                ss << setw( (spacing * 4) + 5 ) << ' ';\n            }\n\n            ss << '|';\n\n            if ( i < asks.size() )\n            {\n               ask_sum += fc::to_double( asks[i].base );\n               ss << ' ' << setw( spacing );\n               prettify_num_string( asks[i].price );\n               ss << ' ' << setw( spacing );\n               prettify_num_string( asks[i].quote );\n               ss << ' ' << setw( spacing );\n               prettify_num_string( asks[i].base );\n               ss << ' ' << setw( spacing );\n               prettify_num( ask_sum );\n            }\n\n            ss << '\\n';\n         }\n\n         ss << endl\n            << \"Buy Total:  \" << bid_sum << ' ' << orders.base << endl\n            << \"Sell Total: \" << ask_sum << ' ' << orders.base << endl;\n\n         return ss.str();\n      };\n\n      m[\"sign_message\"] = [](const variant& result, const fc::variants&)\n      {\n         auto r = result.as<signed_message>( GRAPHENE_MAX_NESTED_OBJECTS );\n\n         fc::stringstream encapsulated;\n         encapsulated << ENC_HEADER;\n         encapsulated << r.message << '\\n';\n         encapsulated << ENC_META;\n         encapsulated << \"account=\" << r.meta.account << '\\n';\n         encapsulated << \"memokey=\" << std::string( r.meta.memo_key ) << '\\n';\n         encapsulated << \"block=\" << r.meta.block << '\\n';\n         encapsulated << \"timestamp=\" << r.meta.time << '\\n';\n         encapsulated << ENC_SIG;\n         encapsulated << fc::to_hex( (const char*)r.signature->data(), r.signature->size() ) << '\\n';\n         encapsulated << ENC_FOOTER;\n\n         return encapsulated.str();\n      };\n\n      return m;\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_sign.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <fc/crypto/aes.hpp>\n\n#include \"wallet_api_impl.hpp\"\n#include <graphene/wallet/wallet.hpp>\n\n/***\n * These methods handle signing and keys\n */\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   string address_to_shorthash( const graphene::protocol::address& addr )\n   {\n      uint32_t x = addr.addr._hash[0].value();\n      static const char hd[] = \"0123456789abcdef\";\n      string result;\n\n      result += hd[(x >> 0x1c) & 0x0f];\n      result += hd[(x >> 0x18) & 0x0f];\n      result += hd[(x >> 0x14) & 0x0f];\n      result += hd[(x >> 0x10) & 0x0f];\n      result += hd[(x >> 0x0c) & 0x0f];\n      result += hd[(x >> 0x08) & 0x0f];\n      result += hd[(x >> 0x04) & 0x0f];\n      result += hd[(x        ) & 0x0f];\n\n      return result;\n   }\n\n   fc::ecc::private_key derive_private_key( const std::string& prefix_string, int sequence_number )\n   {\n      std::string sequence_string = std::to_string(sequence_number);\n      fc::sha512 h = fc::sha512::hash(prefix_string + \" \" + sequence_string);\n      fc::ecc::private_key derived_key = fc::ecc::private_key::regenerate(fc::sha256::hash(h));\n      return derived_key;\n   }\n\n   string normalize_brain_key( string s )\n   {\n      size_t i = 0, n = s.length();\n      std::string result;\n      char c;\n      result.reserve( n );\n\n      bool preceded_by_whitespace = false;\n      bool non_empty = false;\n      while( i < n )\n      {\n         c = s[i++];\n         switch( c )\n         {\n         case ' ':  case '\\t': case '\\r': case '\\n': case '\\v': case '\\f':\n            preceded_by_whitespace = true;\n            continue;\n\n         case 'a': c = 'A'; break;\n         case 'b': c = 'B'; break;\n         case 'c': c = 'C'; break;\n         case 'd': c = 'D'; break;\n         case 'e': c = 'E'; break;\n         case 'f': c = 'F'; break;\n         case 'g': c = 'G'; break;\n         case 'h': c = 'H'; break;\n         case 'i': c = 'I'; break;\n         case 'j': c = 'J'; break;\n         case 'k': c = 'K'; break;\n         case 'l': c = 'L'; break;\n         case 'm': c = 'M'; break;\n         case 'n': c = 'N'; break;\n         case 'o': c = 'O'; break;\n         case 'p': c = 'P'; break;\n         case 'q': c = 'Q'; break;\n         case 'r': c = 'R'; break;\n         case 's': c = 'S'; break;\n         case 't': c = 'T'; break;\n         case 'u': c = 'U'; break;\n         case 'v': c = 'V'; break;\n         case 'w': c = 'W'; break;\n         case 'x': c = 'X'; break;\n         case 'y': c = 'Y'; break;\n         case 'z': c = 'Z'; break;\n\n         default:\n            break;\n         }\n         if( preceded_by_whitespace && non_empty )\n            result.push_back(' ');\n         result.push_back(c);\n         preceded_by_whitespace = false;\n         non_empty = true;\n      }\n      return result;\n   }\n\n   void wallet_api_impl::encrypt_keys()\n   {\n      if( !is_locked() )\n      {\n         plain_keys data;\n         data.keys = _keys;\n         data.checksum = _checksum;\n         auto plain_txt = fc::raw::pack(data);\n         _wallet.cipher_keys = fc::aes_encrypt( data.checksum, plain_txt );\n      }\n   }\n\n   memo_data wallet_api_impl::sign_memo(string from, string to, string memo)\n   {\n      FC_ASSERT( !self.is_locked() );\n\n      memo_data md = memo_data();\n\n      // get account memo key, if that fails, try a pubkey\n      try {\n         account_object from_account = get_account(from);\n         md.from = from_account.options.memo_key;\n      } catch (const fc::exception&) {\n         // check if the string itself is a pubkey, if not, consider it as a label\n         try {\n            md.from = public_key_type( from );\n         } catch (const fc::exception&) {\n            md.from = self.get_public_key( from );\n         }\n      }\n      // same as above, for destination key\n      try {\n         account_object to_account = get_account(to);\n         md.to = to_account.options.memo_key;\n      } catch (const fc::exception&) {\n         // check if the string itself is a pubkey, if not, consider it as a label\n         try {\n            md.to = public_key_type( to );\n         } catch (const fc::exception&) {\n            md.to = self.get_public_key( to );\n         }\n      }\n\n      // try to get private key of from and sign, if that fails, try to sign with to\n      try {\n         md.set_message(get_private_key(md.from), md.to, memo);\n      } catch (const fc::exception&) {\n         std::swap( md.from, md.to );\n         md.set_message(get_private_key(md.from), md.to, memo);\n         std::swap( md.from, md.to );\n      }\n      return md;\n   }\n\n   string wallet_api_impl::read_memo(const memo_data& md)\n   {\n      FC_ASSERT(!is_locked());\n      std::string clear_text;\n\n      const memo_data *memo = &md;\n\n      try {\n         FC_ASSERT( _keys.count(memo->to) > 0 || _keys.count(memo->from) > 0,\n                    \"Memo is encrypted to a key ${to} or ${from} not in this wallet.\",\n                    (\"to\", memo->to)(\"from\",memo->from) );\n         if( _keys.count(memo->to) > 0 ) {\n            auto my_key = wif_to_key(_keys.at(memo->to));\n            FC_ASSERT(my_key, \"Unable to recover private key to decrypt memo. Wallet may be corrupted.\");\n            clear_text = memo->get_message(*my_key, memo->from);\n         } else {\n            auto my_key = wif_to_key(_keys.at(memo->from));\n            FC_ASSERT(my_key, \"Unable to recover private key to decrypt memo. Wallet may be corrupted.\");\n            clear_text = memo->get_message(*my_key, memo->to);\n         }\n      } catch (const fc::exception& e) {\n         elog(\"Error when decrypting memo: ${e}\", (\"e\", e.to_detail_string()));\n      }\n\n      return clear_text;\n   }\n\n   signed_message wallet_api_impl::sign_message(string signer, string message)\n   {\n      FC_ASSERT( !self.is_locked() );\n\n      const account_object from_account = get_account(signer);\n      auto dynamic_props = get_dynamic_global_properties();\n\n      signed_message msg;\n      msg.message = message;\n      msg.meta.account = from_account.name;\n      msg.meta.memo_key = from_account.options.memo_key;\n      msg.meta.block = dynamic_props.head_block_number;\n      msg.meta.time = dynamic_props.time.to_iso_string() + \"Z\";\n      msg.signature = get_private_key( from_account.options.memo_key ).sign_compact( msg.digest() );\n      return msg;\n   }\n\n   bool wallet_api_impl::verify_message( const string& message, const string& account, int32_t block,\n                                         const string& msg_time, const fc::ecc::compact_signature& sig )\n   {\n      const account_object from_account = get_account( account );\n\n      signed_message msg;\n      msg.message = message;\n      msg.meta.account = from_account.name;\n      msg.meta.memo_key = from_account.options.memo_key;\n      msg.meta.block = block;\n      msg.meta.time = msg_time;\n      msg.signature = sig;\n\n      return verify_signed_message( msg );\n   }\n\n   bool wallet_api_impl::verify_signed_message( const signed_message& message )\n   {\n      if( !message.signature.valid() ) return false;\n\n      const account_object from_account = get_account( message.meta.account );\n\n      const fc::ecc::public_key signer( *message.signature, message.digest() );\n      if( !( message.meta.memo_key == signer ) ) return false;\n      FC_ASSERT( from_account.options.memo_key == signer,\n                 \"Message was signed by contained key, but it doesn't belong to the contained account!\" );\n\n      return true;\n   }\n\n   /* meta contains lines of the form \"key=value\".\n    * Returns the value for the corresponding key, throws if key is not present. */\n   static string meta_extract( const string& meta, const string& key )\n   {\n      FC_ASSERT( meta.size() > key.size(), \"Key '${k}' not found!\", (\"k\",key) );\n      size_t start;\n      if( meta.substr( 0, key.size() ) == key && meta[key.size()] == '=' )\n         start = 0;\n      else\n      {\n         start = meta.find( \"\\n\" + key + \"=\" );\n         FC_ASSERT( start != string::npos, \"Key '${k}' not found!\", (\"k\",key) );\n         ++start;\n      }\n      start += key.size() + 1;\n      size_t lf = meta.find( \"\\n\", start );\n      if( lf == string::npos ) lf = meta.size();\n      return meta.substr( start, lf - start );\n   }\n\n   bool wallet_api_impl::verify_encapsulated_message( const string& message )\n   {\n      signed_message msg;\n      size_t begin_p = message.find( ENC_HEADER );\n      FC_ASSERT( begin_p != string::npos, \"BEGIN MESSAGE line not found!\" );\n      size_t meta_p = message.find( ENC_META, begin_p );\n      FC_ASSERT( meta_p != string::npos, \"BEGIN META line not found!\" );\n      FC_ASSERT( meta_p >= begin_p + ENC_HEADER.size() + 1, \"Missing message!?\" );\n      size_t sig_p = message.find( ENC_SIG, meta_p );\n      FC_ASSERT( sig_p != string::npos, \"BEGIN SIGNATURE line not found!\" );\n      FC_ASSERT( sig_p >= meta_p + ENC_META.size(), \"Missing metadata?!\" );\n      size_t end_p = message.find( ENC_FOOTER, meta_p );\n      FC_ASSERT( end_p != string::npos, \"END MESSAGE line not found!\" );\n      FC_ASSERT( end_p >= sig_p + ENC_SIG.size() + 1, \"Missing signature?!\" );\n\n      msg.message = message.substr( begin_p + ENC_HEADER.size(), meta_p - begin_p - ENC_HEADER.size() - 1 );\n      const string meta = message.substr( meta_p + ENC_META.size(), sig_p - meta_p - ENC_META.size() );\n      const string sig = message.substr( sig_p + ENC_SIG.size(), end_p - sig_p - ENC_SIG.size() - 1 );\n\n      msg.meta.account = meta_extract( meta, \"account\" );\n      msg.meta.memo_key = public_key_type( meta_extract( meta, \"memokey\" ) );\n      msg.meta.block = boost::lexical_cast<uint32_t>( meta_extract( meta, \"block\" ) );\n      msg.meta.time = meta_extract( meta, \"timestamp\" );\n      msg.signature = variant(sig).as< fc::ecc::compact_signature >( 5 );\n\n      return verify_signed_message( msg );\n   }\n\n   signed_transaction wallet_api_impl::add_transaction_signature( signed_transaction tx, \n         bool broadcast )\n   {\n      set<public_key_type> approving_key_set = get_owned_required_keys(tx, false);\n\n      if ( ( ( tx.ref_block_num == 0 && tx.ref_block_prefix == 0 ) ||\n             tx.expiration == fc::time_point_sec() ) &&\n           tx.signatures.empty() )\n      {\n         auto dyn_props = get_dynamic_global_properties();\n         auto parameters = get_global_properties().parameters;\n         fc::time_point_sec now = dyn_props.time;\n         tx.set_reference_block( dyn_props.head_block_id );\n         tx.set_expiration( now + parameters.maximum_time_until_expiration );\n      }\n      for ( const public_key_type &key : approving_key_set )\n         tx.sign( get_private_key( key ), _chain_id );\n\n      if ( broadcast )\n      {\n         try\n         {\n            _remote_net_broadcast->broadcast_transaction( tx );\n         }\n         catch ( const fc::exception &e )\n         {\n            elog( \"Caught exception while broadcasting tx ${id}:  ${e}\",\n                  ( \"id\", tx.id().str() )( \"e\", e.to_detail_string() ) );\n            FC_THROW( \"Caught exception while broadcasting tx\" );\n         }\n      }\n\n      return tx;\n   }\n\n   signed_transaction wallet_api_impl::sign_transaction( signed_transaction tx, bool broadcast )\n   {\n      return sign_transaction2(tx, {}, broadcast);\n   }\n\n   signed_transaction wallet_api_impl::sign_transaction2( signed_transaction tx,\n                                                         const vector<public_key_type>& signing_keys, bool broadcast)\n   {\n      set<public_key_type> approving_key_set = get_owned_required_keys(tx);\n\n      // Add any explicit keys to the approving_key_set\n      for (const public_key_type& explicit_key : signing_keys) {\n         approving_key_set.insert(explicit_key);\n      }\n\n      auto dyn_props = get_dynamic_global_properties();\n      tx.set_reference_block( dyn_props.head_block_id );\n\n      // first, some bookkeeping, expire old items from _recently_generated_transactions\n      // since transactions include the head block id, we just need the index for keeping transactions unique\n      // when there are multiple transactions in the same block.  choose a time period that should be at\n      // least one block long, even in the worst case.  2 minutes ought to be plenty.\n      fc::time_point_sec oldest_transaction_ids_to_track(dyn_props.time - fc::minutes(2));\n      auto oldest_transaction_record_iter =\n            _recently_generated_transactions.get<timestamp_index>().lower_bound(oldest_transaction_ids_to_track);\n      auto begin_iter = _recently_generated_transactions.get<timestamp_index>().begin();\n      _recently_generated_transactions.get<timestamp_index>().erase(begin_iter, oldest_transaction_record_iter);\n\n      uint32_t expiration_time_offset = 0;\n      for (;;)\n      {\n         tx.set_expiration( dyn_props.time + fc::seconds(30 + expiration_time_offset) );\n         tx.clear_signatures();\n\n         for( const public_key_type& key : approving_key_set )\n            tx.sign( get_private_key(key), _chain_id );\n\n         graphene::chain::transaction_id_type this_transaction_id = tx.id();\n         auto iter = _recently_generated_transactions.find(this_transaction_id);\n         if (iter == _recently_generated_transactions.end())\n         {\n            // we haven't generated this transaction before, the usual case\n            recently_generated_transaction_record this_transaction_record;\n            this_transaction_record.generation_time = dyn_props.time;\n            this_transaction_record.transaction_id = this_transaction_id;\n            _recently_generated_transactions.insert(this_transaction_record);\n            break;\n         }\n\n         // else we've generated a dupe, increment expiration time and re-sign it\n         ++expiration_time_offset;\n      }\n\n      if( broadcast )\n      {\n         try\n         {\n            _remote_net_broadcast->broadcast_transaction( tx );\n         }\n         catch (const fc::exception& e)\n         {\n            elog(\"Caught exception while broadcasting tx ${id}:  ${e}\",\n                 (\"id\", tx.id().str())(\"e\", e.to_detail_string()) );\n            throw;\n         }\n      }\n\n      return tx;\n   }\n\n   fc::ecc::private_key wallet_api_impl::get_private_key(const public_key_type& id)const\n   {\n      auto it = _keys.find(id);\n      FC_ASSERT( it != _keys.end() );\n\n      fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second );\n      FC_ASSERT( privkey );\n      return *privkey;\n   }\n\n   fc::ecc::private_key wallet_api_impl::get_private_key_for_account(const account_object& account)const\n   {\n      vector<public_key_type> active_keys = account.active.get_keys();\n      if (active_keys.size() != 1)\n         FC_THROW(\"Expecting a simple authority with one active key\");\n      return get_private_key(active_keys.front());\n   }\n\n   // imports the private key into the wallet, and associate it in some way (?) with the\n   // given account name.\n   // @returns true if the key matches a current active/owner/memo key for the named\n   //          account, false otherwise (but it is stored either way)\n   bool wallet_api_impl::import_key(string account_name_or_id, string wif_key)\n   {\n      fc::optional<fc::ecc::private_key> optional_private_key = wif_to_key(wif_key);\n      if (!optional_private_key)\n         FC_THROW(\"Invalid private key\");\n      graphene::chain::public_key_type wif_pub_key = optional_private_key->get_public_key();\n\n      account_object account = get_account( account_name_or_id );\n\n      // make a list of all current public keys for the named account\n      flat_set<public_key_type> all_keys_for_account;\n      std::vector<public_key_type> active_keys = account.active.get_keys();\n      std::vector<public_key_type> owner_keys = account.owner.get_keys();\n      std::copy(active_keys.begin(), active_keys.end(),\n            std::inserter(all_keys_for_account, all_keys_for_account.end()));\n      std::copy(owner_keys.begin(), owner_keys.end(),\n            std::inserter(all_keys_for_account, all_keys_for_account.end()));\n      all_keys_for_account.insert(account.options.memo_key);\n\n      _keys[wif_pub_key] = wif_key;\n\n      _wallet.update_account(account);\n\n      _wallet.extra_keys[account.get_id()].insert(wif_pub_key);\n\n      return all_keys_for_account.find(wif_pub_key) != all_keys_for_account.end();\n   }\n\n   /**\n    * Get the required public keys to sign the transaction which had been\n    * owned by us\n    *\n    * NOTE, if `erase_existing_sigs` set to true, the original trasaction's\n    * signatures will be erased\n    *\n    * @param tx           The transaction to be signed\n    * @param erase_existing_sigs\n    *        The transaction could have been partially signed already,\n    *        if set to false, the corresponding public key of existing\n    *        signatures won't be returned.\n    *        If set to true, the existing signatures will be erased and\n    *        all required keys returned.\n   */\n   set<public_key_type> wallet_api_impl::get_owned_required_keys( signed_transaction &tx,\n         bool erase_existing_sigs )\n   {\n      set<public_key_type> pks = _remote_db->get_potential_signatures( tx );\n      flat_set<public_key_type> owned_keys;\n      owned_keys.reserve( pks.size() );\n      std::copy_if( pks.begin(), pks.end(),\n                    std::inserter( owned_keys, owned_keys.end() ),\n                    [this]( const public_key_type &pk ) {\n                       return _keys.find( pk ) != _keys.end();\n                    } );\n\n      if ( erase_existing_sigs )\n         tx.signatures.clear();\n\n      return _remote_db->get_required_signatures( tx, owned_keys );\n   }\n\n   flat_set<public_key_type> wallet_api_impl::get_transaction_signers(const signed_transaction &tx) const\n   {\n      return tx.get_signature_keys(_chain_id);\n   }\n\n   signed_transaction wallet_api_impl::approve_proposal( const string& fee_paying_account, const string& proposal_id,\n         const approval_delta& delta, bool broadcast )\n   {\n      proposal_update_operation update_op;\n\n      update_op.fee_paying_account = get_account(fee_paying_account).id;\n      update_op.proposal = fc::variant(proposal_id, 1).as<proposal_id_type>( 1 );\n      // make sure the proposal exists\n      get_object( update_op.proposal );\n\n      for( const std::string& name : delta.active_approvals_to_add )\n         update_op.active_approvals_to_add.insert( get_account( name ).get_id() );\n      for( const std::string& name : delta.active_approvals_to_remove )\n         update_op.active_approvals_to_remove.insert( get_account( name ).get_id() );\n      for( const std::string& name : delta.owner_approvals_to_add )\n         update_op.owner_approvals_to_add.insert( get_account( name ).get_id() );\n      for( const std::string& name : delta.owner_approvals_to_remove )\n         update_op.owner_approvals_to_remove.insert( get_account( name ).get_id() );\n      for( const std::string& k : delta.key_approvals_to_add )\n         update_op.key_approvals_to_add.insert( public_key_type( k ) );\n      for( const std::string& k : delta.key_approvals_to_remove )\n         update_op.key_approvals_to_remove.insert( public_key_type( k ) );\n\n      signed_transaction tx;\n      tx.operations.push_back(update_op);\n      set_operation_fees(tx, get_global_properties().parameters.get_current_fees());\n      tx.validate();\n      return sign_transaction(tx, broadcast);\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_transfer.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"wallet_api_impl.hpp\"\n#include <graphene/wallet/wallet.hpp>\n\n/***\n * Methods to handle transfers / exchange orders\n */\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   /***\n    * @brief a helper method to create an htlc_hash from an algo/hash combination\n    */\n   htlc_hash wallet_api_impl::do_hash( const string& algorithm, const std::string& hash )\n   {\n      string name_upper;\n      std::transform( algorithm.begin(), algorithm.end(), std::back_inserter(name_upper), ::toupper);\n      if( name_upper == \"RIPEMD160\" )\n         return fc::ripemd160( hash );\n      if( name_upper == \"SHA256\" )\n         return fc::sha256( hash );\n      if( name_upper == \"SHA1\" )\n         return fc::sha1( hash );\n      if( name_upper == \"HASH160\" )\n         return fc::hash160( hash );\n      FC_THROW_EXCEPTION( fc::invalid_arg_exception, \"Unknown algorithm '${a}'\", (\"a\",algorithm) );\n   }\n\n   signed_transaction wallet_api_impl::transfer(string from, string to, string amount,\n                               string asset_symbol, string memo, bool broadcast )\n   { try {\n      FC_ASSERT( !self.is_locked() );\n      fc::optional<asset_object> asset_obj = get_asset(asset_symbol);\n      FC_ASSERT(asset_obj, \"Could not find asset matching ${asset}\", (\"asset\", asset_symbol));\n\n      account_object from_account = get_account(from);\n      account_object to_account = get_account(to);\n      account_id_type from_id = from_account.get_id();\n      account_id_type to_id = to_account.get_id();\n\n      transfer_operation xfer_op;\n\n      xfer_op.from = from_id;\n      xfer_op.to = to_id;\n      xfer_op.amount = asset_obj->amount_from_string(amount);\n\n      if( memo.size() )\n         {\n            xfer_op.memo = memo_data();\n            xfer_op.memo->from = from_account.options.memo_key;\n            xfer_op.memo->to = to_account.options.memo_key;\n            xfer_op.memo->set_message(get_private_key(from_account.options.memo_key),\n                                      to_account.options.memo_key, memo);\n         }\n\n      signed_transaction tx;\n      tx.operations.push_back(xfer_op);\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction(tx, broadcast);\n   } FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::htlc_create( const string& source, const string& destination,\n         const string& amount, const string& asset_symbol, const string& hash_algorithm,\n         const string& preimage_hash, uint32_t preimage_size,\n         uint32_t claim_period_seconds, const string& memo, bool broadcast )\n   {\n      try\n      {\n         FC_ASSERT( !self.is_locked() );\n         fc::optional<asset_object> asset_obj = get_asset(asset_symbol);\n         FC_ASSERT(asset_obj, \"Could not find asset matching ${asset}\", (\"asset\", asset_symbol));\n\n         const account_object& from_acct = get_account(source);\n         const account_object& to_acct = get_account(destination);\n         htlc_create_operation create_op;\n         create_op.from = get_account(source).id;\n         create_op.to = get_account(destination).id;\n         create_op.amount = asset_obj->amount_from_string(amount);\n         create_op.claim_period_seconds = claim_period_seconds;\n         create_op.preimage_hash = do_hash( hash_algorithm, preimage_hash );\n         create_op.preimage_size = preimage_size;\n         if (!memo.empty())\n         {\n            memo_data data;\n            data.from = from_acct.options.memo_key;\n            data.to = to_acct.options.memo_key;\n            data.set_message( \n                  get_private_key(from_acct.options.memo_key), to_acct.options.memo_key, memo);\n            create_op.extensions.value.memo = data;\n         }\n\n         signed_transaction tx;\n         tx.operations.push_back(create_op);\n         set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n         tx.validate();\n\n         return sign_transaction(tx, broadcast);\n      } FC_CAPTURE_AND_RETHROW( (source)(destination)(amount)(asset_symbol)(hash_algorithm)\n            (preimage_hash)(preimage_size)(claim_period_seconds)(broadcast) )\n   }\n\n   signed_transaction wallet_api_impl::htlc_redeem( const htlc_id_type& htlc_id, const string& issuer,\n         const std::vector<char>& preimage, bool broadcast )\n   {\n      try\n      {\n         FC_ASSERT( !self.is_locked() );\n         fc::optional<htlc_object> htlc_obj = get_htlc(htlc_id);\n         FC_ASSERT(htlc_obj, \"Could not find HTLC matching ${htlc}\", (\"htlc\", htlc_id));\n\n         account_object issuer_obj = get_account(issuer);\n\n         htlc_redeem_operation update_op;\n         update_op.htlc_id = htlc_obj->id;\n         update_op.redeemer = issuer_obj.id;\n         update_op.preimage = preimage;\n\n         signed_transaction tx;\n         tx.operations.push_back(update_op);\n         set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n         tx.validate();\n\n         return sign_transaction(tx, broadcast);\n      } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(preimage)(broadcast) )\n   }\n\n   signed_transaction wallet_api_impl::htlc_extend( const htlc_id_type& htlc_id, const string& issuer,\n         uint32_t seconds_to_add, bool broadcast )\n   {\n      try\n      {\n         FC_ASSERT( !self.is_locked() );\n         fc::optional<htlc_object> htlc_obj = get_htlc(htlc_id);\n         FC_ASSERT(htlc_obj, \"Could not find HTLC matching ${htlc}\", (\"htlc\", htlc_id));\n\n         account_object issuer_obj = get_account(issuer);\n\n         htlc_extend_operation update_op;\n         update_op.htlc_id = htlc_obj->id;\n         update_op.update_issuer = issuer_obj.id;\n         update_op.seconds_to_add = seconds_to_add;\n\n         signed_transaction tx;\n         tx.operations.push_back(update_op);\n         set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n         tx.validate();\n\n         return sign_transaction(tx, broadcast);\n      } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(seconds_to_add)(broadcast) )\n   }\n\n   fc::optional<htlc_object> wallet_api_impl::get_htlc(const htlc_id_type& htlc_id) const\n   {\n      auto obj = _remote_db->get_objects( { object_id_type(htlc_id) }, {}).front();\n      if ( !obj.is_null() )\n      {\n         return fc::optional<htlc_object>(obj.template as<htlc_object>(GRAPHENE_MAX_NESTED_OBJECTS));\n      }\n      return fc::optional<htlc_object>();\n   }\n\n   signed_transaction wallet_api_impl::sell_asset(string seller_account, string amount_to_sell,\n         string symbol_to_sell, string min_to_receive, string symbol_to_receive,\n         uint32_t timeout_sec, bool fill_or_kill, bool broadcast )\n   {\n      account_object seller   = get_account( seller_account );\n\n      limit_order_create_operation op;\n      op.seller = seller.id;\n      op.amount_to_sell = get_asset(symbol_to_sell).amount_from_string(amount_to_sell);\n      op.min_to_receive = get_asset(symbol_to_receive).amount_from_string(min_to_receive);\n      if( timeout_sec )\n         op.expiration = fc::time_point::now() + fc::seconds(timeout_sec);\n      op.fill_or_kill = fill_or_kill;\n\n      signed_transaction tx;\n      tx.operations.push_back(op);\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   }\n\n   signed_transaction wallet_api_impl::borrow_asset_ext( string seller_name, string amount_to_borrow, \n         string asset_symbol, string amount_of_collateral,\n         call_order_update_operation::extensions_type extensions, bool broadcast )\n   {\n      account_object seller = get_account(seller_name);\n      auto mia = get_asset(asset_symbol);\n      FC_ASSERT(mia.is_market_issued());\n      auto collateral = get_asset(get_object(*mia.bitasset_data_id).options.short_backing_asset);\n\n      call_order_update_operation op;\n      op.funding_account = seller.id;\n      op.delta_debt   = mia.amount_from_string(amount_to_borrow);\n      op.delta_collateral = collateral.amount_from_string(amount_of_collateral);\n      op.extensions = extensions;\n\n      signed_transaction trx;\n      trx.operations = {op};\n      set_operation_fees( trx, _remote_db->get_global_properties().parameters.get_current_fees());\n      trx.validate();\n\n      return sign_transaction(trx, broadcast);\n   }\n\n   signed_transaction wallet_api_impl::cancel_order(const limit_order_id_type& order_id, bool broadcast )\n   { try {\n         FC_ASSERT(!is_locked());\n         signed_transaction trx;\n\n         limit_order_cancel_operation op;\n         op.fee_paying_account = get_object(order_id).seller;\n         op.order = order_id;\n         trx.operations = {op};\n         set_operation_fees( trx, _remote_db->get_global_properties().parameters.get_current_fees());\n\n         trx.validate();\n         return sign_transaction(trx, broadcast);\n   } FC_CAPTURE_AND_RETHROW((order_id)) }\n\n   signed_transaction wallet_api_impl::withdraw_vesting( string witness_name, string amount, string asset_symbol,\n         bool broadcast )\n   { try {\n      auto asset_obj = get_asset( asset_symbol );\n      fc::optional<vesting_balance_id_type> vbid = maybe_id<vesting_balance_id_type>(witness_name);\n      if( !vbid )\n      {\n         witness_object wit = get_witness( witness_name );\n         FC_ASSERT( wit.pay_vb );\n         vbid = wit.pay_vb;\n      }\n\n      vesting_balance_object vbo = get_object( *vbid );\n      vesting_balance_withdraw_operation vesting_balance_withdraw_op;\n\n      vesting_balance_withdraw_op.vesting_balance = *vbid;\n      vesting_balance_withdraw_op.owner = vbo.owner;\n      vesting_balance_withdraw_op.amount = asset_obj.amount_from_string(amount);\n\n      signed_transaction tx;\n      tx.operations.push_back( vesting_balance_withdraw_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (witness_name)(amount) )\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "libraries/wallet/wallet_voting.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"wallet_api_impl.hpp\"\n#include <graphene/utilities/key_conversion.hpp>\n\n/*\n * Methods to handle voting / workers / committee\n */\n\nnamespace graphene { namespace wallet { namespace detail {\n\n   template<typename WorkerInit>\n   static WorkerInit _create_worker_initializer( const variant& worker_settings )\n   {\n      WorkerInit result;\n      from_variant( worker_settings, result, GRAPHENE_MAX_NESTED_OBJECTS );\n      return result;\n   }\n\n   signed_transaction wallet_api_impl::update_worker_votes(\n      string account,\n      worker_vote_delta delta,\n      bool broadcast\n      )\n   {\n      account_object acct = get_account( account );\n\n      // you could probably use a faster algorithm for this, but flat_set is fast enough :)\n      flat_set< worker_id_type > merged;\n      merged.reserve( delta.vote_for.size() + delta.vote_against.size() + delta.vote_abstain.size() );\n      for( const worker_id_type& wid : delta.vote_for )\n      {\n         bool inserted = merged.insert( wid ).second;\n         FC_ASSERT( inserted, \"worker ${wid} specified multiple times\", (\"wid\", wid) );\n      }\n      for( const worker_id_type& wid : delta.vote_against )\n      {\n         bool inserted = merged.insert( wid ).second;\n         FC_ASSERT( inserted, \"worker ${wid} specified multiple times\", (\"wid\", wid) );\n      }\n      for( const worker_id_type& wid : delta.vote_abstain )\n      {\n         bool inserted = merged.insert( wid ).second;\n         FC_ASSERT( inserted, \"worker ${wid} specified multiple times\", (\"wid\", wid) );\n      }\n\n      // should be enforced by FC_ASSERT's above\n      assert( merged.size() == delta.vote_for.size() + delta.vote_against.size() + delta.vote_abstain.size() );\n\n      vector< object_id_type > query_ids;\n      for( const worker_id_type& wid : merged )\n         query_ids.push_back( object_id_type(wid) );\n\n      flat_set<vote_id_type> new_votes( acct.options.votes );\n\n      fc::variants objects = _remote_db->get_objects( query_ids, {} );\n      for( const variant& obj : objects )\n      {\n         worker_object wo;\n         worker_id_type wo_id { wo.id };\n         from_variant( obj, wo, GRAPHENE_MAX_NESTED_OBJECTS );\n         new_votes.erase( wo.vote_for );\n         new_votes.erase( wo.vote_against );\n         if( delta.vote_for.find( wo_id ) != delta.vote_for.end() )\n            new_votes.insert( wo.vote_for );\n         else if( delta.vote_against.find( wo_id ) != delta.vote_against.end() )\n            new_votes.insert( wo.vote_against );\n         else\n            assert( delta.vote_abstain.find( wo_id ) != delta.vote_abstain.end() );\n      }\n\n      account_update_operation update_op;\n      update_op.account = acct.id;\n      update_op.new_options = acct.options;\n      update_op.new_options->votes = new_votes;\n\n      signed_transaction tx;\n      tx.operations.push_back( update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   }\n\n   signed_transaction wallet_api_impl::create_committee_member(string owner_account, string url,\n         bool broadcast )\n   { try {\n\n      committee_member_create_operation committee_member_create_op;\n      committee_member_create_op.committee_member_account = get_account_id(owner_account);\n      committee_member_create_op.url = url;\n      if (_remote_db->get_committee_member_by_account(owner_account))\n         FC_THROW(\"Account ${owner_account} is already a committee_member\", (\"owner_account\", owner_account));\n\n      signed_transaction tx;\n      tx.operations.push_back( committee_member_create_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) }\n\n   witness_object wallet_api_impl::get_witness(string owner_account)\n   {\n      try\n      {\n         fc::optional<witness_id_type> witness_id = maybe_id<witness_id_type>(owner_account);\n         if (witness_id)\n         {\n            std::vector<witness_id_type> ids_to_get;\n            ids_to_get.push_back(*witness_id);\n            std::vector<fc::optional<witness_object>> witness_objects = _remote_db->get_witnesses(ids_to_get);\n            if (witness_objects.front())\n               return *witness_objects.front();\n            FC_THROW(\"No witness is registered for id ${id}\", (\"id\", owner_account));\n         }\n         else\n         {\n            // then maybe it's the owner account\n            try\n            {\n               auto owner_account_id = std::string(get_account_id(owner_account));\n               fc::optional<witness_object> witness = _remote_db->get_witness_by_account(owner_account_id);\n               if (witness)\n                  return *witness;\n               else\n                  FC_THROW(\"No witness is registered for account ${account}\", (\"account\", owner_account));\n            }\n            catch (const fc::exception&)\n            {\n               FC_THROW(\"No account or witness named ${account}\", (\"account\", owner_account));\n            }\n         }\n      }\n      FC_CAPTURE_AND_RETHROW( (owner_account) )\n   }\n\n   committee_member_object wallet_api_impl::get_committee_member(string owner_account)\n   {\n      try\n      {\n         fc::optional<committee_member_id_type> committee_member_id =\n               maybe_id<committee_member_id_type>(owner_account);\n         if (committee_member_id)\n         {\n            std::vector<committee_member_id_type> ids_to_get;\n            ids_to_get.push_back(*committee_member_id);\n            std::vector<fc::optional<committee_member_object>> committee_member_objects =\n                  _remote_db->get_committee_members(ids_to_get);\n            if (committee_member_objects.front())\n               return *committee_member_objects.front();\n            FC_THROW(\"No committee_member is registered for id ${id}\", (\"id\", owner_account));\n         }\n         else\n         {\n            // then maybe it's the owner account\n            try\n            {\n               fc::optional<committee_member_object> committee_member =\n                     _remote_db->get_committee_member_by_account(owner_account);\n               if (committee_member)\n                  return *committee_member;\n               else\n                  FC_THROW(\"No committee_member is registered for account ${account}\", (\"account\", owner_account));\n            }\n            catch (const fc::exception&)\n            {\n               FC_THROW(\"No account or committee_member named ${account}\", (\"account\", owner_account));\n            }\n         }\n      }\n      FC_CAPTURE_AND_RETHROW( (owner_account) )\n   }\n\n   signed_transaction wallet_api_impl::create_witness(string owner_account,\n         string url, bool broadcast /* = false */)\n   { try {\n      account_object witness_account = get_account(owner_account);\n      fc::ecc::private_key active_private_key = get_private_key_for_account(witness_account);\n      int witness_key_index = find_first_unused_derived_key_index(active_private_key);\n      fc::ecc::private_key witness_private_key =\n            derive_private_key(key_to_wif(active_private_key), witness_key_index);\n      graphene::chain::public_key_type witness_public_key = witness_private_key.get_public_key();\n\n      witness_create_operation witness_create_op;\n      witness_create_op.witness_account = witness_account.id;\n      witness_create_op.block_signing_key = witness_public_key;\n      witness_create_op.url = url;\n\n      if (_remote_db->get_witness_by_account(std::string(witness_create_op.witness_account)))\n         FC_THROW(\"Account ${owner_account} is already a witness\", (\"owner_account\", owner_account));\n\n      signed_transaction tx;\n      tx.operations.push_back( witness_create_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      _wallet.pending_witness_registrations[owner_account] = key_to_wif(witness_private_key);\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::update_witness(string witness_name, string url,\n         string block_signing_key, bool broadcast )\n   { try {\n      witness_object witness = get_witness(witness_name);\n      account_object witness_account = get_account( witness.witness_account );\n\n      witness_update_operation witness_update_op;\n      witness_update_op.witness = witness.id;\n      witness_update_op.witness_account = witness_account.id;\n      if( url != \"\" )\n         witness_update_op.new_url = url;\n      if( block_signing_key != \"\" )\n         witness_update_op.new_signing_key = public_key_type( block_signing_key );\n\n      signed_transaction tx;\n      tx.operations.push_back( witness_update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (witness_name)(url)(block_signing_key)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::create_worker( string owner_account, time_point_sec work_begin_date,\n         time_point_sec work_end_date, share_type daily_pay, string name, string url,\n         variant worker_settings, bool broadcast)\n   {\n      worker_initializer init;\n      std::string wtype = worker_settings[\"type\"].get_string();\n\n      // TODO:  Use introspection to do this dispatch\n      if( wtype == \"burn\" )\n         init = _create_worker_initializer< burn_worker_initializer >( worker_settings );\n      else if( wtype == \"refund\" )\n         init = _create_worker_initializer< refund_worker_initializer >( worker_settings );\n      else if( wtype == \"vesting\" )\n         init = _create_worker_initializer< vesting_balance_worker_initializer >( worker_settings );\n      else\n      {\n         FC_ASSERT( false, \"unknown worker[\\\"type\\\"] value\" );\n      }\n\n      worker_create_operation op;\n      op.owner = get_account( owner_account ).id;\n      op.work_begin_date = work_begin_date;\n      op.work_end_date = work_end_date;\n      op.daily_pay = daily_pay;\n      op.name = name;\n      op.url = url;\n      op.initializer = init;\n\n      signed_transaction tx;\n      tx.operations.push_back( op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() );\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   }\n\n   signed_transaction wallet_api_impl::vote_for_committee_member(string voting_account,\n         string committee_member, bool approve, bool broadcast )\n   { try {\n      account_object voting_account_object = get_account(voting_account);\n      fc::optional<committee_member_object> committee_member_obj =\n            _remote_db->get_committee_member_by_account(committee_member);\n      if (!committee_member_obj)\n         FC_THROW(\"Account ${committee_member} is not registered as a committee_member\",\n                  (\"committee_member\", committee_member));\n      if (approve)\n      {\n         auto insert_result = voting_account_object.options.votes.insert(committee_member_obj->vote_id);\n         if (!insert_result.second)\n            FC_THROW(\"Account ${account} was already voting for committee_member ${committee_member}\",\n                     (\"account\", voting_account)(\"committee_member\", committee_member));\n      }\n      else\n      {\n         auto votes_removed = voting_account_object.options.votes.erase(committee_member_obj->vote_id);\n         if( 0 == votes_removed )\n            FC_THROW(\"Account ${account} is already not voting for committee_member ${committee_member}\",\n                     (\"account\", voting_account)(\"committee_member\", committee_member));\n      }\n      account_update_operation account_update_op;\n      account_update_op.account = voting_account_object.id;\n      account_update_op.new_options = voting_account_object.options;\n\n      signed_transaction tx;\n      tx.operations.push_back( account_update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (voting_account)(committee_member)(approve)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::vote_for_witness(string voting_account, string witness,\n         bool approve, bool broadcast )\n   { try {\n      account_object voting_account_object = get_account(voting_account);\n\n      fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(witness);\n      if (!witness_obj)\n         FC_THROW(\"Account ${witness} is not registered as a witness\", (\"witness\", witness));\n      if (approve)\n      {\n         auto insert_result = voting_account_object.options.votes.insert(witness_obj->vote_id);\n         if (!insert_result.second)\n            FC_THROW(\"Account ${account} was already voting for witness ${witness}\",\n                     (\"account\", voting_account)(\"witness\", witness));\n      }\n      else\n      {\n         auto votes_removed = voting_account_object.options.votes.erase(witness_obj->vote_id);\n         if( 0 == votes_removed )\n            FC_THROW(\"Account ${account} is already not voting for witness ${witness}\",\n                     (\"account\", voting_account)(\"witness\", witness));\n      }\n      account_update_operation account_update_op;\n      account_update_op.account = voting_account_object.id;\n      account_update_op.new_options = voting_account_object.options;\n\n      signed_transaction tx;\n      tx.operations.push_back( account_update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (voting_account)(witness)(approve)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::set_voting_proxy(string account_to_modify,\n         optional<string> voting_account, bool broadcast )\n   { try {\n      account_object account_object_to_modify = get_account(account_to_modify);\n      if (voting_account)\n      {\n         account_id_type new_voting_account_id = get_account_id(*voting_account);\n         if (account_object_to_modify.options.voting_account == new_voting_account_id)\n            FC_THROW(\"Voting proxy for ${account} is already set to ${voter}\",\n                     (\"account\", account_to_modify)(\"voter\", *voting_account));\n         account_object_to_modify.options.voting_account = new_voting_account_id;\n      }\n      else\n      {\n         if (account_object_to_modify.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT)\n            FC_THROW(\"Account ${account} is already voting for itself\", (\"account\", account_to_modify));\n         account_object_to_modify.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n      }\n\n      account_update_operation account_update_op;\n      account_update_op.account = account_object_to_modify.id;\n      account_update_op.new_options = account_object_to_modify.options;\n\n      signed_transaction tx;\n      tx.operations.push_back( account_update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (account_to_modify)(voting_account)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::set_desired_witness_and_committee_member_count(\n         string account_to_modify, uint16_t desired_number_of_witnesses, uint16_t desired_number_of_committee_members,\n         bool broadcast )\n   { try {\n      account_object account_object_to_modify = get_account(account_to_modify);\n\n      if (account_object_to_modify.options.num_witness == desired_number_of_witnesses &&\n          account_object_to_modify.options.num_committee == desired_number_of_committee_members)\n         FC_THROW(\"Account ${account} is already voting for ${witnesses} witnesses\"\n                  \" and ${committee_members} committee_members\",\n                  (\"account\", account_to_modify)(\"witnesses\", desired_number_of_witnesses)\n                  (\"committee_members\",desired_number_of_witnesses));\n      account_object_to_modify.options.num_witness = desired_number_of_witnesses;\n      account_object_to_modify.options.num_committee = desired_number_of_committee_members;\n\n      account_update_operation account_update_op;\n      account_update_op.account = account_object_to_modify.id;\n      account_update_op.new_options = account_object_to_modify.options;\n\n      signed_transaction tx;\n      tx.operations.push_back( account_update_op );\n      set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees());\n      tx.validate();\n\n      return sign_transaction( tx, broadcast );\n   } FC_CAPTURE_AND_RETHROW( (account_to_modify)(desired_number_of_witnesses)\n                             (desired_number_of_committee_members)(broadcast) ) }\n\n   signed_transaction wallet_api_impl::propose_parameter_change( const string& proposing_account,\n         fc::time_point_sec expiration_time, const variant_object& changed_values, bool broadcast )\n   {\n      FC_ASSERT( !changed_values.contains(\"current_fees\") );\n\n      const chain_parameters& current_params = get_global_properties().parameters;\n      chain_parameters new_params = current_params;\n      fc::reflector<chain_parameters>::visit(\n         fc::from_variant_visitor<chain_parameters>( changed_values, new_params, GRAPHENE_MAX_NESTED_OBJECTS )\n         );\n\n      committee_member_update_global_parameters_operation update_op;\n      update_op.new_parameters = new_params;\n\n      proposal_create_operation prop_op;\n\n      prop_op.expiration_time = expiration_time;\n      prop_op.review_period_seconds = current_params.committee_proposal_review_period;\n      prop_op.fee_paying_account = get_account(proposing_account).id;\n\n      prop_op.proposed_ops.emplace_back( update_op );\n      current_params.get_current_fees().set_fee( prop_op.proposed_ops.back().op );\n\n      signed_transaction tx;\n      tx.operations.push_back(prop_op);\n      set_operation_fees(tx, current_params.get_current_fees());\n      tx.validate();\n\n      return sign_transaction(tx, broadcast);\n   }\n\n   signed_transaction wallet_api_impl::propose_fee_change( const string& proposing_account,\n         fc::time_point_sec expiration_time, const variant_object& changed_fees, bool broadcast )\n   {\n      const chain_parameters& current_params = get_global_properties().parameters;\n      const fee_schedule_type& current_fees = current_params.get_current_fees();\n\n      flat_map< int, fee_parameters > fee_map;\n      fee_map.reserve( current_fees.parameters.size() );\n      for( const fee_parameters& op_fee : current_fees.parameters )\n         fee_map[ op_fee.which() ] = op_fee;\n      uint32_t scale = current_fees.scale;\n\n      for( const auto& item : changed_fees )\n      {\n         const string& key = item.key();\n         if( key == \"scale\" )\n         {\n            int64_t _scale = item.value().as_int64();\n            FC_ASSERT( _scale >= 0 );\n            FC_ASSERT( _scale <= std::numeric_limits<uint32_t>::max() );\n            scale = uint32_t( _scale );\n            continue;\n         }\n         // is key a number?\n         auto is_numeric = [&key]() -> bool\n         {\n            size_t n = key.size();\n            for( size_t i=0; i<n; i++ )\n            {\n               if( 0 == isdigit( key[i] ) )\n                  return false;\n            }\n            return true;\n         };\n\n         int which;\n         if( is_numeric() )\n            which = std::stoi( key );\n         else\n         {\n            const auto& n2w = _operation_which_map.name_to_which;\n            auto it = n2w.find( key );\n            FC_ASSERT( it != n2w.end(), \"unknown operation\" );\n            which = it->second;\n         }\n\n         fee_parameters fp = from_which_variant< fee_parameters >( which, item.value(), GRAPHENE_MAX_NESTED_OBJECTS );\n         fee_map[ which ] = fp;\n      }\n\n      fee_schedule_type new_fees;\n\n      for( const std::pair< int, fee_parameters >& item : fee_map )\n         new_fees.parameters.insert( item.second );\n      new_fees.scale = scale;\n\n      chain_parameters new_params = current_params;\n      new_params.get_mutable_fees() = new_fees;\n\n      committee_member_update_global_parameters_operation update_op;\n      update_op.new_parameters = new_params;\n\n      proposal_create_operation prop_op;\n\n      prop_op.expiration_time = expiration_time;\n      prop_op.review_period_seconds = current_params.committee_proposal_review_period;\n      prop_op.fee_paying_account = get_account(proposing_account).id;\n\n      prop_op.proposed_ops.emplace_back( update_op );\n      current_params.get_current_fees().set_fee( prop_op.proposed_ops.back().op );\n\n      signed_transaction tx;\n      tx.operations.push_back(prop_op);\n      set_operation_fees(tx, current_params.get_current_fees());\n      tx.validate();\n\n      return sign_transaction(tx, broadcast);\n   }\n\n}}} // namespace graphene::wallet::detail\n"
  },
  {
    "path": "programs/CMakeLists.txt",
    "content": "add_subdirectory( build_helpers )\nadd_subdirectory( cli_wallet )\nadd_subdirectory( genesis_util )\nadd_subdirectory( witness_node )\nadd_subdirectory( js_operation_serializer )\nadd_subdirectory( size_checker )\nadd_subdirectory( network_mapper )\n"
  },
  {
    "path": "programs/README.md",
    "content": "# BitShares Programs\n\nThe bitshares programs are a collection of binaries to run the blockchain, interact with it or utilities.\n\nThe main program is the `witness_node`, used to run a bitshares block producer, API or plugin node. The second in importance is the `cli_wallet`, used to interact with the blockchain. These 2 programs are the most used by the community and updated by the developers, rest of the programs are utilities.\n\nPrograms in here are part of the **bitshares-core** project and are maintained by the bitshares core team and contributors.\n\n\n# Available Programs\n\nFolder | Name  | Description | Category | Status | Help \n---|---|---|---|---|---\n[witness_node](witness_node) | Witness Node | Main software used to sign blocks or provide services. | Node | Active | `./witness_node --help` - [Delayed node configuration](https://github.com/bitshares/bitshares-core/wiki/Delayed-Node)\n[cli_wallet](cli_wallet) | CLI Wallet | Software to interact with the blockchain by command line.  | Wallet | Active | `./cli_wallet --help` \n[js_operation_serializer](js_operation_serializer) | Operation Serializer | Dump all blockchain operations and types. Used by the UI. | Tool | Old | `./js_operation_serializer`\n[size_checker](size_checker) | Size Checker | Return wire size average in bytes of all the operations.  | Tool | Old | `./size_checker`\n[cat-parts](build_helpers/cat-parts.cpp) | Cat parts | Used to create `hardfork.hpp` from individual files. | Tool | Active | `./cat-parts`\n[check_reflect](build_helpers/check_reflect.py) | Check reflect | Check reflected fields automatically(https://github.com/cryptonomex/graphene/issues/562) | Tool | Old | `doxygen;cp -rf doxygen programs/build_helpers; ./check_reflect.py`\n[member_enumerator](build_helpers/member_enumerator.cpp) | Member enumerator | | Tool | Deprecated | `./member_enumerator`\n[get_dev_key](genesis_util/get_dev_key.cpp) | Get Dev Key | Create public, private and address keys. Useful in private testnets, `genesis.json` files, new blockchain creation and others. | Tool | Active | `/programs/genesis_util/get_dev_key -h`\n[genesis_util](genesis_util) | Genesis Utils | Other utilities for genesis creation. | Tool | Old |\n[network_mapper](network_mapper) | Network Mapper | Generates .DOT file that can be rendered by graphviz to make images of node connectivity. | Tool | Experimental | `./programs/network_mapper/network_mapper`\n"
  },
  {
    "path": "programs/build_helpers/CMakeLists.txt",
    "content": "get_target_property(GRAPHENE_CHAIN_SOURCE graphene_chain SOURCE_DIR)\nget_target_property(GRAPHENE_CHAIN_BIN graphene_chain BINARY_DIR)\n\n# This is here only to support CMake < 3.4\nif (NOT GRAPHENE_CHAIN_SOURCE)\n  set(GRAPHENE_CHAIN_SOURCE ${GRAPHENE_CHAIN_SOURCE_LEGACY})\nendif()\n# This is here only to support CMake < 3.4\nif (NOT GRAPHENE_CHAIN_BIN)\n  set(GRAPHENE_CHAIN_BIN ${GRAPHENE_CHAIN_BIN_LEGACY})\nendif()\n\nadd_custom_target( build_hardfork_hpp\n  COMMAND\n    ${CMAKE_COMMAND}\n    -DINIT_BINARY_DIR=${GRAPHENE_CHAIN_BIN}\n    -DINIT_SOURCE_DIR=${GRAPHENE_CHAIN_SOURCE}\n    -P ${CMAKE_CURRENT_SOURCE_DIR}/cat-parts.cmake\n)\n\nadd_executable( member_enumerator member_enumerator.cpp )\nif( UNIX AND NOT APPLE )\n  set( rt_library rt )\nendif()\n\n# we only actually need Boost, but link against FC for now so we don't duplicate it.\ntarget_link_libraries( member_enumerator PRIVATE fc graphene_app graphene_net graphene_chain graphene_egenesis_brief\n                                                 graphene_utilities graphene_wallet ${CMAKE_DL_LIBS}\n                                                 ${PLATFORM_SPECIFIC_LIBS} )\n"
  },
  {
    "path": "programs/build_helpers/check_reflect.py",
    "content": "#!/usr/bin/env python3\n\nimport json\nimport os\nimport re\nimport xml.etree.ElementTree as etree\n\ndef process_node(path, node):\n    \"\"\"\n    if node.tag == \"TestCase\":\n        if node.attrib.get(\"result\", \"UNKNOWN\") != \"passed\":\n            failset.add(node)\n        return\n    if node.tag in [\"TestResult\", \"TestSuite\"]:\n        for child in node:\n            cpath = path+\"/\"+child.attrib[\"name\"]\n            process_node(cpath, child)\n        return\n    \"\"\"\n    #print(\"unknown node\", node.tag)\n    print(node.tag)\n    return\n\nname2members_doxygen = {}\n\ndef process_class_node(node):\n    result = {\"name\":\"\", \"vmembers\":[]}\n    for child in node:\n        if child.tag == \"name\":\n            result[\"name\"] = child.text\n        elif child.tag == \"member\":\n            kind = child.attrib.get(\"kind\")\n            if kind == \"variable\":\n                result[\"vmembers\"].append(child[0].text)\n    name2members_doxygen[result[\"name\"]] = result[\"vmembers\"]\n    return\n\ntree = etree.parse(\"doxygen/xml/index.xml\")\nroot = tree.getroot()\nfor child in root:\n    if (child.tag == \"compound\") and (child.attrib.get(\"kind\") in [\"struct\", \"class\"]):\n        process_class_node(child)\n\ns_static_names = set([\"space_id\", \"type_id\"])\n\nfor k, v in name2members_doxygen.items():\n    name2members_doxygen[k] = [x for x in v if x not in s_static_names]\n\n#with open(\"stuff/member_enumerator.out\", \"r\") as f:\n#    name2members_fc = json.load(f)\n\n# scan for FC_REFLECT( graphene::... in all cpp,hpp files under libraries/ programs/ tests/\n\nre_reflect = re.compile(r\"\"\"\nFC_REFLECT\\s*[(]\n\\s*(graphene::[a-zA-Z0-9_:]+)\n\\s*,\n((?:\\s*[(]\\s*[a-zA-Z0-9_]+\\s*[)])*)\n\"\"\", re.VERBOSE)\n\nre_reflect_derived = re.compile(r\"\"\"\nFC_REFLECT_DERIVED\\s*[(]\n\\s*(graphene::[a-zA-Z0-9_:]+)\n\\s*,\n\\s*[(]\\s*(graphene::[a-zA-Z0-9_:]+)\\s*[)]\n\\s*,\n((?:\\s*[(]\\s*[a-zA-Z0-9_]+\\s*[)])*)\n\"\"\", re.VERBOSE)\n\nre_bubble_item = re.compile(r\"\\s*[(]\\s*([a-zA-Z0-9_]+)\\s*\")\n\ndef bubble_list(x):\n    return [re_bubble_item.match(e).group(1) for e in x.split(\")\")[:-1]]\n\nname2members_re = {}\n\nfor root, dirs, files in os.walk(\".\"):\n    if root == \".\":\n        dirs[:] = [\"libraries\", \"programs\", \"tests\"]\n    for filename in files:\n        if not (filename.endswith(\".cpp\") or filename.endswith(\".hpp\")):\n            continue\n        try:\n            with open( os.path.join(root, filename), \"r\" ) as f:\n                content = f.read()\n                for m in re_reflect.finditer(content):\n                    cname = m.group(1)\n                    members = bubble_list(m.group(2))\n                    name2members_re[cname] = members\n                    if cname.endswith(\"_object\"):\n                       print(\"FC_REFLECT on {} should be FC_REFLECT_DERIVED\".format(cname))\n                for m in re_reflect_derived.finditer(content):\n                    cname = m.group(1)\n                    members = bubble_list(m.group(3))\n                    name2members_re[cname] = members\n        except OSError as e:\n            pass\n\ndef validate_members(name2members_ref, name2members_test):\n    ok_items = []\n    ne_items = []\n    error_items = []\n\n    for name in sorted(name2members_ref.keys()):\n        if name not in name2members_test:\n            ne_items.append(name)\n        elif sorted(name2members_ref[name]) != sorted(name2members_test[name]):\n            error_items.append(name)\n            print(\"\")\n            print(\"error in\", name)\n            print(\"doxygen:\", name2members_ref[name])\n            print(\"fc     :\", name2members_test[name])\n        else:\n            ok_items.append(name)\n    return\n\n\"\"\"\nprint(\"\")\nprint(\"ok:\")\nfor item in ok_items:\n    print(item)\n\nprint(\"\")\nprint(\"not evaluated:\")\nfor item in ne_items:\n    print(item)\n\nprint(\"\")\nprint(\"error:\")\nfor item in error_items:\n    print(item)\n\"\"\"\n\nvalidate_members(name2members_doxygen, name2members_re)\n"
  },
  {
    "path": "programs/build_helpers/make_with_sonar",
    "content": "#!/bin/sh\n\nOUT_DIR=\"$1\"\nshift\nif which build-wrapper-linux-x86-64 >/dev/null; then\n    exec build-wrapper-linux-x86-64 --out-dir \"$OUT_DIR\" make \"$@\"\nfi\nexec make \"$@\"\n"
  },
  {
    "path": "programs/build_helpers/member_enumerator.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n *\n * 1. Any modified source or binaries are used only with the BitShares network.\n *\n * 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n *\n * 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\n * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n#include <graphene/protocol/block.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/market_evaluator.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <iostream>\n\nusing namespace graphene::chain;\n\nnamespace graphene { namespace member_enumerator {\n\nstruct class_processor\n{\n   explicit class_processor( std::map< std::string, std::vector< std::string > >& r ) : result(r) {}\n\n   template< typename T >\n   void process_class( const T* dummy );\n\n   template< typename... T >\n   void process_class( const static_variant< T... >* dummy );\n\n   template< typename T >\n   void process_class( const std::vector< T >* dummy );\n\n   template< typename K, typename V >\n   void process_class( const std::map< K, V >* dummy );\n\n   template< typename T >\n   void process_class( const fc::flat_set< T >* dummy );\n\n   template< typename K, typename V >\n   void process_class( const fc::flat_map< K, V >* dummy );\n\n   template< typename T >\n   void process_class( const fc::optional< T >* dummy );\n\n   template< typename T >\n   static void process_class( std::map< std::string, std::vector< std::string > >& result );\n\n   std::map< std::string, std::vector< std::string > >& result;\n};\n\ntemplate< typename T >\nstruct member_visitor\n{\n   member_visitor( class_processor* p ) : proc(p) {}\n\n   template<typename Member, class Class, Member (Class::*member)>\n   void operator()( const char* name )const\n   {\n      members.emplace_back( name );\n      proc->process_class( (const Member*) nullptr );\n   }\n\n   class_processor* proc;\n   mutable std::vector< std::string > members;\n};\n\nstruct static_variant_visitor\n{\n   explicit static_variant_visitor( class_processor* p ) : proc(p) {}\n\n   typedef void result_type;\n\n   template<typename T>\n   void operator()( const T& element )const\n   {\n      proc->process_class( (const T*) nullptr );\n   }\n\n   class_processor* proc;\n};\n\ntemplate< typename... T >\nvoid class_processor::process_class( const static_variant< T... >* dummy )\n{\n   static_variant<T...> dummy2;\n   static_variant_visitor vtor( this );\n\n   for( size_t w=0; w<dummy2.count(); w++ )\n   {\n      dummy2.set_which(w);\n      dummy2.visit( vtor );\n   }\n}\n\ntemplate<typename IsReflected=std::false_type>\nstruct if_reflected\n{\n   template< typename T >\n   static void process_class( class_processor* proc, const T* dummy )\n   {\n      std::string tname = fc::get_typename<T>::name();\n      std::cerr << \"skipping non-reflected class \" << tname << std::endl;\n   }\n};\n\ntemplate<>\nstruct if_reflected<std::true_type>\n{\n   template< typename T >\n   static void process_class( class_processor* proc, const T* dummy )\n   {\n      std::string tname = fc::get_typename<T>::name();\n      if( proc->result.find( tname ) != proc->result.end() )\n         return;\n      ilog( \"processing class ${c}\", (\"c\", tname) );\n      // need this to keep from recursing on same class\n      proc->result.emplace( tname, std::vector< std::string >() );\n\n      member_visitor<T> vtor( proc );\n      fc::reflector<T>::visit( vtor );\n      ilog( \"members of class ${c} are ${m}\", (\"c\", tname)(\"m\", vtor.members) );\n      proc->result[tname] = vtor.members;\n   }\n};\n\ntemplate< typename T >\nvoid class_processor::process_class( const T* dummy )\n{\n   if_reflected< typename fc::reflector<T>::is_defined >::process_class( this, dummy );\n}\n\ntemplate< typename T >\nvoid class_processor::process_class( const std::vector< T >* dummy )\n{\n   process_class( (T*) nullptr );\n}\n\ntemplate< typename K, typename V >\nvoid class_processor::process_class( const std::map< K, V >* dummy )\n{\n   process_class( (K*) nullptr );\n   process_class( (V*) nullptr );\n}\n\ntemplate< typename T >\nvoid class_processor::process_class( const fc::flat_set< T >* dummy )\n{\n   process_class( (T*) nullptr );\n}\n\ntemplate< typename K, typename V >\nvoid class_processor::process_class( const fc::flat_map< K, V >* dummy )\n{\n   process_class( (K*) nullptr );\n   process_class( (V*) nullptr );\n}\n\ntemplate< typename T >\nvoid class_processor::process_class( const fc::optional< T >* dummy )\n{\n   process_class( (T*) nullptr );\n}\n\ntemplate< typename T >\nvoid class_processor::process_class( std::map< std::string, std::vector< std::string > >& result )\n{\n   class_processor proc(result);\n   proc.process_class( (T*) nullptr );\n}\n\n} }\n\nint main( int argc, char** argv )\n{\n   try\n   {\n      std::map< std::string, std::vector< std::string > > result;\n      graphene::member_enumerator::class_processor::process_class<signed_block>(result);\n\n      fc::mutable_variant_object mvo;\n      for( const std::pair< std::string, std::vector< std::string > >& e : result )\n      {\n         variant v;\n         to_variant( e.second, v , 1);\n         mvo.set( e.first, v );\n      }\n\n      std::cout << fc::json::to_string( mvo ) << std::endl;\n   }\n   catch ( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n   }\n   return 0;\n}\n"
  },
  {
    "path": "programs/build_helpers/run-node-test",
    "content": "#!/bin/sh\n\nDATA_DIR=\"`mktemp -d`\"\n\ncleanup () {\n    kill -9 $NODE_PID $CLI_PID\n    if [ \"$1\" != 0 ]; then\n\techo \"----- node.log -----\"\n\tcat node.log\n\techo \"----- cli.log -----\"\n\tcat cli.log\n    fi\n    rm -rf node.log cli.log \"$DATA_DIR\"\n    exit $1\n}\n\necho \"Starting witness node...\" 1>&2\nprograms/witness_node/witness_node --data-dir \"$DATA_DIR\" \\\n\t\t\t--checkpoint '[131072,\"0000000000000000000000000000000000000001\"]' \\\n\t\t\t--rpc-endpoint 127.0.0.1:8090 --force-validate >node.log 2>&1 &\nNODE_PID=$!\n\necho \"Waiting for cli_wallet start...\" 1>&2 &\nCLI_PID=$!\nsleep 5\n_START=\"`date +%s`\"\nwhile ! ps -p \"$CLI_PID\" >/dev/null && [ $((`date +%s` - $_START)) -lt 120 ]; do\n    programs/cli_wallet/cli_wallet -sws://127.0.0.1:8090 -d -H127.0.0.1:8091 >cli.log 2>&1 &\n    CLI_PID=$!\n    sleep 10\ndone\n\nif ! ps -p \"$CLI_PID\" >/dev/null; then\n    echo \"Failed to start?!\" 1>&2\n    cleanup 1\nfi\n\necho \"Waiting for head_block 131071...\" 1>&2\ntouch \"$DATA_DIR\"/info.json\n_START=\"`date +%s`\"\nwhile [ $(( `date +%s` - $_START )) -lt 600 ]; do\n    sleep 2\n    curl --silent -o \"$DATA_DIR\"/info.json --data '{\"id\":0,\"method\":\"info\",\"params\":[]}' \\\n\t http://127.0.0.1:8091/rpc\n    tr , '\\n' <\"$DATA_DIR\"/info.json | grep head_block_num\n    if tr , '\\n' <\"$DATA_DIR\"/info.json | grep head_block_num.*131071; then\n\techo \"Success!\" 1>&2\n\tcleanup 0\n    fi\ndone\n\necho \"Failed to sync?!\" 1>&2\n\ncleanup 1\n"
  },
  {
    "path": "programs/build_helpers/set_sonar_branch_for_github_actions",
    "content": "#!/bin/bash\n\n# Relevant variables set by Github Actions:\n# GITHUB_HEAD_REF: Only set for pull request events.\n#                  The name of the head branch.\n# GITHUB_BASE_REF: Only set for pull request events.\n#                  The name of the base branch.\n# GITHUB_REF: The branch or tag ref that triggered the workflow.\n#             For example, refs/heads/feature-branch-1.\n#             If neither a branch or tag is available for the event type,\n#             the variable will not exist.\n\nif [ \"$#\" != 1 ]; then\n    echo \"Usage: $0 <sonar-properties-file>\" 1>&2\n    exit 1\nfi\n\nclear_branch () {\n    sed -i '/sonar\\.branch/d' \"$1\"\n}\n\nif [ -n \"$GITHUB_HEAD_REF\" ]; then\n    # PRs work per default, remove sonar.branch.* and add sonar.pullrequest.*\n    echo \"Detected PR '$GITHUB_REF' from '$GITHUB_HEAD_REF' to '$GITHUB_BASE_REF'\"\n    PULLREF=${GITHUB_REF#refs/pull/}\n    PULLKEY=${PULLREF%/merge}\n    clear_branch \"$1\"\n    echo \"sonar.pullrequest.key=$PULLKEY\" >>\"$1\"\n    echo \"sonar.pullrequest.base=$GITHUB_BASE_REF\" >>\"$1\"\n    echo \"sonar.pullrequest.branch=$GITHUB_HEAD_REF\" >>\"$1\"\nelse\n    ORIGINAL_TARGET=\"$( grep 'sonar\\.branch\\.target' \"$1\" | sed 's=^.*[:=] *==' )\"\n    TARGET=\"$ORIGINAL_TARGET\"\n\n    if [[ ${GITHUB_REF} == \"refs/tags/\"* ]]; then\n        # Tag build is either master or testnet\n        echo \"Detected tag '${GITHUB_REF}'\"\n        BRANCH=\"${GITHUB_REF#refs/}\"\n        case \"$BRANCH\" in\n\t    *test*) TARGET=testnet; ;;\n\t    *)\tTARGET=master; ;;\n        esac\n    else\n        BRANCH=\"${GITHUB_REF#refs/heads/}\"\n        case \"$BRANCH\" in\n\t    master|develop|testnet|hardfork)\n\t        # Long-lived branches stand for themselves, no target\n\t        echo \"Detected long-lived branch '$BRANCH'\"\n\t        TARGET=\n\t        ;;\n\t    *test*release*)\n\t        # Testnet release branch will be merged into testnet\n\t        echo \"Detected testnet release branch '$BRANCH'\"\n\t        TARGET=testnet\n\t        ;;\n\t    *release*)\n\t        # Release branch will be merged into default (master)\n\t        echo \"Detected release branch '$BRANCH'\"\n\t        TARGET=master\n\t        ;;\n\t    *)\n\t        # All other branches should have sonar.branch.target in their\n\t        # sonar.properties, leave it unchanged\n\t        echo \"Detected normal branch '$BRANCH'\"\n        esac\n    fi\n\n    echo \"Branch '$BRANCH', target '$TARGET'\"\n\n    if [ \"x$TARGET\" != \"x$ORIGINAL_TARGET\" ]; then\n        clear_branch \"$1\"\n        if [ -n \"$TARGET\" ]; then\n            echo \"sonar.branch.target=$TARGET\" >>\"$1\"\n        fi\n    fi\n    if [ -n \"$BRANCH\" ]; then\n        echo \"sonar.branch.name=$BRANCH\" >>\"$1\"\n    fi\n\nfi\n\nexit 0\n"
  },
  {
    "path": "programs/cli_wallet/CMakeLists.txt",
    "content": "add_executable( cli_wallet main.cpp )\nif( UNIX AND NOT APPLE )\n  set(rt_library rt )\nendif()\n\nfind_package( Gperftools QUIET )\nif( GPERFTOOLS_FOUND )\n    message( STATUS \"Found gperftools; compiling cli_wallet with TCMalloc\")\n    list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )\nendif()\n\ntarget_link_libraries( cli_wallet\n      PRIVATE graphene_app graphene_net graphene_chain graphene_egenesis_brief graphene_utilities graphene_wallet fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\nif(MSVC)\n  set_source_files_properties( main.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   cli_wallet\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "programs/cli_wallet/main.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n\n#include <fc/io/json.hpp>\n#include <fc/io/stdio.hpp>\n#include <fc/network/http/websocket.hpp>\n#include <fc/rpc/cli.hpp>\n#include <fc/rpc/websocket_api.hpp>\n#include <fc/stacktrace.hpp>\n\n#include <graphene/app/api.hpp>\n#include <graphene/chain/config.hpp>\n#include <graphene/egenesis/egenesis.hpp>\n#include <graphene/utilities/key_conversion.hpp>\n#include <graphene/wallet/wallet.hpp>\n\n#include <fc/interprocess/signals.hpp>\n#include <boost/program_options.hpp>\n\n#include <fc/log/console_appender.hpp>\n#include <fc/log/file_appender.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/log/logger_config.hpp>\n\n#include <graphene/utilities/git_revision.hpp>\n#include <boost/version.hpp>\n#include <boost/algorithm/string/replace.hpp>\n#include <websocketpp/version.hpp>\n\n#ifdef WIN32\n# include <signal.h>\n#else\n# include <csignal>\n#endif\n\nusing namespace graphene::app;\nusing namespace graphene::chain;\nusing namespace graphene::utilities;\nusing namespace graphene::wallet;\nusing namespace std;\nnamespace bpo = boost::program_options;\n\nfc::log_level string_to_level(string level)\n{\n   fc::log_level result;\n   if(level == \"info\")\n      result = fc::log_level::info;\n   else if(level == \"debug\")\n      result = fc::log_level::debug;\n   else if(level == \"warn\")\n      result = fc::log_level::warn;\n   else if(level == \"error\")\n      result = fc::log_level::error;\n   else if(level == \"all\")\n      result = fc::log_level::all;\n   else\n      FC_THROW(\"Log level not allowed. Allowed levels are info, debug, warn, error and all.\");\n\n   return result;\n}\n\nvoid setup_logging(string console_level, bool file_logger, string file_level, string file_name)\n{\n   fc::logging_config cfg;\n\n   // console logger\n   fc::console_appender::config console_appender_config;\n   console_appender_config.level_colors.emplace_back(\n         fc::console_appender::level_color(fc::log_level::debug,\n         fc::console_appender::color::green));\n   console_appender_config.level_colors.emplace_back(\n         fc::console_appender::level_color(fc::log_level::warn,\n         fc::console_appender::color::brown));\n   console_appender_config.level_colors.emplace_back(\n         fc::console_appender::level_color(fc::log_level::error,\n         fc::console_appender::color::red));\n   cfg.appenders.push_back(fc::appender_config( \"default\", \"console\", fc::variant(console_appender_config, 20)));\n   cfg.loggers = { fc::logger_config(\"default\"), fc::logger_config( \"rpc\") };\n   cfg.loggers.front().level = string_to_level(console_level);\n   cfg.loggers.front().appenders = {\"default\"};\n\n   // file logger\n   if(file_logger) {\n      fc::path data_dir;\n      fc::path log_dir = data_dir / \"cli_wallet_logs\";\n      fc::file_appender::config ac;\n      ac.filename             = log_dir / file_name;\n      ac.flush                = true;\n      ac.rotate               = true;\n      ac.rotation_interval    = fc::hours( 1 );\n      ac.rotation_limit       = fc::days( 1 );\n      cfg.appenders.push_back(fc::appender_config( \"rpc\", \"file\", fc::variant(ac, 5)));\n      cfg.loggers.back().level = string_to_level(file_level);\n      cfg.loggers.back().appenders = {\"rpc\"};\n      fc::configure_logging( cfg );\n      ilog (\"Logging RPC to file: \" + (ac.filename).preferred_string());\n   }\n}\n\nint main( int argc, char** argv )\n{\n   fc::print_stacktrace_on_segfault();\n   try {\n\n      boost::program_options::options_description opts;\n         opts.add_options()\n         (\"help,h\", \"Print this help message and exit.\")\n         (\"server-rpc-endpoint,s\", bpo::value<string>()->implicit_value(\"ws://127.0.0.1:8090\"),\n               \"Server websocket RPC endpoint\")\n         (\"server-rpc-user,u\", bpo::value<string>(), \"Server Username\")\n         (\"server-rpc-password,p\", bpo::value<string>(), \"Server Password\")\n         (\"rpc-endpoint,r\", bpo::value<string>()->implicit_value(\"127.0.0.1:8091\"),\n               \"Endpoint for wallet websocket RPC to listen on (DEPRECATED, use rpc-http-endpoint instead)\")\n         (\"rpc-tls-endpoint,t\", bpo::value<string>()->implicit_value(\"127.0.0.1:8092\"),\n               \"Endpoint for wallet websocket TLS RPC to listen on\")\n         (\"rpc-tls-certificate,c\", bpo::value<string>()->implicit_value(\"server.pem\"),\n               \"PEM certificate for wallet websocket TLS RPC\")\n         (\"rpc-http-endpoint,H\", bpo::value<string>()->implicit_value(\"127.0.0.1:8093\"),\n               \"Endpoint for wallet HTTP and websocket RPC to listen on\")\n         (\"daemon,d\", \"Run the wallet in daemon mode\" )\n         (\"wallet-file,w\", bpo::value<string>()->implicit_value(\"wallet.json\"), \"wallet to load\")\n         (\"chain-id\", bpo::value<string>(), \"chain ID to connect to\")\n         (\"suggest-brain-key\", \"Suggest a safe brain key to use for creating your account\")\n         (\"logs-rpc-console-level\", bpo::value<string>()->default_value(\"info\"),\n               \"Level of console logging. Allowed levels: info, debug, warn, error, all\")\n         (\"logs-rpc-file\", bpo::value<bool>()->default_value(false), \"Turn on/off file logging\")\n         (\"logs-rpc-file-level\", bpo::value<string>()->default_value(\"debug\"),\n               \"Level of file logging. Allowed levels: info, debug, warn, error, all\")\n         (\"logs-rpc-file-name\", bpo::value<string>()->default_value(\"rpc.log\"), \"File name for file rpc logs\")\n         (\"version,v\", \"Display version information\");\n\n      bpo::variables_map options;\n\n      bpo::store( bpo::parse_command_line(argc, argv, opts), options );\n\n      if( options.count(\"help\") > 0 )\n      {\n         std::cout << opts << \"\\n\";\n         return 0;\n      }\n      if( options.count(\"version\") > 0 )\n      {\n         std::cout << \"Version: \" << graphene::utilities::git_revision_description << \"\\n\";\n         std::cout << \"SHA: \" << graphene::utilities::git_revision_sha << \"\\n\";\n         std::cout << \"Timestamp: \" << fc::get_approximate_relative_time_string(fc::time_point_sec(\n                                             graphene::utilities::git_revision_unix_timestamp)) << \"\\n\";\n         std::cout << \"SSL: \" << OPENSSL_VERSION_TEXT << \"\\n\";\n         std::cout << \"Boost: \" << boost::replace_all_copy(std::string(BOOST_LIB_VERSION), \"_\", \".\") << \"\\n\";\n         std::cout << \"Websocket++: \" << websocketpp::major_version << \".\" << websocketpp::minor_version << \".\"\n                   << websocketpp::patch_version << \"\\n\";\n         return 0;\n      }\n      if( options.count(\"suggest-brain-key\") > 0 )\n      {\n         auto keyinfo = graphene::wallet::utility::suggest_brain_key();\n         string data = fc::json::to_pretty_string( keyinfo );\n         std::cout << data.c_str() << std::endl;\n         return 0;\n      }\n\n      setup_logging(options.at(\"logs-rpc-console-level\").as<string>(),options.at(\"logs-rpc-file\").as<bool>(),\n            options.at(\"logs-rpc-file-level\").as<string>(), options.at(\"logs-rpc-file-name\").as<string>());\n\n      // TODO:  We read wallet_data twice, once in main() to grab the\n      //    socket info, again in wallet_api when we do\n      //    load_wallet_file().  Seems like this could be better\n      //    designed.\n      //\n      wallet_data wdata;\n\n      fc::path wallet_file( options.count(\"wallet-file\") > 0 ? options.at(\"wallet-file\").as<string>()\n                                                             : \"wallet.json\" );\n      if( fc::exists( wallet_file ) )\n      {\n         wdata = fc::json::from_file( wallet_file ).as<wallet_data>( GRAPHENE_MAX_NESTED_OBJECTS );\n         if( options.count(\"chain-id\") > 0 )\n         {\n            // the --chain-id on the CLI must match the chain ID embedded in the wallet file\n            if( chain_id_type(options.at(\"chain-id\").as<std::string>()) != wdata.chain_id )\n            {\n               std::cout << \"Chain ID in wallet file does not match specified chain ID\\n\";\n               return 1;\n            }\n         }\n      }\n      else\n      {\n         if( options.count(\"chain-id\") > 0 )\n         {\n            wdata.chain_id = chain_id_type(options.at(\"chain-id\").as<std::string>());\n            std::cout << \"Starting a new wallet with chain ID \" << wdata.chain_id.str() << \" (from CLI)\\n\";\n         }\n         else\n         {\n            wdata.chain_id = graphene::egenesis::get_egenesis_chain_id();\n            std::cout << \"Starting a new wallet with chain ID \" << wdata.chain_id.str() << \" (from egenesis)\\n\";\n         }\n      }\n\n      // but allow CLI to override\n      if( options.count(\"server-rpc-endpoint\") > 0 )\n         wdata.ws_server = options.at(\"server-rpc-endpoint\").as<std::string>();\n      if( options.count(\"server-rpc-user\") > 0 )\n         wdata.ws_user = options.at(\"server-rpc-user\").as<std::string>();\n      if( options.count(\"server-rpc-password\") > 0 )\n         wdata.ws_password = options.at(\"server-rpc-password\").as<std::string>();\n\n      fc::http::websocket_client client;\n      idump((wdata.ws_server));\n      auto con  = client.connect( wdata.ws_server );\n      auto apic = std::make_shared<fc::rpc::websocket_api_connection>(con, GRAPHENE_MAX_NESTED_OBJECTS);\n\n      auto remote_api = apic->get_remote_api< login_api >(1);\n      edump((wdata.ws_user)(wdata.ws_password) );\n      FC_ASSERT( remote_api->login( wdata.ws_user, wdata.ws_password ), \"Failed to log in to API server\" );\n\n      auto wapiptr = std::make_shared<wallet_api>( wdata, remote_api );\n      wapiptr->set_wallet_filename( wallet_file.generic_string() );\n      wapiptr->load_wallet_file();\n\n      fc::api<wallet_api> wapi(wapiptr);\n\n      std::shared_ptr<fc::http::websocket_server> _websocket_server;\n      if( options.count(\"rpc-endpoint\") > 0 )\n      {\n         _websocket_server = std::make_shared<fc::http::websocket_server>(\"\");\n         _websocket_server->on_connection([&wapi]( const fc::http::websocket_connection_ptr& c ){\n            auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(c, GRAPHENE_MAX_NESTED_OBJECTS);\n            wsc->register_api(wapi);\n            c->set_session_data( wsc );\n         });\n         ilog( \"Listening for incoming HTTP and WS RPC requests on ${p}\", (\"p\", options.at(\"rpc-endpoint\").as<string>() ));\n         _websocket_server->listen( fc::ip::endpoint::from_string(options.at(\"rpc-endpoint\").as<string>()) );\n         _websocket_server->start_accept();\n      }\n\n      string cert_pem = \"server.pem\";\n      if( options.count( \"rpc-tls-certificate\" ) > 0 )\n         cert_pem = options.at(\"rpc-tls-certificate\").as<string>();\n\n      std::shared_ptr<fc::http::websocket_tls_server> _websocket_tls_server;\n      if( options.count(\"rpc-tls-endpoint\") > 0 )\n      {\n         _websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>(cert_pem, \"\", \"\");\n         _websocket_tls_server->on_connection([&wapi]( const fc::http::websocket_connection_ptr& c ){\n            auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(c, GRAPHENE_MAX_NESTED_OBJECTS);\n            wsc->register_api(wapi);\n            c->set_session_data( wsc );\n         });\n         ilog( \"Listening for incoming HTTPS and WSS RPC requests on ${p}\",\n               (\"p\", options.at(\"rpc-tls-endpoint\").as<string>()) );\n         _websocket_tls_server->listen( fc::ip::endpoint::from_string(options.at(\"rpc-tls-endpoint\").as<string>()) );\n         _websocket_tls_server->start_accept();\n      }\n\n      std::shared_ptr<fc::http::websocket_server> _http_ws_server;\n      if( options.count(\"rpc-http-endpoint\" ) > 0 )\n      {\n         _http_ws_server = std::make_shared<fc::http::websocket_server>(\"\");\n         ilog( \"Listening for incoming HTTP and WS RPC requests on ${p}\",\n               (\"p\", options.at(\"rpc-http-endpoint\").as<string>()) );\n         _http_ws_server->on_connection([&wapi]( const fc::http::websocket_connection_ptr& c ){\n            auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(c, GRAPHENE_MAX_NESTED_OBJECTS);\n            wsc->register_api(wapi);\n            c->set_session_data( wsc );\n         });\n         _http_ws_server->listen( fc::ip::endpoint::from_string(options.at(\"rpc-http-endpoint\").as<string>()) );\n         _http_ws_server->start_accept();\n      }\n\n      if( options.count( \"daemon\" ) == 0 )\n      {\n         auto wallet_cli = std::make_shared<fc::rpc::cli>( GRAPHENE_MAX_NESTED_OBJECTS );\n         \n         wallet_cli->set_regex_secret(\"\\\\s*(unlock|set_password)\\\\s*\");\n\n         for( auto& name_formatter : wapiptr->get_result_formatters() )\n            wallet_cli->format_result( name_formatter.first, name_formatter.second );\n\n         std::cout << \"\\nType \\\"help\\\" for a list of available commands.\\n\";\n         std::cout << \"Type \\\"gethelp <command>\\\" for info about individual commands.\\n\\n\";\n         if( wapiptr->is_new() )\n         {\n            std::cout << \"Please use the \\\"set_password\\\" method to initialize a new wallet before continuing\\n\";\n            wallet_cli->set_prompt( \"new >>> \" );\n         }\n         else\n            wallet_cli->set_prompt( \"locked >>> \" );\n\n         boost::signals2::scoped_connection locked_connection( wapiptr->lock_changed.connect(\n            [wallet_cli](bool locked) {\n               wallet_cli->set_prompt( locked ? \"locked >>> \" : \"unlocked >>> \" );\n            }));\n\n         auto sig_set = fc::set_signal_handler( [wallet_cli](int signal) {\n            ilog( \"Captured SIGINT not in daemon mode, exiting\" );\n            fc::set_signal_handler( [](int sig) {}, SIGINT ); // reinstall an empty SIGINT handler\n            wallet_cli->cancel();\n         }, SIGINT );\n\n         fc::set_signal_handler( [wallet_cli,sig_set](int signal) {\n            ilog( \"Captured SIGTERM not in daemon mode, exiting\" );\n            sig_set->cancel();\n            fc::set_signal_handler( [](int sig) {}, SIGINT ); // reinstall an empty SIGINT handler\n            wallet_cli->cancel();\n         }, SIGTERM );\n#ifdef SIGQUIT\n         fc::set_signal_handler( [wallet_cli,sig_set](int signal) {\n            ilog( \"Captured SIGQUIT not in daemon mode, exiting\" );\n            sig_set->cancel();\n            fc::set_signal_handler( [](int sig) {}, SIGINT ); // reinstall an empty SIGINT handler\n            wallet_cli->cancel();\n         }, SIGQUIT );\n#endif\n         boost::signals2::scoped_connection closed_connection( con->closed.connect( [wallet_cli,sig_set] {\n            elog( \"Server has disconnected us.\" );\n            sig_set->cancel();\n            fc::set_signal_handler( [](int sig) {}, SIGINT ); // reinstall an empty SIGINT handler\n            wallet_cli->cancel();\n         }));\n\n         wallet_cli->register_api( wapi );\n         wallet_cli->start();\n         wallet_cli->wait();\n\n         locked_connection.disconnect();\n         closed_connection.disconnect();\n      }\n      else\n      {\n         fc::promise<int>::ptr exit_promise = fc::promise<int>::create(\"UNIX Signal Handler\");\n\n         fc::set_signal_handler( [&exit_promise](int signal) {\n            ilog( \"Captured SIGINT in daemon mode, exiting\" );\n            exit_promise->set_value(signal);\n         }, SIGINT );\n\n         fc::set_signal_handler( [&exit_promise](int signal) {\n            ilog( \"Captured SIGTERM in daemon mode, exiting\" );\n            exit_promise->set_value(signal);\n         }, SIGTERM );\n#ifdef SIGQUIT\n         fc::set_signal_handler( [&exit_promise](int signal) {\n            ilog( \"Captured SIGQUIT in daemon mode, exiting\" );\n            exit_promise->set_value(signal);\n         }, SIGQUIT );\n#endif\n         boost::signals2::scoped_connection closed_connection( con->closed.connect( [&exit_promise] {\n            elog( \"Server has disconnected us.\" );\n            exit_promise->set_value(0);\n         }));\n\n         ilog( \"Entering Daemon Mode, ^C to exit\" );\n         exit_promise->wait();\n\n         closed_connection.disconnect();\n      }\n\n      wapi->save_wallet_file(wallet_file.generic_string());\n   }\n   catch ( const fc::exception& e )\n   {\n      std::cerr << e.to_detail_string() << \"\\n\";\n      return -1;\n   }\n   return 0;\n}\n"
  },
  {
    "path": "programs/genesis_util/CMakeLists.txt",
    "content": "\nadd_executable( genesis_update genesis_update.cpp )\nif( UNIX AND NOT APPLE )\n  set(rt_library rt )\nendif()\n\ntarget_link_libraries( genesis_update\n                       PRIVATE graphene_app graphene_chain graphene_egenesis_none fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\ninstall( TARGETS\n   genesis_update\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n\nadd_executable( get_dev_key get_dev_key.cpp )\n\ntarget_link_libraries( get_dev_key\n                       PRIVATE graphene_utilities graphene_protocol fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\ninstall( TARGETS\n   get_dev_key\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n\nadd_executable( convert_address convert_address.cpp )\n\ntarget_link_libraries( convert_address\n                       PRIVATE graphene_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n"
  },
  {
    "path": "programs/genesis_util/apply_patch.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Apply a patch file to a JSON object\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-d\", \"--delta\", metavar=\"DELTA\", nargs=\"+\", help=\"list of delta file(s) to apply\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)        \n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    if opts.delta is None: \n        opts.delta = []\n    for filename in opts.delta:\n        with open(filename, \"r\") as f:\n            patch = json.load(f)\n        for k, v in patch.get(\"append\", {}).items():\n            if k not in genesis:\n                genesis[k] = []\n                sys.stderr.write(\"[WARN]  item {k} was created\\n\".format(k=k))\n            genesis[k].extend(v)\n            sys.stderr.write(\"appended {n} items to {k}\\n\".format(n=len(v), k=k))\n        for k, v in patch.get(\"replace\", {}).items():\n            genesis[k] = v\n            sys.stderr.write(\"replaced item {k}\\n\".format(k=k))\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/canonical_format.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\nif len(sys.argv) < 3:\n    print(\"syntax: \"+sys.argv[0]+\" INFILE OUTFILE\")\n    sys.exit(0)\n\nwith open(sys.argv[1], \"r\") as infile:\n    genesis = json.load(infile)\nwith open(sys.argv[2], \"w\") as outfile:\n    json.dump(genesis, outfile, separators=(',', ':'), sort_keys=True)\n"
  },
  {
    "path": "programs/genesis_util/change_asset_symbol.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Change an asset's symbol with referential integrity\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-f\", \"--from\", metavar=\"PREFIX\", default=\"\", help=\"initial prefix\")\n    parser.add_argument(\"-t\", \"--to\", metavar=\"PREFIX\", default=\"\", help=\"new prefix\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    frum = opts.__dict__[\"from\"]    # from is a language keyword and cannot be an attribute name\n\n    for asset in genesis[\"initial_assets\"]:\n        if asset[\"symbol\"] == frum:\n            asset[\"symbol\"] = opts.to\n\n    for balance in genesis[\"initial_balances\"]:\n        if balance[\"asset_symbol\"] == frum:\n            balance[\"asset_symbol\"] = opts.to\n\n    for vb in genesis[\"initial_vesting_balances\"]:\n        if balance[\"asset_symbol\"] == frum:\n            balance[\"asset_symbol\"] = opts.to\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/change_bitasset_owners.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Change initial_assets owned by the witness-account to the committee-account\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    for asset in genesis[\"initial_assets\"]:\n        if asset[\"issuer_name\"] == \"witness-account\":\n            asset[\"issuer_name\"] = \"committee-account\"\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/change_key_prefix.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Set is_prefixed=false for all asset owners\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-f\", \"--from\", metavar=\"PREFIX\", default=\"\", help=\"initial prefix\")\n    parser.add_argument(\"-t\", \"--to\", metavar=\"PREFIX\", default=\"\", help=\"new prefix\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    frum = opts.__dict__[\"from\"]    # from is a language keyword and cannot be an attribute name\n\n    def convert(k):\n        if not k.startswith(frum):\n            print(\"key {k} does not start with prefix {p}\".format(k=repr(k), p=repr(frum)))\n            raise RuntimeError()\n        return opts.to + k[len(frum):]\n\n    for account in genesis[\"initial_accounts\"]:\n        account[\"owner_key\"] = convert(account[\"owner_key\"])\n        account[\"active_key\"] = convert(account[\"active_key\"])\n\n    for asset in genesis[\"initial_assets\"]:\n        for cr in asset.get(\"collateral_records\", []):\n            cr[\"owner\"] = convert(cr[\"owner\"])\n\n    for balance in genesis[\"initial_balances\"]:\n        balance[\"owner\"] = convert(balance[\"owner\"])\n\n    for vb in genesis[\"initial_vesting_balances\"]:\n        vb[\"owner\"] = convert(vb[\"owner\"])\n\n    for witness in genesis[\"initial_witness_candidates\"]:\n        witness[\"block_signing_key\"] = convert(witness[\"block_signing_key\"])\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/convert_address.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/**\n * Convert BTC / PTS addresses to a Graphene address.\n */\n\n#include <graphene/protocol/pts_address.hpp>\n#include <graphene/protocol/address.hpp>\n\n#include <iostream>\n#include <string>\n\nusing namespace graphene::protocol;\n\nint main(int argc, char** argv)\n{\n   // grab 0 or more whitespace-delimited PTS addresses from stdin\n   std::string s;\n   while( std::cin >> s )\n   {\n      std::cout << std::string( address( pts_address( s ) ) ) << std::endl;\n   }\n   return 0;\n}\n"
  },
  {
    "path": "programs/genesis_util/create_bloom_filter.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport hashlib\nimport json\nimport sys\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Set is_prefixed=false for all asset owners\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-n\", \"--num\", metavar=\"N\", default=3, type=int, help=\"number of hashes per key (default: 3)\")\n    parser.add_argument(\"-s\", \"--size\", metavar=\"BITS\", default=8*1048576, type=int, help=\"number of bits in filter\")\n    parser.add_argument(\"-a\", \"--algorithm\", metavar=\"NAME\", default=\"sha256\", type=str, help=\"hash algorithm (must exist in hashlib)\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    keys = set()\n\n    for account in genesis[\"initial_accounts\"]:\n        keys.add(account[\"owner_key\"])\n        keys.add(account[\"active_key\"])\n\n    for asset in genesis[\"initial_assets\"]:\n        for cr in asset.get(\"collateral_records\", []):\n            keys.add(cr[\"owner\"])\n\n    for balance in genesis[\"initial_balances\"]:\n        keys.add(balance[\"owner\"])\n\n    for vb in genesis[\"initial_vesting_balances\"]:\n        keys.add(vb[\"owner\"])\n\n    for witness in genesis[\"initial_witness_candidates\"]:\n        keys.add(witness[\"block_signing_key\"])\n\n    sys.stderr.write(\"got {n} distinct keys\\n\".format(n=len(keys)))\n\n    keys = [(str(i) + \":\" + k).encode(\"UTF-8\") for k in sorted(keys) for i in range(opts.num)]\n\n    data = bytearray((opts.size + 7) >> 3)\n\n    h = getattr(hashlib, opts.algorithm)\n    for k in keys:\n        address = int(h(k).hexdigest(), 16) % opts.size\n        data[address >> 3] |= (1 << (address & 7))\n\n    popcount = [bin(i).count(\"1\") for i in range(256)]\n    w = sum(popcount[x] for x in data)\n    sys.stderr.write(\"\"\"w={w}   o={o:.3%}   p={p:.3%}\nw: Hamming weight    o: Occupancy    p: False positive probability\n\"\"\".format(\nw=w,\no=float(w) / float(opts.size),\np=(float(w) / float(opts.size))**opts.num,\n))\n\n    if opts.output == \"-\":\n        sys.stdout.write(data)\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"wb\") as f:\n            f.write(data)\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/generate_account_patch.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport subprocess\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Generate a patch file that adds init accounts\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-a\", \"--accounts\", metavar=\"ACCOUNTS\", default=\"-\", help=\"file containing name, balances to create\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    parser.add_argument(\"-s\", \"--secret\", metavar=\"SECRET\", default=None, help=\"private key generation secret\")\n    opts = parser.parse_args()\n\n    if opts.secret is None:\n        sys.stderr.write(\"missing required parameter --secret\\n\")\n        sys.stderr.flush()\n        sys.exit(1)\n\n    with open(opts.accounts, \"r\") as f:\n        accounts = json.load(f)\n\n    initial_accounts = []\n    initial_balances = []\n    for e in accounts:\n        name = e[\"name\"]\n        owner_str = subprocess.check_output([\"programs/genesis_util/get_dev_key\", opts.secret, \"owner-\"+name]).decode(\"utf-8\")\n        active_str = subprocess.check_output([\"programs/genesis_util/get_dev_key\", opts.secret, \"active-\"+name]).decode(\"utf-8\")\n        owner = json.loads(owner_str)\n        active = json.loads(active_str)\n        initial_accounts.append({\n            \"name\" : name,\n            \"owner_key\" : owner[0][\"public_key\"],\n            \"active_key\" : active[0][\"public_key\"],\n            \"is_lifetime_member\" : True,\n            })\n        for bal in e.get(\"balances\", []):\n            bal = dict(bal)\n            bal[\"owner\"] = active[0][\"address\"]\n            initial_balances.append(bal)\n    result = {\n       \"append\" : {\n       \"initial_accounts\" : initial_accounts },\n    }\n    if len(initial_balances) > 0:\n        result[\"append\"][\"initial_balances\"] = initial_balances\n\n    if opts.output == \"-\":\n        dump_json( result, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( result, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/generate_init_config.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport subprocess\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Generate a patch file that adds init accounts\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-n\", \"--num\", metavar=\"N\", default=11, type=int, help=\"number of init witnesses\")\n    parser.add_argument(\"-w\", \"--witness\", metavar=\"N\", default=1, type=int, help=\"starting witness ID\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    parser.add_argument(\"-m\", \"--mname\", metavar=\"HOSTNAME\", default=\"\", help=\"machine name of target machine\")\n    parser.add_argument(\"-s\", \"--secret\", metavar=\"SECRET\", default=None, help=\"private key generation secret\")\n    opts = parser.parse_args()\n\n    if opts.secret is None:\n        sys.stderr.write(\"missing required parameter --secret\\n\")\n        sys.stderr.flush()\n        sys.exit(1)\n\n    out_wits = []\n    out_keys = []\n\n    for i in range(opts.num):\n        if opts.mname != \"\":\n           istr = \"wit-block-signing-\"+opts.mname+\"-\"+str(i)\n        else:\n           istr = \"wit-block-signing-\"+str(i)\n        prod_str = subprocess.check_output([\"programs/genesis_util/get_dev_key\", opts.secret, istr]).decode(\"utf-8\")\n        prod = json.loads(prod_str)\n        out_wits.append('witness-id = \"1.6.'+str(opts.witness+i)+'\"\\n')\n        out_keys.append(\"private-key = \"+json.dumps([prod[0][\"public_key\"], prod[0][\"private_key\"]])+\"\\n\")\n\n    out_data = \"\".join(out_wits + [\"\\n\"] + out_keys)\n\n    if opts.output == \"-\":\n        sys.stdout.write(out_data)\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            f.write(out_data)\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/generate_init_patch.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport subprocess\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Generate a patch file that adds init accounts\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-n\", \"--num\", metavar=\"N\", default=11, type=int, help=\"number of init witnesses\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    parser.add_argument(\"-s\", \"--secret\", metavar=\"SECRET\", default=None, help=\"private key generation secret\")\n    opts = parser.parse_args()\n\n    if opts.secret is None:\n        sys.stderr.write(\"missing required parameter --secret\\n\")\n        sys.stderr.flush()\n        sys.exit(1)\n\n    wit_accounts = []\n    wit_wits = []\n    committee = []\n\n    for i in range(opts.num):\n        owner_str = subprocess.check_output([\"programs/genesis_util/get_dev_key\", opts.secret, \"wit-owner-\"+str(i)]).decode(\"utf-8\")\n        active_str = subprocess.check_output([\"programs/genesis_util/get_dev_key\", opts.secret, \"wit-active-\"+str(i)]).decode(\"utf-8\")\n        prod_str = subprocess.check_output([\"programs/genesis_util/get_dev_key\", opts.secret, \"wit-block-signing-\"+str(i)]).decode(\"utf-8\")\n        owner = json.loads(owner_str)\n        active = json.loads(active_str)\n        prod = json.loads(prod_str)\n        wit_accounts.append({\n            \"name\" : \"init\"+str(i),\n            \"owner_key\" : owner[0][\"public_key\"],\n            \"active_key\" : active[0][\"public_key\"],\n            \"is_lifetime_member\" : True,\n            })\n        wit_wits.append({\n            \"owner_name\" : \"init\"+str(i),\n            \"block_signing_key\" : prod[0][\"public_key\"],\n            })\n        committee.append({\"owner_name\" : \"init\"+str(i)})\n    result = {\n       \"append\" : {\n       \"initial_accounts\" : wit_accounts },\n       \"replace\" : {\n       \"initial_active_witnesses\" : opts.num,\n       \"initial_worker_candidates\" : [],\n       \"initial_witness_candidates\" : wit_wits,\n       \"initial_committee_candidates\" : committee,\n        }\n    }\n\n    if opts.output == \"-\":\n        dump_json( result, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( result, f, opts.pretty )\n  \n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/genesis_update.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n\n#include <fc/io/fstream.hpp>\n#include <fc/io/json.hpp>\n#include <fc/io/stdio.hpp>\n\n#include <graphene/app/api.hpp>\n#include <graphene/protocol/address.hpp>\n#include <graphene/egenesis/egenesis.hpp>\n#include <graphene/utilities/key_conversion.hpp>\n\n#include <boost/filesystem.hpp>\n\n#ifndef WIN32\n#include <csignal>\n#endif\n\nusing namespace graphene::app;\nusing namespace graphene::chain;\nusing namespace graphene::utilities;\nusing namespace std;\nnamespace bpo = boost::program_options;\n\n// hack:  import create_example_genesis() even though it's a way, way\n// specific internal detail\nnamespace graphene { namespace app { namespace detail {\ngenesis_state_type create_example_genesis();\n} } } // graphene::app::detail\n\nint main( int argc, char** argv )\n{\n   try\n   {\n      bpo::options_description cli_options(\"BitShares empty blocks\");\n      cli_options.add_options()\n            (\"help,h\", \"Print this help message and exit.\")\n            (\"genesis-json,g\", bpo::value<boost::filesystem::path>(), \"File to read genesis state from\")\n            (\"out,o\", bpo::value<boost::filesystem::path>(), \"File to output new genesis to\")\n            (\"dev-account-prefix\", bpo::value<std::string>()->default_value(\"devacct\"), \"Prefix for dev accounts\")\n            (\"dev-key-prefix\", bpo::value<std::string>()->default_value(\"devkey-\"), \"Prefix for dev key\")\n            (\"dev-account-count\", bpo::value<uint32_t>()->default_value(0), \"Prefix for dev accounts\")\n            (\"dev-balance-count\", bpo::value<uint32_t>()->default_value(0), \"Prefix for dev balances\")\n            (\"dev-balance-amount\", bpo::value<uint64_t>()->default_value(uint64_t(1000)*uint64_t(1000)*uint64_t(100000)), \"Amount in each dev balance\")\n            ;\n\n      bpo::variables_map options;\n      try\n      {\n         boost::program_options::store( boost::program_options::parse_command_line(argc, argv, cli_options), options );\n      }\n      catch (const boost::program_options::error& e)\n      {\n         std::cerr << \"empty_blocks:  error parsing command line: \" << e.what() << \"\\n\";\n         return 1;\n      }\n\n      if( options.count(\"help\") > 0 )\n      {\n         std::cout << cli_options << \"\\n\";\n         return 1;\n      }\n\n      if( options.count( \"genesis-json\" ) == 0 )\n      {\n         std::cerr << \"--genesis-json option is required\\n\";\n         return 1;\n      }\n\n      if( options.count( \"out\" ) == 0 )\n      {\n         std::cerr << \"--out option is required\\n\";\n         return 1;\n      }\n\n      genesis_state_type genesis;\n      if( options.count(\"genesis-json\") > 0 )\n      {\n         fc::path genesis_json_filename = options[\"genesis-json\"].as<boost::filesystem::path>();\n         std::cerr << \"update_genesis:  Reading genesis from file \" << genesis_json_filename.preferred_string() << \"\\n\";\n         std::string genesis_json;\n         read_file_contents( genesis_json_filename, genesis_json );\n         genesis = fc::json::from_string( genesis_json ).as< genesis_state_type >(20);\n      }\n      else\n      {\n         std::cerr << \"update_genesis:  Using example genesis\\n\";\n         genesis = graphene::app::detail::create_example_genesis();\n      }\n\n      const std::string dev_key_prefix = options[\"dev-key-prefix\"].as<std::string>();\n\n      auto get_dev_key = [&dev_key_prefix]( std::string prefix, uint32_t i )\n      {\n         return fc::ecc::private_key::regenerate( fc::sha256::hash( dev_key_prefix + prefix + std::to_string(i) ) ).get_public_key();\n      };\n\n      uint32_t dev_account_count = options[\"dev-account-count\"].as<uint32_t>();\n      std::string dev_account_prefix = options[\"dev-account-prefix\"].as<std::string>();\n      for(uint32_t i=0;i<dev_account_count;i++)\n      {\n         genesis_state_type::initial_account_type acct(\n            dev_account_prefix+std::to_string(i),\n            get_dev_key( \"owner-\", i ),\n            get_dev_key( \"active-\", i ),\n            false );\n\n         genesis.initial_accounts.push_back( acct );\n      }\n\n      uint32_t dev_balance_count = options[\"dev-balance-count\"].as<uint32_t>();\n      uint64_t dev_balance_amount = options[\"dev-balance-amount\"].as<uint64_t>();\n      for(uint32_t i=0;i<dev_balance_count;i++)\n      {\n         genesis_state_type::initial_balance_type bal;\n         bal.owner = address( get_dev_key( \"balance-\", i ) );\n         bal.asset_symbol = \"CORE\";\n         bal.amount = dev_balance_amount;\n         genesis.initial_balances.push_back( bal );\n      }\n\n      std::map< std::string, size_t > name2index;\n      size_t num_accounts = genesis.initial_accounts.size();\n      for( size_t i=0; i<num_accounts; i++ )\n         name2index[ genesis.initial_accounts[i].name ] = i;\n\n      for( uint32_t i=0; i<genesis.initial_active_witnesses; i++ )\n      {\n         genesis_state_type::initial_witness_type& wit = genesis.initial_witness_candidates[ i ];\n         genesis_state_type::initial_account_type& wit_acct = genesis.initial_accounts[ name2index[ wit.owner_name ] ];\n         if( wit.owner_name.substr(0, 4) != \"init\" )\n         {\n            std::cerr << \"need \" << genesis.initial_active_witnesses << \" init accounts as first entries in initial_active_witnesses\\n\";\n            return 1;\n         }\n         wit.block_signing_key = get_dev_key( \"wit-block-signing-\", i );\n         wit_acct.owner_key = get_dev_key( \"wit-owner-\", i );\n         wit_acct.active_key = get_dev_key( \"wit-active-\", i );\n      }\n\n      fc::path output_filename = options[\"out\"].as<boost::filesystem::path>();\n      fc::json::save_to_file( genesis, output_filename );\n   }\n   catch ( const fc::exception& e )\n   {\n      std::cout << e.to_detail_string() << \"\\n\";\n      return 1;\n   }\n   return 0;\n}\n"
  },
  {
    "path": "programs/genesis_util/get_dev_key.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <iostream>\n#include <string>\n\n#include <fc/crypto/elliptic.hpp>\n#include <fc/io/json.hpp>\n\n#include <graphene/protocol/address.hpp>\n#include <graphene/protocol/types.hpp>\n#include <graphene/utilities/key_conversion.hpp>\n\n#ifndef WIN32\n#include <csignal>\n#endif\n\nusing namespace std;\n\nint main( int argc, char** argv )\n{\n   try\n   {\n      std::string dev_key_prefix;\n      bool need_help = false;\n      if( argc < 3 ) // requires at least a prefix and a suffix\n         need_help = true;\n      else\n      {\n         dev_key_prefix = argv[1];\n         if(  (dev_key_prefix == \"-h\")\n           || (dev_key_prefix == \"--help\")\n           )\n           need_help = true;\n      }\n\n      if( need_help )\n      {\n         std::cerr << \"\\nThis program generates keys with specified prefix and suffix(es) as seed(s).\\n\\n\"\n             \"Syntax:\\n\\n\"\n             \"  get_dev_key <prefix> <suffix> ...\\n\\n\"\n             \"Examples:\\n\\n\"\n             \"  get_dev_key nath an\\n\"\n             \"  get_dev_key wxyz- owner-5 active-7 balance-9 wit-block-signing-3 wit-owner-5 wit-active-33\\n\"\n             \"  get_dev_key wxyz- wit-block-signing-0:101\\n\"\n             \"\\n\";\n         return 1;\n      }\n\n      bool comma = false;\n\n      auto show_key = [&comma]( const fc::ecc::private_key& priv_key )\n      {\n         fc::limited_mutable_variant_object mvo(5);\n         graphene::protocol::public_key_type pub_key = priv_key.get_public_key();\n         mvo( \"private_key\", graphene::utilities::key_to_wif( priv_key ) )\n            ( \"public_key\", std::string( pub_key ) )\n            ( \"address\", graphene::protocol::address( pub_key ) )\n            ;\n         if( comma )\n            std::cout << \",\\n\";\n         std::cout << fc::json::to_string( fc::mutable_variant_object(mvo) );\n         comma = true;\n      };\n\n      std::cout << \"[\";\n\n      for( int i=2; i<argc; i++ )\n      {\n         std::string arg = argv[i];\n         std::string prefix;\n         int lep = -1, rep = -1;\n         auto dash_pos = arg.rfind('-');\n         if( dash_pos != string::npos )\n         {\n            std::string lhs = arg.substr( 0, dash_pos+1 );\n            std::string rhs = arg.substr( dash_pos+1 );\n            auto colon_pos = rhs.find(':');\n            if( colon_pos != string::npos )\n            {\n               prefix = lhs;\n               lep = std::stoi( rhs.substr( 0, colon_pos ) );\n               rep = std::stoi( rhs.substr( colon_pos+1 ) );\n            }\n         }\n         if( lep >= 0 )\n         {\n            for( int k=lep; k<rep; k++ )\n            {\n               std::string s = dev_key_prefix + prefix + std::to_string(k);\n               show_key( fc::ecc::private_key::regenerate( fc::sha256::hash( s ) ) );\n            }\n         }\n         else\n         {\n            show_key( fc::ecc::private_key::regenerate( fc::sha256::hash( dev_key_prefix + arg ) ) );\n         }\n      }\n      std::cout << \"]\\n\";\n   }\n   catch ( const fc::exception& e )\n   {\n      std::cerr << e.to_detail_string() << \"\\n\";\n      return 1;\n   }\n   return 0;\n}\n"
  },
  {
    "path": "programs/genesis_util/prefix_accounts.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Add a prefix to selected account names\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-b\", \"--begin\", metavar=\"PREFIX\", default=\"\", help=\"prefix to add\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    taken_names = set()\n    unsettled_names = []\n    name_map = {}\n    prefix = opts.begin\n\n    for account in genesis[\"initial_accounts\"]:\n        is_prefixed = account[\"is_prefixed\"]\n        name = account[\"name\"]\n        if is_prefixed:\n            unsettled_names.append(name)\n            name_map[name] = name\n        else:\n            taken_names.add(name)\n\n    pass_num = 0\n    while len(unsettled_names) > 0:\n        num_resolved = 0\n        pass_num += 1\n        sys.stderr.write(\"attempting to resolve {n} names\\n\".format(n=len(unsettled_names)))\n        if pass_num > 1:\n            sys.stderr.write(\"names: {}\\n\".format(\"\\n\".join(unsettled_names)))\n        new_unsettled_names = []\n        for name in unsettled_names:\n            new_name = prefix+name_map[name]\n            name_map[name] = new_name\n            if new_name in taken_names:\n                new_unsettled_names.append(name)\n            else:\n                taken_names.add(name)\n                num_resolved += 1\n        sys.stderr.write(\"resolved {n} names\\n\".format(n=num_resolved))\n        unsettled_names = new_unsettled_names\n\n    for account in genesis[\"initial_accounts\"]:\n        name = account[\"name\"]\n        account[\"name\"] = name_map.get(name, name)\n        del account[\"is_prefixed\"]\n    for asset in genesis[\"initial_assets\"]:\n        issuer_name = asset[\"issuer_name\"]\n        asset[\"issuer_name\"] = name_map.get(issuer_name, issuer_name)\n    for witness in genesis[\"initial_witness_candidates\"]:\n        owner_name = witness[\"owner_name\"]\n        witness[\"owner_name\"] = name_map.get(owner_name, owner_name)\n    for committee in genesis[\"initial_committee_candidates\"]:\n        owner_name = member[\"owner_name\"]\n        member[\"owner_name\"] = name_map.get(owner_name, owner_name)\n    for worker in genesis[\"initial_worker_candidates\"]:\n        owner_name = worker[\"owner_name\"]\n        worker[\"owner_name\"] = name_map.get(owner_name, owner_name)\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/python_format.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\nif len(sys.argv) < 3:\n    print(\"syntax: \"+sys.argv[0]+\" INFILE OUTFILE\")\n    sys.exit(0)\n\nwith open(sys.argv[1], \"r\") as infile:\n    genesis = json.load(infile)\nwith open(sys.argv[2], \"w\") as outfile:\n    json.dump(genesis, outfile, indent=2, sort_keys=True)\n"
  },
  {
    "path": "programs/genesis_util/remove.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Remove entities from snapshot\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-a\", \"--asset\", metavar=\"ASSETS\", nargs=\"+\", help=\"list of asset(s) to delete\")    \n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)        \n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    if opts.asset is None: \n        opts.asset = []\n    rm_asset_set = set(opts.asset)\n\n    removed_asset_entries = {aname : 0 for aname in opts.asset}\n    new_initial_assets = []\n    for asset in genesis[\"initial_assets\"]:\n        symbol = asset[\"symbol\"]\n        if symbol not in rm_asset_set:\n            new_initial_assets.append(asset)\n        else:\n            removed_asset_entries[symbol] += 1\n    genesis[\"initial_assets\"] = new_initial_assets\n\n    removed_balance_entries = {aname : [] for aname in opts.asset}\n    new_initial_balances = []\n    for balance in genesis[\"initial_balances\"]:\n        symbol = balance[\"asset_symbol\"]\n        if symbol not in rm_asset_set:\n            new_initial_balances.append(balance)\n        else:\n            removed_balance_entries[symbol].append(balance)\n    genesis[\"initial_balances\"] = new_initial_balances\n    # TODO:  Remove from initial_vesting_balances\n\n    for aname in opts.asset:\n        sys.stderr.write(\n           \"Asset {sym} removed {acount} initial_assets, {bcount} initial_balances totaling {btotal}\\n\".format(\n              sym=aname,\n              acount=removed_asset_entries[aname],\n              bcount=len(removed_balance_entries[aname]),\n              btotal=sum(int(e[\"amount\"]) for e in removed_balance_entries[aname]),\n              ))\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/sort_objects.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Sort initial_accounts and initial_assets by \\\"id\\\" member, then remove \\\"id\\\" member\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    genesis[\"initial_assets\"].sort( key=lambda e : e[\"id\"] )\n    genesis[\"initial_accounts\"].sort( key=lambda e : e[\"id\"] )\n\n    for e in genesis[\"initial_assets\"]:\n        del e[\"id\"]\n    for e in genesis[\"initial_accounts\"]:\n        del e[\"id\"]\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/unprefix_asset_owners.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Set is_prefixed=false for all asset owners\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    asset_owners = set()\n    for asset in genesis[\"initial_assets\"]:\n        asset_owners.add(asset[\"issuer_name\"])\n    for account in genesis[\"initial_accounts\"]:\n        if account[\"name\"] in asset_owners:\n            account[\"is_prefixed\"] = False\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/unprefix_names.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\ndef load_names(infile):\n    names = set()\n    for line in infile:\n        if '#' in line:\n            line = line[:line.index('#')]\n        line = line.strip()\n        if line == \"\":\n            continue\n        names.add(line)\n    return names\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Set is_prefixed=False for a list of names\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-n\", \"--names\", metavar=\"NAMES\", help=\"list of names to unprefix\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    if opts.names == \"-\":\n        names = load_names(sys.stdin)\n    else:\n        with open(opts.names, \"r\") as f:\n            names = load_names(f)\n\n    for account in genesis[\"initial_accounts\"]:\n        if account[\"name\"] in names:\n            account[\"is_prefixed\"] = False\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/genesis_util/upgrade_members.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport json\nimport re\nimport sys\n\ndef dump_json(obj, out, pretty):\n    if pretty:\n        json.dump(obj, out, indent=2, sort_keys=True)\n    else:\n        json.dump(obj, out, separators=(\",\", \":\"), sort_keys=True)\n    return\n\nre_init = re.compile(r\"^init[0-9]+$\")\n\ndef load_names(infile):\n    names = set()\n    for line in infile:\n        if '#' in line:\n            line = line[:line.index('#')]\n        line = line.strip()\n        if line == \"\":\n            continue\n        names.add(line)\n    return names\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Upgrade a list of members\")\n    parser.add_argument(\"-o\", \"--output\", metavar=\"OUT\", default=\"-\", help=\"output filename (default: stdout)\")\n    parser.add_argument(\"-i\", \"--input\", metavar=\"IN\", default=\"-\", help=\"input filename (default: stdin)\")\n    parser.add_argument(\"-n\", \"--names\", metavar=\"NAMES\", default=\"\", help=\"file containing names to upgrade\")\n    parser.add_argument(\"-p\", \"--pretty\", action=\"store_true\", default=False, help=\"pretty print output\")\n    opts = parser.parse_args()\n\n    if opts.input == \"-\":\n        genesis = json.load(sys.stdin)\n    else:\n        with open(opts.input, \"r\") as f:\n            genesis = json.load(f)\n\n    if opts.names == \"-\":\n        names = load_names(sys.stdin)\n    else:\n        with open(opts.names, \"r\") as f:\n            names = load_names(f)\n\n    for account in genesis[\"initial_accounts\"]:\n        if account[\"name\"] in names:\n            account[\"is_lifetime_member\"] = True\n\n    if opts.output == \"-\":\n        dump_json( genesis, sys.stdout, opts.pretty )\n        sys.stdout.flush()\n    else:\n        with open(opts.output, \"w\") as f:\n            dump_json( genesis, f, opts.pretty )\n    return\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "programs/js_operation_serializer/CMakeLists.txt",
    "content": "add_executable( js_operation_serializer main.cpp )\nif( UNIX AND NOT APPLE )\n  set(rt_library rt )\nendif()\n\ntarget_link_libraries( js_operation_serializer\n                       PRIVATE graphene_app graphene_net graphene_chain graphene_egenesis_none graphene_utilities graphene_wallet fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\ninstall( TARGETS\n   js_operation_serializer\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "programs/js_operation_serializer/main.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/protocol/block.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/confidential_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n\n#include <iostream>\n\nusing namespace graphene::chain;\nusing namespace graphene::custom_operations;\n\nnamespace detail_ns {\n\nstring remove_tail_if( const string& str, char c, const string& match )\n{\n   auto last = str.find_last_of( c );\n   if( last != std::string::npos )\n      if( str.substr( last + 1 ) == match )\n         return str.substr( 0, last );\n   return str;\n}\nstring remove_namespace_if( const string& str, const string& match )\n{\n   auto last = str.find( match );\n   if( last != std::string::npos )\n      return str.substr( match.size()+2 );\n   return str;\n}\n\n\nstring remove_namespace( string str )\n{\n   str = remove_tail_if( str, '_', \"operation\" );\n   str = remove_tail_if( str, '_', \"t\" );\n   str = remove_tail_if( str, '_', \"object\" );\n   str = remove_tail_if( str, '_', \"type\" );\n   str = remove_namespace_if( str, \"graphene::chain\" );\n   str = remove_namespace_if( str, \"graphene::db\" );\n   str = remove_namespace_if( str, \"std\" );\n   str = remove_namespace_if( str, \"fc\" );\n   auto pos = str.find( \":\" );\n   if( pos != str.npos )\n      str.replace( pos, 2, \"_\" );\n   return str;\n}\n\n\n\n\ntemplate<typename T>\nvoid generate_serializer();\ntemplate<typename T>\nvoid register_serializer();\n\n\nmap<string, size_t >                st;\nvector<std::function<void()>>       serializers;\n\nbool register_serializer( const string& name, std::function<void()> sr )\n{\n   if( st.find(name) == st.end() )\n   {\n      serializers.push_back( sr );\n      st[name] = serializers.size() - 1;\n      return true;\n   }\n   return false;\n}\n\ntemplate<typename T> struct js_name { static std::string name(){ return  remove_namespace(fc::get_typename<T>::name()); }; };\n\ntemplate<typename T, size_t N>\nstruct js_name<std::array<T,N>>\n{\n   static std::string name(){ return  \"fixed_array \"+ fc::to_string(N) + \", \" \n                                      + remove_namespace(fc::get_typename<T>::name()); };\n};\ntemplate<size_t N>   struct js_name<std::array<char,N>>   { static std::string name(){ return  \"bytes(\"+ fc::to_string(N) + \")\"; }; };\ntemplate<size_t N>   struct js_name<std::array<uint8_t,N>>{ static std::string name(){ return  \"bytes(\"+ fc::to_string(N) + \")\"; }; };\ntemplate<typename T> struct js_name< fc::optional<T> >    { static std::string name(){ return \"optional(\" + js_name<T>::name() + \")\"; } };\ntemplate<>           struct js_name< object_id_type >     { static std::string name(){ return \"object_id_type\"; } };\ntemplate<typename T> struct js_name< fc::flat_set<T> >    { static std::string name(){ return \"set(\" + js_name<T>::name() + \")\"; } };\ntemplate<typename T> struct js_name< std::vector<T> >     { static std::string name(){ return \"array(\" + js_name<T>::name() + \")\"; } };\ntemplate<typename T> struct js_name< fc::safe<T> > { static std::string name(){ return js_name<T>::name(); } };\n\n\ntemplate<> struct js_name< std::vector<char> > { static std::string name(){ return \"bytes()\";     } };\ntemplate<> struct js_name<fc::uint160>         { static std::string name(){ return \"bytes(20)\";   } };\ntemplate<> struct js_name<fc::sha1>            { static std::string name(){ return \"bytes(20)\";   } };\ntemplate<> struct js_name<fc::sha224>          { static std::string name(){ return \"bytes(28)\";   } };\ntemplate<> struct js_name<fc::sha256>          { static std::string name(){ return \"bytes(32)\";   } };\ntemplate<> struct js_name<fc::unsigned_int>    { static std::string name(){ return \"varuint64\";  } };\ntemplate<> struct js_name< vote_id_type >      { static std::string name(){ return \"vote_id\";    } };\ntemplate<> struct js_name< time_point_sec >    { static std::string name(){ return \"time_point_sec\"; } };\n\ntemplate<uint8_t S, uint8_t T>\nstruct js_name<graphene::protocol::object_id<S,T> >\n{\n   static std::string name(){\n      return \"protocol_id_type(\\\"\" +\n             remove_namespace(fc::get_typename<object_downcast_t<object_id<S,T>>>::name()) + \"\\\")\";\n   };\n};\n\n\ntemplate<typename T> struct js_name< std::set<T> > { static std::string name(){ return \"set \" + js_name<T>::name(); } };\n\ntemplate<typename K, typename V>\nstruct js_name< std::map<K,V> > { static std::string name(){ return \"map(\" + js_name<K>::name() + \", \" + js_name<V>::name() +\")\"; } };\n\ntemplate<typename K, typename V>\nstruct js_name< fc::flat_map<K,V> > { static std::string name(){ return \"map(\" + js_name<K>::name() + \", \" + js_name<V>::name() +\")\"; } };\n\n\ntemplate<typename... T> struct js_sv_name;\n\ntemplate<typename A> struct js_sv_name<A>\n{ static std::string name(){ return  \"\\n    \" + js_name<A>::name(); } };\n\ntemplate<typename A, typename... T>\nstruct js_sv_name<A,T...> { static std::string name(){ return  \"\\n    \" + js_name<A>::name() +\",\" + js_sv_name<T...>::name(); } };\n\ntemplate<typename... T>\nstruct js_name< fc::static_variant<T...> >\n{\n   static std::string name( std::string n = \"\"){\n      static const std::string name = n;\n      if( name == \"\" )\n         return \"static_variant([\" + js_sv_name<T...>::name() + \"\\n]);\";\n      else return name;\n   }\n};\ntemplate<>\nstruct js_name< fc::static_variant<> >\n{\n   static std::string name( std::string n = \"\"){\n      static const std::string name = n;\n      if( name == \"\" )\n         return \"static_variant([]);\";\n      else return name;\n   }\n};\n\n\n\ntemplate<typename T, bool reflected = fc::reflector<T>::is_defined::value>\nstruct serializer;\n\n\nstruct register_type_visitor\n{\n   typedef void result_type;\n\n   template<typename Type>\n   result_type operator()( const Type& op )const { serializer<Type>::init(); }\n};\n\nclass register_member_visitor;\n\nstruct serialize_type_visitor\n{\n   typedef void result_type;\n\n   int t = 0;\n   serialize_type_visitor(int _t ):t(_t){}\n\n   template<typename Type>\n   result_type operator()( const Type& op )const\n   {\n      std::cout << \"    \" <<remove_namespace( fc::get_typename<Type>::name() )  <<\": \"<<t<<\"\\n\";\n   }\n};\n\n\nclass serialize_member_visitor\n{\n   public:\n      template<typename Member, class Class, Member (Class::*member)>\n      void operator()( const char* name )const\n      {\n         std::cout << \"    \" << name <<\": \" << js_name<Member>::name() <<\",\\n\";\n      }\n};\n\ntemplate<typename T>\nstruct serializer<T,false>\n{\n   static_assert( !fc::reflector<T>::is_defined::value, \"invalid template arguments\" );\n   static void init()\n   {}\n\n   static void generate()\n   {}\n};\n\ntemplate<typename T, size_t N>\nstruct serializer<std::array<T,N>,false>\n{\n   static void init() { serializer<T>::init(); }\n   static void generate() {}\n};\ntemplate<typename T>\nstruct serializer<std::vector<T>,false>\n{\n   static void init() { serializer<T>::init(); }\n   static void generate() {}\n};\n\ntemplate<>\nstruct serializer<std::vector<operation>,false>\n{\n   static void init() { }\n   static void generate() {}\n};\n\ntemplate<>\nstruct serializer<object_id_type,true>\n{\n   static void init() {}\n\n   static void generate() {}\n};\ntemplate<>\nstruct serializer<uint64_t,false>\n{\n   static void init() {}\n   static void generate() {}\n};\ntemplate<> struct serializer<vote_id_type,false> { static void init() {} static void generate() {} };\n#ifdef __APPLE__\n// on mac, size_t is a distinct type from uint64_t or uint32_t and needs a separate specialization\ntemplate<> struct serializer<size_t,false> { static void init() {} static void generate() {} };\n#endif\ntemplate<> struct serializer<int64_t,false> { static void init() {} static void generate() {} };\ntemplate<> struct serializer<int64_t,true> { static void init() {} static void generate() {} };\n\ntemplate<typename T>\nstruct serializer<fc::optional<T>,false>\n{\n   static void init() { serializer<T>::init(); }\n   static void generate(){}\n};\n\ntemplate<uint8_t SpaceID, uint8_t TypeID>\nstruct serializer< graphene::db::object_id<SpaceID,TypeID> ,true>\n{\n   static void init() {}\n   static void generate() {}\n};\n\ntemplate<typename... T>\nstruct serializer< fc::static_variant<T...>, false >\n{\n   static void init()\n   {\n      static bool init = false;\n      if( !init )\n      {\n         init = true;\n         fc::static_variant<T...> var;\n         for( size_t i = 0; i < var.count(); ++i )\n         {\n            var.set_which(i);\n            var.visit( register_type_visitor() );\n         }\n         register_serializer( js_name<fc::static_variant<T...>>::name(), [=](){ generate(); } );\n      }\n   }\n\n   static void generate()\n   {\n      std::cout << \"var \" <<  js_name<fc::static_variant<T...>>::name() << \" = static_variant([\" + js_sv_name<T...>::name() + \"\\n]);\\n\\n\";\n   }\n};\ntemplate<>\nstruct serializer< fc::static_variant<>, false >\n{\n   static void init()\n   {\n      static bool init = false;\n      if( !init )\n      {\n         init = true;\n         fc::static_variant<> var;\n         register_serializer( js_name<fc::static_variant<>>::name(), [=](){ generate(); } );\n      }\n   }\n\n   static void generate()\n   {\n      std::cout <<  js_name<fc::static_variant<>>::name() << \" = static_variant([]);\\n\\n\";\n   }\n};\n\n\nclass register_member_visitor\n{\n   public:\n      template<typename Member, class Class, Member (Class::*member)>\n      void operator()( const char* name )const\n      {\n         serializer<Member>::init();\n      }\n};\n\ntemplate<typename T, bool reflected>\nstruct serializer\n{\n   static_assert( fc::reflector<T>::is_defined::value, \"invalid template arguments\" );\n   static void init()\n   {\n      auto name = js_name<T>::name();\n      if( st.find(name) == st.end() )\n      {\n         fc::reflector<T>::visit( register_member_visitor() );\n         register_serializer( name, [=](){ generate(); } );\n      }\n   }\n\n   static void generate()\n   {\n      auto name = remove_namespace( js_name<T>::name() );\n      if( name == \"int64\" ) return;\n      std::cout << \"export const \" << name\n                << \" = new Serializer(\"\n                << \"\\\"\" + name + \"\\\", {\\n\";\n      fc::reflector<T>::visit( serialize_member_visitor() );\n      std::cout <<\"});\\n\\n\";\n   }\n};\n\n} // namespace detail_ns\n\nint main( int argc, char** argv )\n{\n   try {\n    operation op;\n\n    std::cout << \"ChainTypes.operations=\\n\";\n    for( size_t i = 0; i < op.count(); ++i )\n    {\n       op.set_which(i);\n       op.visit( detail_ns::serialize_type_visitor(i) );\n    }\n    std::cout << \"\\n\";\n\n    detail_ns::js_name<fee_parameters>::name(\"fee_parameters\");\n    detail_ns::js_name<operation>::name(\"operation\");\n    detail_ns::js_name<operation_result>::name(\"operation_result\");\n    detail_ns::js_name<future_extensions>::name(\"future_extensions\");\n    detail_ns::js_name<worker_initializer>::name(\"worker_initializer\");\n    detail_ns::js_name<predicate>::name(\"predicate\");\n    detail_ns::js_name<vesting_policy_initializer>::name(\"vesting_policy_initializer\");\n    detail_ns::serializer<fee_parameters>::init();\n    detail_ns::serializer<fee_schedule>::init();\n    detail_ns::serializer<signed_block>::init();\n    detail_ns::serializer<block_header>::init();\n    detail_ns::serializer<signed_block_header>::init();\n    detail_ns::serializer<operation>::init();\n    detail_ns::serializer<transaction>::init();\n    detail_ns::serializer<signed_transaction>::init();\n    detail_ns::serializer<account_storage_map>::init();\n\n    for( const auto& gen : detail_ns::serializers )\n       gen();\n\n  } catch ( const fc::exception& e ){ edump((e.to_detail_string())); }\n   return 0;\n}\n"
  },
  {
    "path": "programs/network_mapper/CMakeLists.txt",
    "content": "add_executable( network_mapper network_mapper.cpp )\ntarget_link_libraries( network_mapper fc graphene_chain graphene_net )\n\n"
  },
  {
    "path": "programs/network_mapper/network_mapper.cpp",
    "content": "#include <fc/crypto/elliptic.hpp>\n#include <fc/io/json.hpp>\n#include <fc/exception/exception.hpp>\n#include <fc/io/raw_variant.hpp>\n#include <fc/network/ip.hpp>\n#include <fc/network/resolve.hpp>\n#include <fc/thread/future.hpp>\n#include <fstream>\n#include <iostream>\n#include <queue>\n#include <future>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/net/peer_connection.hpp>\n\nclass peer_probe : public graphene::net::peer_connection_delegate\n{\npublic:\n  bool _connection_was_rejected = false;\n  bool _peer_closed_connection = false;\n  bool _we_closed_connection = false;\n  bool _done = false;\n  graphene::net::peer_connection_ptr _connection = graphene::net::peer_connection::make_shared(this);\n  fc::promise<void>::ptr _probe_complete_promise = fc::promise<void>::create(\"probe_complete\");\n  fc::future<void> _timeout_handler;\n\n  fc::ip::endpoint _remote;\n  graphene::net::node_id_t _node_id;\n  std::vector<graphene::net::address_info> _peers;\n\npublic:\n  explicit peer_probe( const fc::ip::endpoint& remote ) : _remote(remote) { /* Nothing else to do */ }\n\n  void start( const fc::ecc::public_key& my_node_id,\n              const fc::ecc::private_key& my_node_key,\n              const graphene::chain::chain_id_type& chain_id )\n  { try {\n    // This blocks\n    _connection->connect_to(_remote);\n\n    fc::sha256::encoder shared_secret_encoder;\n    fc::sha512 shared_secret = _connection->get_shared_secret();\n    shared_secret_encoder.write(shared_secret.data(), sizeof(shared_secret));\n    fc::ecc::compact_signature signature = my_node_key.sign_compact(shared_secret_encoder.result());\n\n    graphene::net::hello_message hello(\"network_mapper\",\n                                  GRAPHENE_NET_PROTOCOL_VERSION,\n                                  fc::ip::address(), 0, 0,\n                                  my_node_id,\n                                  signature,\n                                  chain_id,\n                                  fc::variant_object());\n\n    constexpr uint16_t timeout = 180; // seconds\n    _timeout_handler = fc::schedule( [this]() {\n        wlog( \"Communication with peer ${remote} took too long, closing connection\", (\"remote\", _remote) );\n        wdump( (_peers)(_peers.size()) );\n        _timeout_handler = fc::future<void>();\n        _we_closed_connection = true;\n        _connection->close_connection();\n      }, fc::time_point::now() + fc::seconds(timeout), \"timeout_handler\" );\n\n    _connection->send_message(hello);\n  } catch( const fc::exception& e ) {\n    ilog( \"Got exception when connecting to peer ${endpoint} ${e}\",\n          (\"endpoint\", _remote) (\"e\", e.to_detail_string()) );\n    _probe_complete_promise->set_exception( std::make_shared<fc::exception>(e) );\n  } }\n\n  void on_message(graphene::net::peer_connection* originating_peer,\n                  const graphene::net::message& received_message) override\n  {\n    graphene::net::message_hash_type message_hash = received_message.id();\n    dlog( \"handling message ${type} ${hash} size ${size} from peer ${endpoint}\",\n          (\"type\", graphene::net::core_message_type_enum(received_message.msg_type.value() ) )\n          (\"hash\", message_hash )\n          (\"size\", received_message.size )(\"endpoint\", originating_peer->get_remote_endpoint() ) );\n    switch ( received_message.msg_type.value() )\n    {\n    case graphene::net::core_message_type_enum::hello_message_type:\n      on_hello_message( originating_peer, received_message.as<graphene::net::hello_message>() );\n      break;\n    case graphene::net::core_message_type_enum::connection_accepted_message_type:\n      on_connection_accepted_message( originating_peer,\n          received_message.as<graphene::net::connection_accepted_message>() );\n      break;\n    case graphene::net::core_message_type_enum::connection_rejected_message_type:\n      on_connection_rejected_message( originating_peer,\n          received_message.as<graphene::net::connection_rejected_message>() );\n      break;\n    case graphene::net::core_message_type_enum::address_request_message_type:\n      on_address_request_message( originating_peer,\n          received_message.as<graphene::net::address_request_message>() );\n      break;\n    case graphene::net::core_message_type_enum::address_message_type:\n      on_address_message( originating_peer,\n          received_message.as<graphene::net::address_message>() );\n      break;\n    case graphene::net::core_message_type_enum::closing_connection_message_type:\n      on_closing_connection_message( originating_peer,\n          received_message.as<graphene::net::closing_connection_message>() );\n      break;\n    default:\n      break;\n    }\n  }\n\n  void on_hello_message(graphene::net::peer_connection* originating_peer,\n          const graphene::net::hello_message& hello_message_received)\n  {\n    _node_id = hello_message_received.node_public_key;\n    try {\n      if (hello_message_received.user_data.contains(\"node_id\"))\n        _node_id = hello_message_received.user_data[\"node_id\"].as<graphene::net::node_id_t>( 1 );\n    } catch( const fc::exception& ) {\n        dlog( \"Peer ${endpoint} sent us a hello message with an invalid node_id in user_data\",\n              (\"endpoint\", originating_peer->get_remote_endpoint() ) );\n    }\n    originating_peer->send_message(graphene::net::connection_rejected_message());\n  }\n\n  void on_connection_accepted_message(graphene::net::peer_connection* originating_peer,\n          const graphene::net::connection_accepted_message& connection_accepted_message_received)\n  {\n    _connection_was_rejected = false;\n    originating_peer->send_message(graphene::net::address_request_message());\n  }\n\n  void on_connection_rejected_message( graphene::net::peer_connection* originating_peer,\n          const graphene::net::connection_rejected_message& connection_rejected_message_received )\n  {\n    // Note: We will be rejected and disconnected if our chain_id is not the same as the peer's .\n    //       If we aren't disconnected, it is OK to send an address request message.\n    _connection_was_rejected = true;\n    wlog( \"peer ${endpoint} rejected our connection with reason ${reason}\",\n          (\"endpoint\", originating_peer->get_remote_endpoint() )\n          (\"reason\", connection_rejected_message_received.reason_code ) );\n    originating_peer->send_message(graphene::net::address_request_message());\n  }\n\n  void on_address_request_message(graphene::net::peer_connection* originating_peer,\n          const graphene::net::address_request_message& address_request_message_received)\n  {\n    originating_peer->send_message(graphene::net::address_message());\n  }\n\n\n  void on_address_message(graphene::net::peer_connection* originating_peer,\n          const graphene::net::address_message& address_message_received)\n  {\n    _peers = address_message_received.addresses;\n    originating_peer->send_message(graphene::net::closing_connection_message(\"Thanks for the info\"));\n    _we_closed_connection = true;\n  }\n\n  void on_closing_connection_message(graphene::net::peer_connection* originating_peer,\n          const graphene::net::closing_connection_message& closing_connection_message_received)\n  {\n    if (_we_closed_connection)\n      _connection->close_connection();\n    else\n      _peer_closed_connection = true;\n  }\n\n  void on_connection_closed(graphene::net::peer_connection* originating_peer) override\n  {\n    // Note: In rare cases, the peer may neither send us an address_message nor close the connection,\n    //       causing us to wait forever.\n    //       In this case the timeout handler will close the connection.\n    _done = true;\n    _timeout_handler.cancel();\n    _probe_complete_promise->set_value();\n  }\n\n  graphene::net::message get_message_for_item(const graphene::net::item_id& item) override\n  {\n    return graphene::net::item_not_available_message(item);\n  }\n\n  void wait( const fc::microseconds& timeout_us )\n  {\n    _probe_complete_promise->wait( timeout_us );\n  }\n};\n\nint main(int argc, char** argv)\n{\n  std::queue<fc::ip::endpoint> nodes_to_visit;\n  std::set<fc::ip::endpoint> nodes_to_visit_set;\n  std::set<fc::ip::endpoint> nodes_already_visited;\n\n  if ( argc < 3 ) {\n     std::cerr << \"Usage: \" << argv[0] << \" <chain-id> <seed-addr> [<seed-addr> ...]\\n\";\n     return 1;\n  }\n\n  const graphene::chain::chain_id_type chain_id( argv[1] );\n  for ( int i = 2; i < argc; i++ )\n  {\n     std::string ep(argv[i]);\n     uint16_t port;\n     auto pos = ep.find(':');\n     if (pos > 0)\n        port = boost::lexical_cast<uint16_t>( ep.substr( pos+1, ep.size() ) );\n     else\n        port = 1776;\n     for (const auto& addr : fc::resolve( ep.substr( 0, pos > 0 ? pos : ep.size() ), port ))\n        nodes_to_visit.push( addr );\n  }\n\n  fc::path data_dir = fc::temp_directory_path() / (\"network_map_\" + (fc::string) chain_id);\n  fc::create_directories(data_dir);\n\n  fc::ip::endpoint seed_node1 = nodes_to_visit.front();\n\n  fc::ecc::private_key my_node_key = fc::ecc::private_key::generate();\n  auto my_node_id = my_node_key.get_public_key();\n  std::map<graphene::net::node_id_t, graphene::net::address_info> address_info_by_node_id;\n  std::map<graphene::net::node_id_t, std::vector<graphene::net::address_info> > connections_by_node_id;\n  std::map<fc::ip::endpoint, graphene::net::node_id_t> node_id_by_endpoint;\n  std::vector<std::shared_ptr<peer_probe>> probes;\n\n  const auto& update_info_by_probe = [ &connections_by_node_id, &address_info_by_node_id, &node_id_by_endpoint ]\n                                     ( const std::shared_ptr<peer_probe>& probe )\n  {\n          idump( (probe->_node_id)(probe->_remote)(probe->_peers.size()) );\n\n          graphene::net::address_info this_node_info;\n          this_node_info.direction = graphene::net::peer_connection_direction::outbound;\n          this_node_info.firewalled = graphene::net::firewalled_state::not_firewalled;\n          this_node_info.remote_endpoint = probe->_remote;\n          this_node_info.node_id = probe->_node_id;\n\n          // Note: Update if it already exists (usually unlikely).\n          connections_by_node_id[this_node_info.node_id] = probe->_peers;\n          address_info_by_node_id[this_node_info.node_id] = this_node_info;\n\n          node_id_by_endpoint[probe->_remote] = probe->_node_id;\n  };\n\n  const auto& update_info_by_address_info = [ &address_info_by_node_id, &my_node_id, &nodes_already_visited,\n                 &nodes_to_visit_set, &nodes_to_visit ] ( const graphene::net::address_info& info )\n  {\n             if( info.node_id == graphene::net::node_id_t(my_node_id) ) // We should not be in the list, be defensive\n                return;\n             if (nodes_already_visited.find(info.remote_endpoint) == nodes_already_visited.end() &&\n                 nodes_to_visit_set.find(info.remote_endpoint) == nodes_to_visit_set.end())\n             {\n                nodes_to_visit.push(info.remote_endpoint);\n                nodes_to_visit_set.insert(info.remote_endpoint);\n             }\n             if (address_info_by_node_id.find(info.node_id) == address_info_by_node_id.end())\n             {\n                address_info_by_node_id[info.node_id] = info;\n                // Set it to unknown here, we will check later\n                address_info_by_node_id[info.node_id].firewalled = graphene::net::firewalled_state::unknown;\n             }\n             else if ( !address_info_by_node_id[info.node_id].remote_endpoint.get_address().is_public_address()\n                       && info.remote_endpoint.get_address().is_public_address() )\n                // Replace private or local addresses with public addresses when possible\n                address_info_by_node_id[info.node_id].remote_endpoint = info.remote_endpoint;\n  };\n\n  constexpr size_t max_concurrent_probes = 200;\n  while (!nodes_to_visit.empty() || !probes.empty())\n  {\n    while (!nodes_to_visit.empty() && probes.size() < max_concurrent_probes )\n    {\n       fc::ip::endpoint remote = nodes_to_visit.front();\n       nodes_to_visit.pop();\n       nodes_to_visit_set.erase( remote );\n       nodes_already_visited.insert( remote );\n\n       probes.emplace_back( std::make_shared<peer_probe>(remote) );\n       auto& probe = *probes.back();\n       fc::async( [&probe, &my_node_id, &my_node_key, &chain_id](){\n          probe.start(my_node_id, my_node_key, chain_id);\n       });\n    }\n\n    if( probes.empty() )\n       continue;\n\n    std::vector<std::shared_ptr<peer_probe>> running;\n    for ( const auto& probe : probes )\n    {\n       if (probe->_probe_complete_promise->error())\n       {\n          continue;\n       }\n       if (!probe->_probe_complete_promise->ready())\n       {\n          running.push_back( probe );\n          continue;\n       }\n       update_info_by_probe(probe);\n       for (const graphene::net::address_info& info : probe->_peers)\n          update_info_by_address_info( info );\n    }\n\n    constexpr uint32_t five = 5;\n    if( running.size() == probes.size() )\n       fc::usleep( fc::seconds( five ) );\n    else\n       probes = std::move( running );\n\n    ilog( \"${total} nodes detected, ${tried} endpoints tried, \"\n          \"${reachable} reachable, ${trying} trying, ${todo} to do\",\n          ( \"total\",     address_info_by_node_id.size() )\n          ( \"tried\",     nodes_already_visited.size() )\n          ( \"reachable\", node_id_by_endpoint.size() )\n          ( \"trying\",    probes.size() )\n          ( \"todo\",      nodes_to_visit.size() ) );\n\n  }\n\n  ilog( \"${total} nodes, ${reachable} reachable\",\n        ( \"total\",     address_info_by_node_id.size() )\n        ( \"reachable\", node_id_by_endpoint.size() ) );\n\n  graphene::net::node_id_t seed_node_id;\n  std::set<graphene::net::node_id_t> non_firewalled_nodes_set;\n  for (const auto& address_info_for_node : address_info_by_node_id)\n  {\n    if (address_info_for_node.second.remote_endpoint == seed_node1)\n      seed_node_id = address_info_for_node.first;\n    if (address_info_for_node.second.firewalled == graphene::net::firewalled_state::not_firewalled)\n      non_firewalled_nodes_set.insert(address_info_for_node.first);\n  }\n  std::set<graphene::net::node_id_t> seed_node_connections;\n  std::set<graphene::net::node_id_t> seed_node_non_fw_connections;\n  for (const graphene::net::address_info& info : connections_by_node_id[seed_node_id])\n  {\n    seed_node_connections.insert(info.node_id);\n    if( non_firewalled_nodes_set.find(info.node_id) != non_firewalled_nodes_set.end() )\n      seed_node_non_fw_connections.insert(info.node_id);\n  }\n  std::set<graphene::net::node_id_t> seed_node_missing_connections;\n  std::set_difference(non_firewalled_nodes_set.begin(), non_firewalled_nodes_set.end(),\n                      seed_node_connections.begin(), seed_node_connections.end(),\n                      std::inserter(seed_node_missing_connections, seed_node_missing_connections.end()));\n  seed_node_missing_connections.erase(seed_node_id);\n\n  std::ofstream dot_stream((data_dir / \"network_graph.dot\").string().c_str());\n\n  dot_stream << \"graph G {\\n\";\n  dot_stream << \"  // Total \" << address_info_by_node_id.size() << \" nodes, firewalled: \"\n                              << (address_info_by_node_id.size() - non_firewalled_nodes_set.size())\n                              << \", non-firewalled: \" << non_firewalled_nodes_set.size() << \"\\n\";\n  dot_stream << \"  // Seed node is \" << (std::string)address_info_by_node_id[seed_node_id].remote_endpoint\n             << \" id: \" << fc::variant( seed_node_id, 1 ).as_string() << \"\\n\";\n  dot_stream << \"  // Seed node is connected to \" << connections_by_node_id[seed_node_id].size() << \" nodes\\n\";\n  dot_stream << \"  // Seed node is connected to \" << seed_node_non_fw_connections.size()\n             << \" non-firewalled nodes:\\n\";\n  for (const graphene::net::node_id_t& id : seed_node_non_fw_connections)\n    dot_stream << \"  //           \" << (std::string)address_info_by_node_id[id].remote_endpoint << \"\\n\";\n  dot_stream << \"  // Seed node is missing connections to \" << seed_node_missing_connections.size()\n             << \" non-firewalled nodes:\\n\";\n  for (const graphene::net::node_id_t& id : seed_node_missing_connections)\n    dot_stream << \"  //           \" << (std::string)address_info_by_node_id[id].remote_endpoint << \"\\n\";\n\n  dot_stream << \"  layout=\\\"circo\\\";\\n\";\n\n  for (const auto& address_info_for_node : address_info_by_node_id)\n  {\n    dot_stream << \"  \\\"\" << fc::variant( address_info_for_node.first, 1 ).as_string()\n               << \"\\\"[label=\\\"\" << (std::string)address_info_for_node.second.remote_endpoint << \"\\\"\";\n    if (address_info_for_node.second.firewalled != graphene::net::firewalled_state::not_firewalled)\n      dot_stream << \",shape=rectangle\";\n    dot_stream << \"];\\n\";\n  }\n  constexpr uint16_t pair_depth = 2;\n  for (auto& node_and_connections : connections_by_node_id)\n    for (const graphene::net::address_info& this_connection : node_and_connections.second)\n      if( this_connection.node_id != graphene::net::node_id_t(my_node_id) ) // We should not be in the list,\n                                                                            // just be defensive here\n        dot_stream << \"  \\\"\" << fc::variant( node_and_connections.first, pair_depth ).as_string()\n                   << \"\\\" -- \\\"\" << fc::variant( this_connection.node_id, 1 ).as_string() << \"\\\";\\n\";\n\n  dot_stream << \"}\\n\";\n\n  return 0;\n}\n"
  },
  {
    "path": "programs/size_checker/CMakeLists.txt",
    "content": "add_executable( size_checker main.cpp )\nif( UNIX AND NOT APPLE )\n  set(rt_library rt )\nendif()\n\ntarget_link_libraries( size_checker\n                       PRIVATE graphene_chain graphene_egenesis_none fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\ninstall( TARGETS\n   size_checker\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "programs/size_checker/main.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <fc/io/json.hpp>\n#include <fc/io/raw.hpp>\n#include <fc/variant.hpp>\n#include <fc/variant_object.hpp>\n\n#include <graphene/protocol/block.hpp>\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include <algorithm>\n#include <iostream>\n#include <sstream>\n#include <string>\n#include <vector>\n\nusing namespace graphene::protocol;\n\nvector< fc::variant_object > g_op_types;\n\ntemplate< typename T >\nuint64_t get_wire_size()\n{\n   T data;\n   return fc::raw::pack( data ).size();\n}\n\nstruct size_check_type_visitor\n{\n   typedef void result_type;\n\n   int t = 0;\n   size_check_type_visitor(int _t ):t(_t){}\n\n   template<typename Type>\n   result_type operator()( const Type& op )const\n   {\n      fc::mutable_variant_object vo;\n      vo[\"name\"] = fc::get_typename<Type>::name();\n      vo[\"mem_size\"] = sizeof( Type );\n      vo[\"wire_size\"] = get_wire_size<Type>();\n      g_op_types.push_back( vo );\n   }\n};\n\nint main( int argc, char** argv )\n{\n   try\n   {\n      graphene::protocol::operation op;\n\n\n      vector<uint64_t> witnesses; witnesses.resize(50);\n      for( uint32_t i = 0; i < 60*60*24*30; ++i )\n      {\n         witnesses[ rand() % 50 ]++;\n      }\n\n      std::sort( witnesses.begin(), witnesses.end() );\n      idump((witnesses.back() - witnesses.front()) );\n      idump((60*60*24*30/50));\n      idump((\"deviation: \")((60*60*24*30/50-witnesses.front())/(60*60*24*30/50.0)));\n\n      idump( (witnesses) );\n\n      for( size_t i = 0; i < op.count(); ++i )\n      {\n         op.set_which(i);\n         op.visit( size_check_type_visitor(i) );\n      }\n\n      // sort them by mem size\n      std::stable_sort( g_op_types.begin(), g_op_types.end(),\n      [](const variant_object& oa, const variant_object& ob) {\n      return oa[\"mem_size\"].as_uint64() > ob[\"mem_size\"].as_uint64();\n      });\n      std::cout << \"[\\n\";\n      for( size_t i=0; i<g_op_types.size(); i++ )\n      {\n         std::cout << \"   \" << fc::json::to_string( g_op_types[i] );\n         if( i < g_op_types.size()-1 )\n            std::cout << \",\\n\";\n         else\n            std::cout << \"\\n\";\n      }\n      std::cout << \"]\\n\";\n      std::cerr << \"Size of block header: \" << sizeof( block_header ) << \" \" << fc::raw::pack_size( block_header() ) << \"\\n\";\n   }\n   catch ( const fc::exception& e ){ edump((e.to_detail_string())); }\n   idump((sizeof(signed_block)));\n   idump((fc::raw::pack_size(signed_block())));\n   return 0;\n}\n"
  },
  {
    "path": "programs/witness_node/CMakeLists.txt",
    "content": "add_executable( witness_node main.cpp )\nif( UNIX AND NOT APPLE )\n  set(rt_library rt )\nendif()\n\nfind_package( Gperftools QUIET )\nif( GPERFTOOLS_FOUND )\n    message( STATUS \"Found gperftools; compiling witness_node with TCMalloc\")\n    list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )\nendif()\n\n# We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246\ntarget_link_libraries( witness_node\n\nPRIVATE graphene_app graphene_delayed_node graphene_account_history graphene_elasticsearch graphene_market_history graphene_grouped_orders graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full graphene_snapshot graphene_es_objects\n        graphene_api_helper_indexes graphene_custom_operations\n        fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\nif (MSVC)\n    set_target_properties( witness_node PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\ninstall( TARGETS\n   witness_node\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "programs/witness_node/main.cpp",
    "content": "/*\n * Copyright (c) 2015-2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/app/application.hpp>\n#include <graphene/app/config_util.hpp>\n\n#include <graphene/witness/witness.hpp>\n#include <graphene/debug_witness/debug_witness.hpp>\n#include <graphene/account_history/account_history_plugin.hpp>\n#include <graphene/elasticsearch/elasticsearch_plugin.hpp>\n#include <graphene/market_history/market_history_plugin.hpp>\n#include <graphene/delayed_node/delayed_node_plugin.hpp>\n#include <graphene/snapshot/snapshot.hpp>\n#include <graphene/es_objects/es_objects.hpp>\n#include <graphene/grouped_orders/grouped_orders_plugin.hpp>\n#include <graphene/api_helper_indexes/api_helper_indexes.hpp>\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n\n#include <fc/thread/thread.hpp>\n#include <fc/interprocess/signals.hpp>\n#include <fc/stacktrace.hpp>\n#include <fc/log/console_appender.hpp>\n#include <fc/log/logger_config.hpp>\n\n#include <boost/filesystem.hpp>\n#include <boost/property_tree/ptree.hpp>\n#include <boost/container/flat_set.hpp>\n#include <boost/algorithm/string.hpp>\n\n#include <graphene/utilities/git_revision.hpp>\n#include <boost/algorithm/string/replace.hpp>\n#include <websocketpp/version.hpp>\n\n#include <sstream>\n\n#ifdef WIN32\n# include <signal.h>\n#else\n# include <csignal>\n#endif\n\nnamespace bpo = boost::program_options;\n\n/// Disable default logging\nvoid disable_default_logging()\n{\n   fc::configure_logging( fc::logging_config() );\n}\n\n/// Hack to log messages to console with default color and no format via fc::console_appender\n// TODO fix console_appender and use ilog() or etc instead: 1) stream is always stderr, 2) format can not change\nvoid my_log( const std::string& s )\n{\n   static fc::console_appender::config my_console_config;\n   static fc::console_appender my_appender( my_console_config );\n   my_appender.print(s);\n   my_appender.print(\"\\n\"); // This is needed, otherwise the next message will cover it\n}\n\n/// The main program\nint main(int argc, char** argv) {\n   fc::print_stacktrace_on_segfault();\n   auto node = std::make_unique<graphene::app::application>();\n   fc::oexception unhandled_exception;\n   try {\n      bpo::options_description app_options(\"BitShares Witness Node\");\n      bpo::options_description cfg_options(\"BitShares Witness Node\");\n      std::string default_plugins = \"witness account_history market_history grouped_orders \"\n                                    \"api_helper_indexes custom_operations\";\n      app_options.add_options()\n            (\"help,h\", \"Print this help message and exit.\")\n            (\"data-dir,d\", bpo::value<boost::filesystem::path>()->default_value(\"witness_node_data_dir\"),\n                    \"Directory containing databases, configuration file, etc.\")\n            (\"version,v\", \"Display version information\")\n            (\"plugins\", bpo::value<std::string>()->default_value(default_plugins),\n                    \"Space-separated list of plugins to activate\")\n            (\"ignore-api-helper-indexes-warning\", \"Do not exit if api_helper_indexes plugin is not enabled.\");\n\n      auto sharable_options = std::make_shared<bpo::variables_map>();\n      auto& options = *sharable_options;\n\n      bpo::options_description cli;\n      bpo::options_description cfg;\n      node->set_program_options(cli, cfg);\n      cfg_options.add(cfg);\n\n      cfg_options.add_options()\n            (\"plugins\", bpo::value<std::string>()->default_value(default_plugins),\n                    \"Space-separated list of plugins to activate\")\n            (\"ignore-api-helper-indexes-warning\", \"Do not exit if api_helper_indexes plugin is not enabled.\");\n\n      node->register_plugin<graphene::witness_plugin::witness_plugin>();\n      node->register_plugin<graphene::debug_witness_plugin::debug_witness_plugin>();\n      node->register_plugin<graphene::account_history::account_history_plugin>();\n      node->register_plugin<graphene::elasticsearch::elasticsearch_plugin>();\n      node->register_plugin<graphene::market_history::market_history_plugin>();\n      node->register_plugin<graphene::delayed_node::delayed_node_plugin>();\n      node->register_plugin<graphene::snapshot_plugin::snapshot_plugin>();\n      node->register_plugin<graphene::es_objects::es_objects_plugin>();\n      node->register_plugin<graphene::grouped_orders::grouped_orders_plugin>();\n      node->register_plugin<graphene::api_helper_indexes::api_helper_indexes>();\n      node->register_plugin<graphene::custom_operations::custom_operations_plugin>();\n\n      // add plugin options to config\n      try\n      {\n         bpo::options_description tmp_cli;\n         bpo::options_description tmp_cfg;\n         node->set_program_options(tmp_cli, tmp_cfg);\n         app_options.add(tmp_cli);\n         cfg_options.add(tmp_cfg);\n         bpo::store(bpo::parse_command_line(argc, argv, app_options), options);\n      }\n      catch (const boost::program_options::error& e)\n      {\n         disable_default_logging();\n         std::stringstream ss;\n         ss << \"Error parsing command line: \" << e.what();\n         my_log( ss.str() );\n         return EXIT_FAILURE;\n      }\n\n      if( options.count(\"version\") > 0 )\n      {\n         disable_default_logging();\n         std::stringstream ss;\n         ss << \"Version: \" << graphene::utilities::git_revision_description << \"\\n\";\n         ss << \"SHA: \" << graphene::utilities::git_revision_sha << \"\\n\";\n         ss << \"Timestamp: \" << fc::get_approximate_relative_time_string(fc::time_point_sec(\n                                             graphene::utilities::git_revision_unix_timestamp)) << \"\\n\";\n         ss << \"SSL: \" << OPENSSL_VERSION_TEXT << \"\\n\";\n         ss << \"Boost: \" << boost::replace_all_copy(std::string(BOOST_LIB_VERSION), \"_\", \".\") << \"\\n\";\n         ss << \"Websocket++: \" << websocketpp::major_version << \".\" << websocketpp::minor_version\n                                      << \".\" << websocketpp::patch_version; // No end of line in the end\n         my_log( ss.str() );\n         return EXIT_SUCCESS;\n      }\n      if( options.count(\"help\") > 0 )\n      {\n         disable_default_logging();\n         std::stringstream ss;\n         ss << app_options << \"\\n\";\n         my_log( ss.str() );\n         return EXIT_SUCCESS;\n      }\n\n      fc::path data_dir;\n      if( options.count(\"data-dir\") > 0 )\n      {\n         data_dir = options[\"data-dir\"].as<boost::filesystem::path>();\n         if( data_dir.is_relative() )\n            data_dir = fc::current_path() / data_dir;\n      }\n      graphene::app::load_configuration_options(data_dir, cfg_options, options);\n\n      std::set<std::string> plugins;\n      boost::split(plugins, options.at(\"plugins\").as<std::string>(), [](char c){return c == ' ';});\n\n      if( plugins.count(\"account_history\") > 0 && plugins.count(\"elasticsearch\") > 0 ) {\n         disable_default_logging();\n         std::stringstream ss;\n         ss << \"Plugin conflict: Cannot load both account_history plugin and elasticsearch plugin\";\n         my_log( ss.str() );\n         return EXIT_FAILURE;\n      }\n\n      if( plugins.count(\"api_helper_indexes\") == 0 && options.count(\"ignore-api-helper-indexes-warning\") == 0\n          && ( options.count(\"rpc-endpoint\") > 0 || options.count(\"rpc-tls-endpoint\") > 0 ) )\n      {\n         disable_default_logging();\n         std::stringstream ss;\n         ss << \"\\nIf this is an API node, please enable api_helper_indexes plugin.\"\n               \"\\nIf this is not an API node, please start with \\\"--ignore-api-helper-indexes-warning\\\"\"\n               \" or enable it in config.ini file.\\n\";\n         my_log( ss.str() );\n         return EXIT_FAILURE;\n      }\n\n      std::for_each(plugins.begin(), plugins.end(), [&node](const std::string& plug) mutable {\n         if (!plug.empty()) {\n            node->enable_plugin(plug);\n         }\n      });\n\n      bpo::notify(options);\n\n      node->initialize(data_dir, sharable_options);\n\n      node->startup();\n\n      fc::promise<int>::ptr exit_promise = fc::promise<int>::create(\"UNIX Signal Handler\");\n\n      fc::set_signal_handler([&exit_promise](int the_signal) {\n         wlog( \"Caught SIGINT, attempting to exit cleanly\" );\n         exit_promise->set_value(the_signal);\n      }, SIGINT);\n\n      fc::set_signal_handler([&exit_promise](int the_signal) {\n         wlog( \"Caught SIGTERM, attempting to exit cleanly\" );\n         exit_promise->set_value(the_signal);\n      }, SIGTERM);\n\n#ifdef SIGQUIT\n      fc::set_signal_handler( [&exit_promise](int the_signal) {\n         wlog( \"Caught SIGQUIT, attempting to exit cleanly\" );\n         exit_promise->set_value(the_signal);\n      }, SIGQUIT );\n#endif\n\n      ilog(\"Started BitShares node on a chain with ${h} blocks.\", (\"h\", node->chain_database()->head_block_num()));\n      ilog(\"Chain ID is ${id}\", (\"id\", node->chain_database()->get_chain_id()) );\n\n      auto caught_signal = exit_promise->wait();\n      ilog(\"Exiting from signal ${n}\", (\"n\", caught_signal));\n      return EXIT_SUCCESS;\n   } catch( const fc::exception& e ) {\n      // deleting the node can yield, so do this outside the exception handler\n      unhandled_exception = e;\n   }\n\n   if (unhandled_exception)\n   {\n      elog(\"Exiting with error:\\n${e}\", (\"e\", unhandled_exception->to_detail_string()));\n      return EXIT_FAILURE;\n   }\n}\n\n"
  },
  {
    "path": "programs/witness_node/saltpass.py",
    "content": "#!/usr/bin/env python3\n\nimport base64\nimport getpass\nimport hashlib\nimport json\nimport os\n\npw = getpass.getpass(\"enter your password:  \")\npw_bytes = pw.encode(\"utf-8\")\nsalt_bytes = os.urandom(8)\nsalt_b64 = base64.b64encode( salt_bytes )\npw_hash = hashlib.sha256( pw_bytes + salt_bytes ).digest()\npw_hash_b64 = base64.b64encode( pw_hash )\n\nprint(json.dumps(\n{\n    \"password_hash_b64\" : pw_hash_b64.decode(\"ascii\"),\n    \"password_salt_b64\" : salt_b64.decode(\"ascii\"),\n},\nsort_keys=True,\nindent=3, separators=(',', ' : ')\n))\n"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.organization=bitshares-on-github\n\nsonar.projectKey=bitshares_bitshares-core\nsonar.projectName=BitShares-Core\nsonar.projectDescription=BitShares Blockchain node and command-line wallet\nsonar.projectVersion=7.1.x\n\nsonar.host.url=https://sonarcloud.io\n\nsonar.links.homepage=https://bitshares.github.io\nsonar.links.ci=https://github.com/bitshares/bitshares-core/actions\nsonar.links.issue=https://github.com/bitshares/bitshares-core/issues\nsonar.links.scm=https://github.com/bitshares/bitshares-core/tree/master\n\n# Note:\n# According to docs, sonar.tests is ignored by the C/C++/Objective-C analyzer.\n# See https://docs.sonarcloud.io/advanced-setup/languages/c-c-objective-c/#language-specific-properties\nsonar.tests=tests\n\nsonar.exclusions=programs/build_helper/**/*,libraries/fc/**/*,libraries/egenesis/egenesis_full.cpp\nsonar.sources=libraries,programs\nsonar.cfamily.build-wrapper-output=bw-output\n\n# Note:\n# It is hard to get the gcov sensor working, but gcovr works.\n# See https://community.sonarsource.com/t/code-coverage-incorrect-for-c-gcov-project/41837/5\n#sonar.cfamily.gcov.reportsPath=.\nsonar.coverageReportPaths=coverage.xml\n\n# Decide which tree the current build belongs to in SonarCloud.\n# Managed by the `set_sonar_branch*` script(s) when building with CI.\nsonar.branch.target=master\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "file(GLOB COMMON_SOURCES \"common/*.cpp\")\nfile(GLOB COMMON_HEADERS \"common/*.hpp\")\n\nadd_library( database_fixture\n             ${COMMON_SOURCES}\n             ${COMMON_HEADERS}\n           )\ntarget_link_libraries( database_fixture PUBLIC graphene_es_objects graphene_app graphene_egenesis_none )\ntarget_include_directories( database_fixture\n                            PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/common\" )\n\nfind_package( Gperftools QUIET )\nif( GPERFTOOLS_FOUND )\n    message( STATUS \"Found gperftools; compiling tests with TCMalloc\")\n    list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )\nendif()\n\nfile(GLOB UNIT_TESTS \"tests/*.cpp\")\nadd_executable( chain_test ${UNIT_TESTS} )\ntarget_link_libraries( chain_test database_fixture\n                       graphene_witness graphene_wallet graphene_app ${PLATFORM_SPECIFIC_LIBS} )\nif(MSVC)\n  set_source_files_properties( tests/serialization_tests.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\n  set_source_files_properties( tests/common/database_fixture.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\nfile(GLOB PERFORMANCE_TESTS \"performance/*.cpp\")\nadd_executable( performance_test ${PERFORMANCE_TESTS} )\ntarget_link_libraries( performance_test database_fixture ${PLATFORM_SPECIFIC_LIBS} )\n\nfile(GLOB APP_SOURCES \"app/*.cpp\")\nadd_executable( app_test ${APP_SOURCES} )\ntarget_link_libraries( app_test graphene_app graphene_egenesis_none\n                       ${PLATFORM_SPECIFIC_LIBS} )\n\nfile(GLOB CLI_SOURCES \"cli/*.cpp\")\nadd_executable( cli_test ${CLI_SOURCES} )\nif(WIN32)\n   list(APPEND PLATFORM_SPECIFIC_LIBS ws2_32)\nendif()\ntarget_link_libraries( cli_test graphene_wallet graphene_app graphene_egenesis_none\n                       ${PLATFORM_SPECIFIC_LIBS} )\nif(MSVC)\n  set_source_files_properties( cli/main.cpp PROPERTIES COMPILE_FLAGS \"/bigobj\" )\nendif(MSVC)\n\nfile(GLOB ES_SOURCES \"elasticsearch/*.cpp\")\nadd_executable( es_test ${ES_SOURCES} )\ntarget_link_libraries( es_test database_fixture ${PLATFORM_SPECIFIC_LIBS} )\n                       \nadd_subdirectory( generate_empty_blocks )\n"
  },
  {
    "path": "tests/app/main.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/app/application.hpp>\n#include <graphene/app/config_util.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/thread/thread.hpp>\n#include <fc/log/appender.hpp>\n#include <fc/log/console_appender.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/log/logger_config.hpp>\n\n#include <boost/filesystem/path.hpp>\n\n#include \"../../libraries/app/application_impl.hxx\"\n\n#include \"../common/init_unit_test_suite.hpp\"\n#include \"../common/genesis_file_util.hpp\"\n#include \"../common/program_options_util.hpp\"\n#include \"../common/utils.hpp\"\n\nusing namespace graphene;\nnamespace bpo = boost::program_options;\n\nnamespace fc {\n   extern std::unordered_map<std::string, logger> &get_logger_map();\n   extern std::unordered_map<std::string, appender::ptr> &get_appender_map();\n}\n\nBOOST_AUTO_TEST_CASE(load_configuration_options_test_config_logging_files_created)\n{\n   fc::temp_directory app_dir(graphene::utilities::temp_directory_path());\n   auto dir = app_dir.path();\n   auto config_ini_file = dir / \"config.ini\";\n   auto logging_ini_file = dir / \"logging.ini\";\n\n   /// create default config options\n   auto node = new app::application();\n   bpo::options_description cli, cfg;\n   node->set_program_options(cli, cfg);\n   bpo::options_description cfg_options(\"BitShares Witness Node\");\n   cfg_options.add(cfg);\n\n   /// check preconditions\n   BOOST_CHECK(!fc::exists(config_ini_file));\n   BOOST_CHECK(!fc::exists(logging_ini_file));\n\n   bpo::variables_map options;\n   app::load_configuration_options(dir, cfg_options, options);\n\n   /// check post-conditions\n   BOOST_CHECK(fc::exists(config_ini_file));\n   BOOST_CHECK(fc::exists(logging_ini_file));\n   BOOST_CHECK_GT(fc::file_size(config_ini_file), 0u);\n   BOOST_CHECK_GT(fc::file_size(logging_ini_file), 0u);\n}\n\nBOOST_AUTO_TEST_CASE(load_configuration_options_test_config_ini_options)\n{\n   fc::temp_directory app_dir(graphene::utilities::temp_directory_path());\n   auto dir = app_dir.path();\n   auto config_ini_file = dir / \"config.ini\";\n   auto logging_ini_file = dir / \"logging.ini\";\n\n   /// create config.ini\n   bpo::options_description cfg_options(\"config.ini options\");\n   cfg_options.add_options()\n   (\"option1\", bpo::value<std::string>(), \"\")\n   (\"option2\", bpo::value<int>(), \"\")\n   ;\n   std::ofstream out(config_ini_file.preferred_string());\n   out << \"option1=is present\\n\"\n          \"option2=1\\n\\n\";\n   out.close();\n\n   /// check preconditions\n   BOOST_CHECK(fc::exists(config_ini_file));\n   BOOST_CHECK(!fc::exists(logging_ini_file));\n\n   bpo::variables_map options;\n   app::load_configuration_options(dir, cfg_options, options);\n\n   /// check the options values are parsed into the output map\n   BOOST_CHECK(!options.empty());\n   BOOST_CHECK_EQUAL(options.count(\"option1\"), 1u);\n   BOOST_CHECK_EQUAL(options.count(\"option2\"), 1u);\n   BOOST_CHECK_EQUAL(options[\"option1\"].as<std::string>(), \"is present\");\n   BOOST_CHECK_EQUAL(options[\"option2\"].as<int>(), 1);\n\n   /// when the config.ini exists and doesn't contain logging configuration while the logging.ini doesn't exist\n   /// the logging.ini is not created\n   BOOST_CHECK(!fc::exists(logging_ini_file));\n}\n\nBOOST_AUTO_TEST_CASE(load_configuration_options_test_logging_ini_options)\n{\n   fc::temp_directory app_dir(graphene::utilities::temp_directory_path());\n   auto dir = app_dir.path();\n   auto config_ini_file = dir / \"config.ini\";\n   auto logging_ini_file = dir / \"logging.ini\";\n\n   /// create logging.ini\n   /// configure exactly one logger and appender\n   std::ofstream out(logging_ini_file.preferred_string());\n   out << \"[log.file_appender.default]\\n\"\n          \"filename=test.log\\n\\n\"\n          \"[logger.default]\\n\"\n          \"level=info\\n\"\n          \"appenders=default\\n\\n\"\n          ;\n   out.close();\n\n   /// clear logger and appender state\n   fc::get_logger_map().clear();\n   fc::get_appender_map().clear();\n   BOOST_CHECK(fc::get_logger_map().empty());\n   BOOST_CHECK(fc::get_appender_map().empty());\n\n   bpo::options_description cfg_options(\"empty\");\n   bpo::variables_map options;\n   app::load_configuration_options(dir, cfg_options, options);\n\n   /// check the options values are parsed into the output map\n   /// this is a little bit tricky since load_configuration_options() doesn't provide output variable for logging_config\n   auto logger_map = fc::get_logger_map();\n   auto appender_map = fc::get_appender_map();\n   BOOST_CHECK_EQUAL(logger_map.size(), 1u);\n   BOOST_CHECK(logger_map.count(\"default\"));\n   BOOST_CHECK_EQUAL(appender_map.size(), 1u);\n   BOOST_CHECK(appender_map.count(\"default\"));\n}\n\nBOOST_AUTO_TEST_CASE(load_configuration_options_test_legacy_config_ini_options)\n{\n   fc::temp_directory app_dir(graphene::utilities::temp_directory_path());\n   auto dir = app_dir.path();\n   auto config_ini_file = dir / \"config.ini\";\n   auto logging_ini_file = dir / \"logging.ini\";\n\n   /// create config.ini\n   bpo::options_description cfg_options(\"config.ini options\");\n   cfg_options.add_options()\n   (\"option1\", bpo::value<std::string>(), \"\")\n   (\"option2\", bpo::value<int>(), \"\")\n   ;\n   std::ofstream out(config_ini_file.preferred_string());\n   out << \"option1=is present\\n\"\n          \"option2=1\\n\\n\"\n          \"[log.file_appender.default]\\n\"\n          \"filename=test.log\\n\\n\"\n          \"[logger.default]\\n\"\n          \"level=info\\n\"\n          \"appenders=default\\n\\n\"\n          ;\n   out.close();\n\n   /// clear logger and appender state\n   fc::get_logger_map().clear();\n   fc::get_appender_map().clear();\n   BOOST_CHECK(fc::get_logger_map().empty());\n   BOOST_CHECK(fc::get_appender_map().empty());\n\n   bpo::variables_map options;\n   app::load_configuration_options(dir, cfg_options, options);\n\n   /// check logging.ini not created\n   BOOST_CHECK(!fc::exists(logging_ini_file));\n\n   /// check the options values are parsed into the output map\n   BOOST_CHECK(!options.empty());\n   BOOST_CHECK_EQUAL(options.count(\"option1\"), 1u);\n   BOOST_CHECK_EQUAL(options.count(\"option2\"), 1u);\n   BOOST_CHECK_EQUAL(options[\"option1\"].as<std::string>(), \"is present\");\n   BOOST_CHECK_EQUAL(options[\"option2\"].as<int>(), 1);\n\n   auto logger_map = fc::get_logger_map();\n   auto appender_map = fc::get_appender_map();\n   BOOST_CHECK_EQUAL(logger_map.size(), 1u);\n   BOOST_CHECK(logger_map.count(\"default\"));\n   BOOST_CHECK_EQUAL(appender_map.size(), 1u);\n   BOOST_CHECK(appender_map.count(\"default\"));\n}\n\n/////////////\n/// @brief create a 3 node network\n/////////////\nBOOST_AUTO_TEST_CASE( three_node_network )\n{\n   using namespace graphene::chain;\n   using namespace graphene::app;\n   try {\n      // Configure logging\n      fc::logging_config logging_config = fc::logging_config::default_config();\n\n      auto logger = logging_config.loggers.back(); // get a copy of the default logger\n      logger.name = \"p2p\";                         // update the name to p2p\n      logging_config.loggers.push_back( logger );  // add it to logging_config\n\n      fc::configure_logging(logging_config);\n\n      // Start app1\n      BOOST_TEST_MESSAGE( \"Creating and initializing app1\" );\n\n      auto port = fc::network::get_available_port();\n      auto app1_p2p_endpoint_str = string(\"127.0.0.1:\") + std::to_string(port);\n      auto app2_seed_nodes_str = string(\"[\\\"\") + app1_p2p_endpoint_str + \"\\\"]\";\n      auto app3_seed_nodes_str = string(\"[\\\"\") + app1_p2p_endpoint_str + \"\\\"]\";\n\n      fc::temp_directory app_dir( graphene::utilities::temp_directory_path() );\n      auto genesis_file = create_genesis_file(app_dir);\n\n      graphene::app::application app1;\n      auto sharable_cfg = std::make_shared<boost::program_options::variables_map>();\n      auto& cfg = *sharable_cfg;\n      fc::set_option( cfg, \"p2p-endpoint\", app1_p2p_endpoint_str );\n      fc::set_option( cfg, \"genesis-json\", genesis_file );\n      fc::set_option( cfg, \"seed-nodes\", string(\"[]\") );\n      app1.initialize(app_dir.path(), sharable_cfg);\n      BOOST_TEST_MESSAGE( \"Starting app1 and waiting\" );\n      app1.startup();\n\n      auto node_startup_wait_time = fc::seconds(15);\n\n      fc::wait_for( node_startup_wait_time, [&app1,port] () {\n         const auto status = app1.p2p_node()->network_get_info();\n         return status[\"listening_on\"].as<fc::ip::endpoint>( 5 ).port() == port;\n      });\n\n      // Start app2\n      BOOST_TEST_MESSAGE( \"Creating and initializing app2\" );\n\n      fc::temp_directory app2_dir( graphene::utilities::temp_directory_path() );\n      graphene::app::application app2;\n      auto sharable_cfg2 = std::make_shared<boost::program_options::variables_map>();\n      auto& cfg2 = *sharable_cfg2;\n      fc::set_option( cfg2, \"genesis-json\", genesis_file );\n      fc::set_option( cfg2, \"seed-nodes\", app2_seed_nodes_str );\n      app2.initialize(app2_dir.path(), sharable_cfg2);\n\n      BOOST_TEST_MESSAGE( \"Starting app2 and waiting for connection\" );\n      app2.startup();\n\n      fc::wait_for( node_startup_wait_time, [&app1] () {\n         if( app1.p2p_node()->get_connection_count() > 0 )\n         {\n            auto peers = app1.p2p_node()->get_connected_peers();\n            BOOST_REQUIRE_EQUAL( peers.size(), 1u );\n            const auto& peer_info = peers.front().info;\n            auto itr = peer_info.find( \"peer_needs_sync_items_from_us\" );\n            if( itr == peer_info.end() )\n               return false;\n            return !itr->value().as<bool>(1);\n         }\n         return false;\n      });\n\n      BOOST_REQUIRE_EQUAL(app1.p2p_node()->get_connection_count(), 1u);\n      BOOST_CHECK_EQUAL(std::string(app1.p2p_node()->get_connected_peers().front().host.get_address()), \"127.0.0.1\");\n      BOOST_TEST_MESSAGE( \"app1 and app2 successfully connected\" );\n\n      std::shared_ptr<chain::database> db1 = app1.chain_database();\n      std::shared_ptr<chain::database> db2 = app2.chain_database();\n\n      BOOST_CHECK_EQUAL( db1->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 0 );\n\n      // Transaction test\n      BOOST_TEST_MESSAGE( \"Creating transfer tx\" );\n      graphene::chain::precomputable_transaction trx;\n      {\n         account_id_type nathan_id = db2->get_index_type<account_index>().indices().get<by_name>().find( \"nathan\" )\n                                        ->get_id();\n         fc::ecc::private_key nathan_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n\n         balance_claim_operation claim_op;\n         balance_id_type bid = balance_id_type();\n         claim_op.deposit_to_account = nathan_id;\n         claim_op.balance_to_claim = bid;\n         claim_op.balance_owner_key = nathan_key.get_public_key();\n         claim_op.total_claimed = bid(*db1).balance;\n         trx.operations.push_back( claim_op );\n         db1->current_fee_schedule().set_fee( trx.operations.back() );\n\n         transfer_operation xfer_op;\n         xfer_op.from = nathan_id;\n         xfer_op.to = GRAPHENE_NULL_ACCOUNT;\n         xfer_op.amount = asset( 1000000 );\n         trx.operations.push_back( xfer_op );\n         db1->current_fee_schedule().set_fee( trx.operations.back() );\n\n         trx.set_expiration( db1->get_slot_time( 10 ) );\n         trx.sign( nathan_key, db1->get_chain_id() );\n         trx.validate();\n      }\n\n      BOOST_TEST_MESSAGE( \"Pushing tx locally on db1\" );\n      processed_transaction ptrx = db1->push_transaction(trx);\n\n      BOOST_CHECK_EQUAL( db1->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 );\n      BOOST_CHECK_EQUAL( db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 0 );\n\n      BOOST_TEST_MESSAGE( \"Broadcasting tx\" );\n      app1.p2p_node()->broadcast(graphene::net::trx_message(trx));\n\n      auto broadcast_wait_time = fc::seconds(15);\n\n      fc::wait_for( broadcast_wait_time, [db2] () {\n         return db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value == 1000000;\n      });\n\n      BOOST_CHECK_EQUAL( db1->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 );\n      BOOST_CHECK_EQUAL( db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 );\n\n      // Block test\n      BOOST_TEST_MESSAGE( \"Generating block on db2\" );\n      fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n\n      // the other node will reject the block if its timestamp is in the future, so we wait\n      fc::wait_for( broadcast_wait_time, [db2] () {\n         return db2->get_slot_time(1) <= fc::time_point::now();\n      });\n\n      auto block_1 = db2->generate_block(\n         db2->get_slot_time(1),\n         db2->get_scheduled_witness(1),\n         committee_key,\n         database::skip_nothing);\n\n      BOOST_CHECK_EQUAL( db1->head_block_num(), 0u );\n      BOOST_CHECK_EQUAL( db2->head_block_num(), 1u );\n      BOOST_CHECK_EQUAL( block_1.block_num(), 1u );\n\n      BOOST_TEST_MESSAGE( \"Broadcasting block\" );\n      app2.p2p_node()->broadcast(graphene::net::block_message( block_1 ));\n\n      fc::wait_for( broadcast_wait_time, [db1] () {\n         return db1->head_block_num() == 1;\n      });\n\n      BOOST_TEST_MESSAGE( \"Verifying nodes are still connected\" );\n      BOOST_CHECK_EQUAL(app1.p2p_node()->get_connection_count(), 1u);\n      BOOST_CHECK_EQUAL(app1.chain_database()->head_block_num(), 1u);\n\n      BOOST_TEST_MESSAGE( \"Checking GRAPHENE_NULL_ACCOUNT has balance\" );\n      BOOST_CHECK_EQUAL( db1->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 );\n      BOOST_CHECK_EQUAL( db2->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value, 1000000 );\n\n      // Start app3\n      BOOST_TEST_MESSAGE( \"Creating and initializing app3\" );\n\n      fc::temp_directory app3_dir( graphene::utilities::temp_directory_path() );\n      graphene::app::application app3;\n      auto sharable_cfg3 = std::make_shared<boost::program_options::variables_map>();\n      auto& cfg3 = *sharable_cfg3;\n      fc::set_option( cfg3, \"genesis-json\", genesis_file );\n      fc::set_option( cfg3, \"seed-nodes\", app3_seed_nodes_str );\n      fc::set_option( cfg3, \"p2p-accept-incoming-connections\", false );\n      app3.initialize(app3_dir.path(), sharable_cfg3);\n\n      BOOST_TEST_MESSAGE( \"Starting app3 and waiting for connection\" );\n      app3.startup();\n\n      fc::wait_for( node_startup_wait_time, [&app1] () {\n         if( app1.p2p_node()->get_connection_count() < 2 )\n            return false;\n         auto peers = app1.p2p_node()->get_connected_peers();\n         if( peers.size() < 2 )\n            return false;\n         for( const auto& peer : peers )\n         {\n            auto itr = peer.info.find( \"peer_needs_sync_items_from_us\" );\n            if( itr == peer.info.end() )\n               return false;\n            if( itr->value().as<bool>(1) )\n               return false;\n         }\n         return true;\n      });\n\n      BOOST_REQUIRE_EQUAL(app1.p2p_node()->get_connection_count(), 2u);\n      BOOST_TEST_MESSAGE( \"app1 and app3 successfully connected\" );\n\n      BOOST_TEST_MESSAGE( \"Verifying app3 is synced\" );\n      BOOST_CHECK_EQUAL( app3.chain_database()->head_block_num(), 1u);\n      BOOST_CHECK_EQUAL( app3.chain_database()->get_balance( GRAPHENE_NULL_ACCOUNT, asset_id_type() ).amount.value,\n                         1000000 );\n\n      auto new_peer_wait_time = fc::seconds(45);\n\n      BOOST_TEST_MESSAGE( \"Waiting for app2 and app3 to connect to each other\" );\n      fc::wait_for( new_peer_wait_time, [&app2] () {\n         if( app2.p2p_node()->get_connection_count() < 2 )\n            return false;\n         auto peers = app2.p2p_node()->get_connected_peers();\n         if( peers.size() < 2 )\n            return false;\n         for( const auto& peer : peers )\n         {\n            auto itr = peer.info.find( \"peer_needs_sync_items_from_us\" );\n            if( itr == peer.info.end() )\n               return false;\n            if( itr->value().as<bool>(1) )\n               return false;\n         }\n         return true;\n      });\n\n      BOOST_REQUIRE_EQUAL(app3.p2p_node()->get_connection_count(), 2u);\n      BOOST_TEST_MESSAGE( \"app2 and app3 successfully connected\" );\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// a contrived example to test the breaking out of application_impl to a header file\nBOOST_AUTO_TEST_CASE(application_impl_breakout) {\n\n   static graphene::app::application my_app;\n\n   class test_impl : public graphene::app::detail::application_impl {\n      // override the constructor, just to test that we can\n   public:\n      test_impl() : application_impl(my_app) {}\n      bool has_item(const net::item_id& id) override {\n         return true;\n      }\n   };\n\n   test_impl impl;\n   graphene::net::item_id id;\n   BOOST_CHECK(impl.has_item(id));\n}\n"
  },
  {
    "path": "tests/cli/main.cpp",
    "content": "/*\n * Copyright (c) 2018 John Jones, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/app/application.hpp>\n#include <graphene/app/plugin.hpp>\n\n#include <graphene/utilities/key_conversion.hpp>\n#include <graphene/utilities/tempdir.hpp>\n\n#include <graphene/account_history/account_history_plugin.hpp>\n#include <graphene/api_helper_indexes/api_helper_indexes.hpp>\n#include <graphene/market_history/market_history_plugin.hpp>\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n#include <graphene/egenesis/egenesis.hpp>\n#include <graphene/wallet/wallet.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <fc/thread/thread.hpp>\n#include <fc/network/http/websocket.hpp>\n#include <fc/rpc/websocket_api.hpp>\n#include <fc/rpc/cli.hpp>\n#include <fc/crypto/base58.hpp>\n#include <fc/crypto/hex.hpp>\n\n#include <fc/crypto/aes.hpp>\n\n#include <thread>\n\n#include <boost/filesystem/path.hpp>\n\n#include \"../common/init_unit_test_suite.hpp\"\n#include \"../common/genesis_file_util.hpp\"\n#include \"../common/program_options_util.hpp\"\n#include \"../common/utils.hpp\"\n\n#ifdef _WIN32\n/*****\n * Global Initialization for Windows\n * ( sets up Winsock stuf )\n */\nint sockInit(void)\n{\n   WSADATA wsa_data;\n   return WSAStartup(MAKEWORD(1,1), &wsa_data);\n}\nint sockQuit(void)\n{\n   return WSACleanup();\n}\n#endif\n\n/*********************\n * Helper Methods\n *********************/\n\nusing std::exception;\nusing std::cerr;\n\n#define INVOKE(test) ((struct test*)this)->test_method();\n\n///////////\n/// @brief Start the application\n/// @param app_dir the temporary directory to use\n/// @param server_port_number to be filled with the rpc endpoint port number\n/// @returns the application object\n//////////\nstd::shared_ptr<graphene::app::application> start_application(fc::temp_directory& app_dir, int& server_port_number) {\n   auto app1 = std::make_shared<graphene::app::application>();\n\n   app1->register_plugin<graphene::account_history::account_history_plugin>(true);\n   app1->register_plugin< graphene::market_history::market_history_plugin >(true);\n   app1->register_plugin< graphene::grouped_orders::grouped_orders_plugin>(true);\n   app1->register_plugin< graphene::api_helper_indexes::api_helper_indexes>(true);\n   app1->register_plugin<graphene::custom_operations::custom_operations_plugin>(true);\n\n   auto sharable_cfg = std::make_shared<boost::program_options::variables_map>();\n   auto& cfg = *sharable_cfg;\n   server_port_number = fc::network::get_available_port();\n   auto p2p_port = server_port_number;\n   for( size_t i = 0; i < 10 && p2p_port == server_port_number; ++i )\n   {\n      p2p_port = fc::network::get_available_port();\n   }\n   BOOST_REQUIRE( p2p_port != server_port_number );\n   fc::set_option( cfg, \"rpc-endpoint\", string(\"127.0.0.1:\") + std::to_string(server_port_number) );\n   fc::set_option( cfg, \"p2p-endpoint\", string(\"0.0.0.0:\") + std::to_string(p2p_port) );\n   fc::set_option( cfg, \"genesis-json\", create_genesis_file(app_dir) );\n   fc::set_option( cfg, \"seed-nodes\", string(\"[]\") );\n   fc::set_option( cfg, \"custom-operations-start-block\", uint32_t(1) );\n   app1->initialize(app_dir.path(), sharable_cfg);\n\n   app1->startup();\n\n   return app1;\n}\n\n///////////\n/// Send a block to the db\n/// @param app the application\n/// @param returned_block the signed block\n/// @returns true on success\n///////////\nbool generate_block(std::shared_ptr<graphene::app::application> app, graphene::chain::signed_block& returned_block)\n{\n   try {\n      fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n      auto db = app->chain_database();\n      returned_block = db->generate_block( db->get_slot_time(1),\n                                         db->get_scheduled_witness(1),\n                                         committee_key,\n                                         database::skip_nothing );\n      return true;\n   } catch (exception &e) {\n      return false;\n   }\n}\n\nbool generate_block(std::shared_ptr<graphene::app::application> app)\n{\n   graphene::chain::signed_block returned_block;\n   return generate_block(app, returned_block);\n}\n\n\nsigned_block generate_block(std::shared_ptr<graphene::app::application> app, uint32_t skip, const fc::ecc::private_key& key, int miss_blocks)\n{\n   // skip == ~0 will skip checks specified in database::validation_steps\n   skip |= database::skip_undo_history_check;\n\n   auto db = app->chain_database();\n   auto block = db->generate_block(db->get_slot_time(miss_blocks + 1),\n                                   db->get_scheduled_witness(miss_blocks + 1),\n                                   key, skip);\n   db->clear_pending();\n   return block;\n}\n\n\n//////\n// Generate blocks until the timestamp\n//////\nuint32_t generate_blocks(std::shared_ptr<graphene::app::application> app, fc::time_point_sec timestamp)\n{\n   fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n   uint32_t skip = ~0;\n   auto db = app->chain_database();\n\n   generate_block(app);\n   auto slots_to_miss = db->get_slot_at_time(timestamp);\n   if( slots_to_miss <= 1 )\n      return 1;\n   --slots_to_miss;\n   generate_block(app, skip, committee_key, slots_to_miss);\n   return 2;\n}\n\n\n///////////\n/// @brief Skip intermediate blocks, and generate a maintenance block\n/// @param app the application\n/// @returns true on success\n///////////\nbool generate_maintenance_block(std::shared_ptr<graphene::app::application> app) {\n   try {\n      fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n      uint32_t skip = ~0;\n      auto db = app->chain_database();\n      auto maint_time = db->get_dynamic_global_properties().next_maintenance_time;\n      auto slots_to_miss = db->get_slot_at_time(maint_time);\n      db->generate_block(db->get_slot_time(slots_to_miss),\n            db->get_scheduled_witness(slots_to_miss),\n            committee_key,\n            skip);\n      return true;\n   } catch (exception& e)\n   {\n      return false;\n   }\n}\n\n///////////\n/// Check if hardfork core-2262 has passed\n///////////\nbool is_hf2262_passed(std::shared_ptr<graphene::app::application> app) {\n   auto db = app->chain_database();\n   auto maint_time = db->get_dynamic_global_properties().next_maintenance_time;\n   return HARDFORK_CORE_2262_PASSED( maint_time );\n}\n\n///////////\n/// @brief a class to make connecting to the application server easier\n///////////\nclass client_connection\n{\npublic:\n   /////////\n   // constructor\n   /////////\n   client_connection(\n      std::shared_ptr<graphene::app::application> app,\n      const fc::temp_directory& data_dir,\n      const int server_port_number,\n      const std::string custom_wallet_filename = \"wallet.json\"\n   )\n   {\n      wallet_data.chain_id = app->chain_database()->get_chain_id();\n      wallet_data.ws_server = \"ws://127.0.0.1:\" + std::to_string(server_port_number);\n      wallet_data.ws_user = \"\";\n      wallet_data.ws_password = \"\";\n      websocket_connection  = websocket_client.connect( wallet_data.ws_server );\n\n      api_connection = std::make_shared<fc::rpc::websocket_api_connection>( websocket_connection,\n                                                                            GRAPHENE_MAX_NESTED_OBJECTS );\n\n      remote_login_api = api_connection->get_remote_api< graphene::app::login_api >(1);\n      BOOST_CHECK(remote_login_api->login( wallet_data.ws_user, wallet_data.ws_password ) );\n\n      wallet_api_ptr = std::make_shared<graphene::wallet::wallet_api>(wallet_data, remote_login_api);\n      wallet_filename = data_dir.path().generic_string() + \"/\" + custom_wallet_filename;\n      wallet_api_ptr->set_wallet_filename(wallet_filename);\n\n      wallet_api = fc::api<graphene::wallet::wallet_api>(wallet_api_ptr);\n\n      wallet_cli = std::make_shared<fc::rpc::cli>(GRAPHENE_MAX_NESTED_OBJECTS);\n      for( auto& name_formatter : wallet_api_ptr->get_result_formatters() )\n         wallet_cli->format_result( name_formatter.first, name_formatter.second );\n   }\n   ~client_connection()\n   {\n      wallet_cli->stop();\n   }\npublic:\n   fc::http::websocket_client websocket_client;\n   graphene::wallet::wallet_data wallet_data;\n   fc::http::websocket_connection_ptr websocket_connection;\n   std::shared_ptr<fc::rpc::websocket_api_connection> api_connection;\n   fc::api<login_api> remote_login_api;\n   std::shared_ptr<graphene::wallet::wallet_api> wallet_api_ptr;\n   fc::api<graphene::wallet::wallet_api> wallet_api;\n   std::shared_ptr<fc::rpc::cli> wallet_cli;\n   std::string wallet_filename;\n};\n\n///////////////////////////////\n// Cli Wallet Fixture\n///////////////////////////////\n\nstruct cli_fixture\n{\n#ifdef _WIN32\n   struct socket_maintainer {\n      socket_maintainer() {\n         sockInit();\n      }\n      ~socket_maintainer() {\n         sockQuit();\n      }\n   } sock_maintainer;\n#endif\n   int server_port_number;\n   fc::temp_directory app_dir;\n   std::shared_ptr<graphene::app::application> app1;\n   client_connection con;\n   std::vector<std::string> nathan_keys;\n\n   cli_fixture() :\n      server_port_number(0),\n      app_dir( graphene::utilities::temp_directory_path() ),\n      app1( start_application(app_dir, server_port_number) ),\n      con( app1, app_dir, server_port_number ),\n      nathan_keys( {\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"} )\n   {\n      BOOST_TEST_MESSAGE(\"Setup cli_wallet::boost_fixture_test_case\");\n\n      using namespace graphene::chain;\n      using namespace graphene::app;\n\n      try\n      {\n         BOOST_TEST_MESSAGE(\"Setting wallet password\");\n         con.wallet_api_ptr->set_password(\"supersecret\");\n         con.wallet_api_ptr->unlock(\"supersecret\");\n\n         // import Nathan account\n         BOOST_TEST_MESSAGE(\"Importing nathan key\");\n         BOOST_CHECK_EQUAL(nathan_keys[0], \"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\");\n         BOOST_CHECK(con.wallet_api_ptr->import_key(\"nathan\", nathan_keys[0]));\n      } catch( fc::exception& e ) {\n         edump((e.to_detail_string()));\n         throw;\n      }\n   }\n\n   ~cli_fixture()\n   {\n      BOOST_TEST_MESSAGE(\"Cleanup cli_wallet::boost_fixture_test_case\");\n   }\n};\n\n///////////////////////////////\n// Tests\n///////////////////////////////\n\n////////////////\n// Start a server and connect using the same calls as the CLI\n////////////////\nBOOST_FIXTURE_TEST_CASE( cli_connect, cli_fixture )\n{\n   BOOST_TEST_MESSAGE(\"Testing wallet connection.\");\n}\n\n////////////////\n// Start a server and connect using the same calls as the CLI\n// Quit wallet and be sure that file was saved correctly\n////////////////\nBOOST_FIXTURE_TEST_CASE( cli_quit, cli_fixture )\n{\n   BOOST_TEST_MESSAGE(\"Testing wallet connection and quit command.\");\n   BOOST_CHECK_THROW( con.wallet_api_ptr->quit(), fc::canceled_exception );\n}\n\nBOOST_FIXTURE_TEST_CASE( cli_help_gethelp, cli_fixture )\n{\n   BOOST_TEST_MESSAGE(\"Testing help and gethelp commands.\");\n   auto formatters = con.wallet_api_ptr->get_result_formatters();\n\n   string result = con.wallet_api_ptr->help();\n   BOOST_CHECK( result.find(\"gethelp\") != string::npos );\n   if( formatters.find(\"help\") != formatters.end() )\n   {\n      BOOST_TEST_MESSAGE(\"Testing formatter of help\");\n      string output = formatters[\"help\"](fc::variant(result), fc::variants());\n      BOOST_CHECK( output.find(\"gethelp\") != string::npos );\n   }\n\n   result = con.wallet_api_ptr->gethelp( \"transfer\" );\n   BOOST_CHECK( result.find(\"usage\") != string::npos );\n   if( formatters.find(\"gethelp\") != formatters.end() )\n   {\n      BOOST_TEST_MESSAGE(\"Testing formatter of gethelp\");\n      string output = formatters[\"gethelp\"](fc::variant(result), fc::variants());\n      BOOST_CHECK( output.find(\"usage\") != string::npos );\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( upgrade_nathan_account, cli_fixture )\n{\n   try\n   {\n      BOOST_TEST_MESSAGE(\"Upgrade Nathan's account\");\n\n      account_object nathan_acct_before_upgrade, nathan_acct_after_upgrade;\n      std::vector<signed_transaction> import_txs;\n      signed_transaction upgrade_tx;\n\n      BOOST_TEST_MESSAGE(\"Importing nathan's balance\");\n      import_txs = con.wallet_api_ptr->import_balance(\"nathan\", nathan_keys, true);\n      nathan_acct_before_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // upgrade nathan\n      BOOST_TEST_MESSAGE(\"Upgrading Nathan to LTM\");\n      upgrade_tx = con.wallet_api_ptr->upgrade_account(\"nathan\", true);\n      nathan_acct_after_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // verify that the upgrade was successful\n      BOOST_CHECK_PREDICATE(\n         std::not_equal_to<uint32_t>(),\n         (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch())\n         (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch())\n      );\n      BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member());\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( create_new_account, cli_fixture )\n{\n   try\n   {\n      INVOKE(upgrade_nathan_account);\n\n      // create a new account\n      graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key();\n      BOOST_CHECK(!bki.brain_priv_key.empty());\n      signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(\n         bki.brain_priv_key, \"jmjatlanta\", \"nathan\", \"nathan\", true\n      );\n      // save the private key for this new account in the wallet file\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"jmjatlanta\", bki.wif_priv_key));\n      con.wallet_api_ptr->save_wallet_file(con.wallet_filename);\n\n      // attempt to give jmjatlanta some bitshares\n      BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to jmjatlanta\");\n      signed_transaction transfer_tx = con.wallet_api_ptr->transfer(\n         \"nathan\", \"jmjatlanta\", \"10000\", \"1.3.0\", \"Here are some CORE token for your new account\", true\n      );\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( uia_tests, cli_fixture )\n{\n   try\n   {\n      BOOST_TEST_MESSAGE(\"Cli UIA Tests\");\n\n      INVOKE(upgrade_nathan_account);\n\n      BOOST_CHECK(generate_block(app1));\n\n      account_object nathan_acct = con.wallet_api_ptr->get_account(\"nathan\");\n\n      auto formatters = con.wallet_api_ptr->get_result_formatters();\n\n      auto check_account_last_history = [&]( string account, string keyword ) {\n         auto history = con.wallet_api_ptr->get_relative_account_history(account, 0, 1, 0);\n         BOOST_REQUIRE_GT( history.size(), 0 );\n         BOOST_CHECK( history[0].description.find( keyword ) != string::npos );\n      };\n      auto check_nathan_last_history = [&]( string keyword ) {\n         check_account_last_history( \"nathan\", keyword );\n      };\n\n      check_nathan_last_history( \"account_upgrade_operation\" );\n\n      // Create new asset called BOBCOIN\n      {\n         BOOST_TEST_MESSAGE(\"Create UIA 'BOBCOIN'\");\n         graphene::chain::asset_options asset_ops;\n         asset_ops.issuer_permissions = DEFAULT_UIA_ASSET_ISSUER_PERMISSION;\n         asset_ops.flags = charge_market_fee | override_authority;\n         asset_ops.max_supply = 1000000;\n         asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1)));\n         auto result = con.wallet_api_ptr->create_asset(\"nathan\", \"BOBCOIN\", 4, asset_ops, {}, true);\n         if( formatters.find(\"create_asset\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of create_asset\");\n            string output = formatters[\"create_asset\"](\n                  fc::variant(result, FC_PACK_MAX_DEPTH), fc::variants());\n            BOOST_CHECK( output.find(\"BOBCOIN\") != string::npos );\n         }\n\n         BOOST_CHECK_THROW( con.wallet_api_ptr->get_asset_name(\"BOBCOI\"), fc::exception );\n         BOOST_CHECK_EQUAL( con.wallet_api_ptr->get_asset_name(\"BOBCOIN\"), \"BOBCOIN\" );\n         BOOST_CHECK_EQUAL( con.wallet_api_ptr->get_asset_symbol(\"BOBCOIN\"), \"BOBCOIN\" );\n\n         BOOST_CHECK_THROW( con.wallet_api_ptr->get_account_name(\"nath\"), fc::exception );\n         BOOST_CHECK_EQUAL( con.wallet_api_ptr->get_account_name(\"nathan\"), \"nathan\" );\n         BOOST_CHECK( con.wallet_api_ptr->get_account_id(\"nathan\") == con.wallet_api_ptr->get_account(\"nathan\").id );\n      }\n      BOOST_CHECK(generate_block(app1));\n\n      check_nathan_last_history( \"Create User-Issue Asset\" );\n      check_nathan_last_history( \"BOBCOIN\" );\n\n      auto bobcoin = con.wallet_api_ptr->get_asset(\"BOBCOIN\");\n\n      BOOST_CHECK( con.wallet_api_ptr->get_asset_id(\"BOBCOIN\") == bobcoin.id );\n\n      bool balance_formatter_tested = false;\n      auto check_bobcoin_balance = [&](string account, int64_t amount) {\n         auto balances = con.wallet_api_ptr->list_account_balances( account );\n         size_t count = 0;\n         for( auto& bal : balances )\n         {\n            if( bal.asset_id == bobcoin.id )\n            {\n               ++count;\n               BOOST_CHECK_EQUAL( bal.amount.value, amount );\n            }\n         }\n         BOOST_CHECK_EQUAL(count, 1u);\n\n         // Testing result formatter\n         if( !balance_formatter_tested && formatters.find(\"list_account_balances\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of list_account_balances\");\n            string output = formatters[\"list_account_balances\"](\n                  fc::variant(balances, FC_PACK_MAX_DEPTH ), fc::variants());\n            BOOST_CHECK( output.find(\"BOBCOIN\") != string::npos );\n            balance_formatter_tested = true;\n         }\n      };\n      auto check_nathan_bobcoin_balance = [&](int64_t amount) {\n         check_bobcoin_balance( \"nathan\", amount );\n      };\n\n      {\n         // Issue asset\n         BOOST_TEST_MESSAGE(\"Issue asset\");\n         con.wallet_api_ptr->issue_asset(\"init0\", \"3\", \"BOBCOIN\", \"new coin for you\", true);\n      }\n      BOOST_CHECK(generate_block(app1));\n\n      check_nathan_last_history( \"nathan issue 3 BOBCOIN to init0\" );\n      check_nathan_last_history( \"new coin for you\" );\n      check_account_last_history( \"init0\", \"nathan issue 3 BOBCOIN to init0\" );\n      check_account_last_history( \"init0\", \"new coin for you\" );\n\n      check_bobcoin_balance( \"init0\", 30000 );\n\n      {\n         // Override transfer, and test sign_memo and read_memo by the way\n         BOOST_TEST_MESSAGE(\"Override-transfer BOBCOIN from init0\");\n         auto handle = con.wallet_api_ptr->begin_builder_transaction();\n         override_transfer_operation op;\n         op.issuer = con.wallet_api_ptr->get_account( \"nathan\" ).id;\n         op.from = con.wallet_api_ptr->get_account( \"init0\" ).id;\n         op.to = con.wallet_api_ptr->get_account( \"nathan\" ).id;\n         op.amount = bobcoin.amount(10000);\n\n         const auto test_bki = con.wallet_api_ptr->suggest_brain_key();\n         auto test_pubkey = fc::json::to_string( test_bki.pub_key );\n         test_pubkey = test_pubkey.substr( 1, test_pubkey.size() - 2 );\n         idump( (test_pubkey) );\n         op.memo = con.wallet_api_ptr->sign_memo( \"nathan\", test_pubkey, \"get back some coin\" );\n         idump( (op.memo) );\n         con.wallet_api_ptr->add_operation_to_builder_transaction( handle, op );\n         con.wallet_api_ptr->set_fees_on_builder_transaction( handle, \"1.3.0\" );\n         con.wallet_api_ptr->sign_builder_transaction( handle, true );\n\n         auto memo = con.wallet_api_ptr->read_memo( *op.memo );\n         BOOST_CHECK_EQUAL( memo, \"get back some coin\" );\n\n         op.memo = con.wallet_api_ptr->sign_memo( test_pubkey, \"nathan\", \"another test\" );\n         idump( (op.memo) );\n         memo = con.wallet_api_ptr->read_memo( *op.memo );\n         BOOST_CHECK_EQUAL( memo, \"another test\" );\n\n         BOOST_CHECK_THROW( con.wallet_api_ptr->sign_memo( \"non-exist-account-or-label\", \"nathan\", \"some text\" ),\n                            fc::exception );\n         BOOST_CHECK_THROW( con.wallet_api_ptr->sign_memo( \"nathan\", \"non-exist-account-or-label\", \"some text\" ),\n                            fc::exception );\n      }\n      BOOST_CHECK(generate_block(app1));\n\n      check_nathan_last_history( \"nathan force-transfer 1 BOBCOIN from init0 to nathan\" );\n      check_nathan_last_history( \"get back some coin\" );\n      check_account_last_history( \"init0\", \"nathan force-transfer 1 BOBCOIN from init0 to nathan\" );\n      check_account_last_history( \"init0\", \"get back some coin\" );\n\n      check_bobcoin_balance( \"init0\", 20000 );\n      check_bobcoin_balance( \"nathan\", 10000 );\n\n      {\n         // Reserve / burn asset\n         BOOST_TEST_MESSAGE(\"Reserve/burn asset\");\n         con.wallet_api_ptr->reserve_asset(\"nathan\", \"1\", \"BOBCOIN\", true);\n      }\n      BOOST_CHECK(generate_block(app1));\n\n      check_nathan_last_history( \"Reserve (burn) 1 BOBCOIN\" );\n\n      check_nathan_bobcoin_balance( 0 );\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( mpa_tests, cli_fixture )\n{\n   try\n   {\n      BOOST_TEST_MESSAGE(\"Cli MPA Tests\");\n\n      INVOKE(upgrade_nathan_account);\n\n      account_object nathan_acct = con.wallet_api_ptr->get_account(\"nathan\");\n\n      auto formatters = con.wallet_api_ptr->get_result_formatters();\n\n      // Create new asset called BOBCOIN backed by CORE\n      try\n      {\n         BOOST_TEST_MESSAGE(\"Create MPA 'BOBCOIN'\");\n         graphene::chain::asset_options asset_ops;\n         asset_ops.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n         asset_ops.flags = charge_market_fee;\n         asset_ops.max_supply = 1000000;\n         asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1)));\n         graphene::chain::bitasset_options bit_opts;\n         auto result = con.wallet_api_ptr->create_asset(\"nathan\", \"BOBCOIN\", 4, asset_ops, bit_opts, true);\n         if( formatters.find(\"create_asset\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of create_asset\");\n            string output = formatters[\"create_asset\"](\n                  fc::variant(result, FC_PACK_MAX_DEPTH), fc::variants());\n            BOOST_CHECK( output.find(\"BOBCOIN\") != string::npos );\n         }\n      }\n      catch(exception& e)\n      {\n         BOOST_FAIL(e.what());\n      }\n      catch(...)\n      {\n         BOOST_FAIL(\"Unknown exception creating BOBCOIN\");\n      }\n      BOOST_CHECK(generate_block(app1));\n\n      auto check_nathan_last_history = [&]( string keyword ) {\n         auto history = con.wallet_api_ptr->get_relative_account_history(\"nathan\", 0, 1, 0);\n         BOOST_REQUIRE_GT( history.size(), 0 );\n         BOOST_CHECK( history[0].description.find( keyword ) != string::npos );\n      };\n\n      check_nathan_last_history( \"Create BitAsset\" );\n      check_nathan_last_history( \"BOBCOIN\" );\n\n      auto bobcoin = con.wallet_api_ptr->get_asset(\"BOBCOIN\");\n      {\n         // Update asset\n         BOOST_TEST_MESSAGE(\"Update asset\");\n         auto options = bobcoin.options;\n         BOOST_CHECK_EQUAL( options.max_supply.value, 1000000 );\n         options.max_supply = 2000000;\n         con.wallet_api_ptr->update_asset(\"BOBCOIN\", {}, options, true);\n         // Check\n         bobcoin = con.wallet_api_ptr->get_asset(\"BOBCOIN\");\n         BOOST_CHECK_EQUAL( bobcoin.options.max_supply.value, 2000000 );\n      }\n      BOOST_CHECK(generate_block(app1));\n      check_nathan_last_history( \"Update asset\" );\n\n      auto bitbobcoin = con.wallet_api_ptr->get_bitasset_data(\"BOBCOIN\");\n      {\n         // Update bitasset\n         BOOST_TEST_MESSAGE(\"Update bitasset\");\n         auto bitoptions = bitbobcoin.options;\n         BOOST_CHECK_EQUAL( bitoptions.feed_lifetime_sec, uint32_t(GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME) );\n         bitoptions.feed_lifetime_sec = 3600u;\n         con.wallet_api_ptr->update_bitasset(\"BOBCOIN\", bitoptions, true);\n         // Check\n         bitbobcoin = con.wallet_api_ptr->get_bitasset_data(\"BOBCOIN\");\n         BOOST_CHECK_EQUAL( bitbobcoin.options.feed_lifetime_sec, 3600u );\n      }\n      BOOST_CHECK(generate_block(app1));\n      check_nathan_last_history( \"Update bitasset\" );\n\n      {\n         // Play with asset fee pool\n         auto objs = con.wallet_api_ptr->get_object( object_id_type( bobcoin.dynamic_asset_data_id ) )\n                        .as<vector<asset_dynamic_data_object>>( FC_PACK_MAX_DEPTH );\n         idump( (objs) );\n         BOOST_REQUIRE_EQUAL( objs.size(), 1u );\n         asset_dynamic_data_object bobcoin_dyn = objs[0];\n         idump( (bobcoin_dyn) );\n         share_type old_pool = bobcoin_dyn.fee_pool;\n\n         BOOST_TEST_MESSAGE(\"Fund fee pool\");\n         con.wallet_api_ptr->fund_asset_fee_pool(\"nathan\", \"BOBCOIN\", \"2\", true);\n         objs = con.wallet_api_ptr->get_object( object_id_type( bobcoin.dynamic_asset_data_id ) )\n                   .as<vector<asset_dynamic_data_object>>( FC_PACK_MAX_DEPTH );\n         BOOST_REQUIRE_EQUAL( objs.size(), 1u );\n         bobcoin_dyn = objs[0];\n         share_type funded_pool = bobcoin_dyn.fee_pool;\n         BOOST_CHECK_EQUAL( funded_pool.value, old_pool.value + GRAPHENE_BLOCKCHAIN_PRECISION * 2 );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_last_history( \"Fund\" );\n\n         BOOST_TEST_MESSAGE(\"Claim fee pool\");\n         con.wallet_api_ptr->claim_asset_fee_pool(\"BOBCOIN\", \"1\", true);\n         objs = con.wallet_api_ptr->get_object( object_id_type( bobcoin.dynamic_asset_data_id ) )\n                   .as<vector<asset_dynamic_data_object>>( FC_PACK_MAX_DEPTH );\n         BOOST_REQUIRE_EQUAL( objs.size(), 1u );\n         bobcoin_dyn = objs[0];\n         share_type claimed_pool = bobcoin_dyn.fee_pool;\n         BOOST_CHECK_EQUAL( claimed_pool.value, old_pool.value + GRAPHENE_BLOCKCHAIN_PRECISION );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_last_history( \"Claim\" );\n      }\n\n      {\n         // Set price feed producer\n         BOOST_TEST_MESSAGE(\"Set price feed producer\");\n         asset_bitasset_data_object bob_bitasset = con.wallet_api_ptr->get_bitasset_data( \"BOBCOIN\" );\n         BOOST_CHECK_EQUAL( bob_bitasset.feeds.size(), 0u );\n\n         auto handle = con.wallet_api_ptr->begin_builder_transaction();\n         asset_update_feed_producers_operation aufp_op;\n         aufp_op.issuer = nathan_acct.id;\n         aufp_op.asset_to_update = bobcoin.id;\n         aufp_op.new_feed_producers = { nathan_acct.get_id() };\n         con.wallet_api_ptr->add_operation_to_builder_transaction( handle, aufp_op );\n         con.wallet_api_ptr->set_fees_on_builder_transaction( handle, \"1.3.0\" );\n         con.wallet_api_ptr->sign_builder_transaction( handle, true );\n\n         bob_bitasset = con.wallet_api_ptr->get_bitasset_data( \"BOBCOIN\" );\n         BOOST_CHECK_EQUAL( bob_bitasset.feeds.size(), 1u );\n         BOOST_CHECK( bob_bitasset.current_feed.settlement_price.is_null() );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_last_history( \"Update price feed producers\" );\n      }\n\n      {\n         // Publish price feed\n         BOOST_TEST_MESSAGE(\"Publish price feed\");\n         price_feed feed;\n         feed.settlement_price = price( asset(1,bobcoin.get_id()), asset(2) );\n         feed.core_exchange_rate = price( asset(1,bobcoin.get_id()), asset(1) );\n         con.wallet_api_ptr->publish_asset_feed( \"nathan\", \"BOBCOIN\", feed, true );\n         asset_bitasset_data_object bob_bitasset = con.wallet_api_ptr->get_bitasset_data( \"BOBCOIN\" );\n         BOOST_CHECK( bob_bitasset.current_feed.settlement_price == feed.settlement_price );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_last_history( \"Publish price feed\" );\n      }\n\n      bool balance_formatter_tested = false;\n      auto check_bobcoin_balance = [&](string account, int64_t amount) {\n         auto balances = con.wallet_api_ptr->list_account_balances( account );\n         size_t count = 0;\n         for( auto& bal : balances )\n         {\n            if( bal.asset_id == bobcoin.id )\n            {\n               ++count;\n               BOOST_CHECK_EQUAL( bal.amount.value, amount );\n            }\n         }\n         BOOST_CHECK_EQUAL(count, 1u);\n\n         // Testing result formatter\n         if( !balance_formatter_tested && formatters.find(\"list_account_balances\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of list_account_balances\");\n            string output = formatters[\"list_account_balances\"](\n                  fc::variant(balances, FC_PACK_MAX_DEPTH ), fc::variants());\n            BOOST_CHECK( output.find(\"BOBCOIN\") != string::npos );\n            balance_formatter_tested = true;\n         }\n      };\n      auto check_nathan_bobcoin_balance = [&](int64_t amount) {\n         check_bobcoin_balance( \"nathan\", amount );\n      };\n\n      {\n         // Borrow\n         BOOST_TEST_MESSAGE(\"Borrow BOBCOIN\");\n         auto calls = con.wallet_api_ptr->get_call_orders( \"BOBCOIN\", 10 );\n         BOOST_CHECK_EQUAL( calls.size(), 0u );\n         con.wallet_api_ptr->borrow_asset( \"nathan\", \"1\", \"BOBCOIN\", \"10\", true );\n         calls = con.wallet_api_ptr->get_call_orders( \"BOBCOIN\", 10 );\n         BOOST_REQUIRE_EQUAL( calls.size(), 1u );\n         BOOST_CHECK_EQUAL( calls.front().debt.value, 10000 );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_bobcoin_balance( 10000 );\n         check_nathan_last_history( \"Adjust debt position\" );\n      }\n\n      {\n         // Settle\n         BOOST_TEST_MESSAGE(\"Settle BOBCOIN\");\n         auto settles = con.wallet_api_ptr->get_settle_orders( \"BOBCOIN\", 10 );\n         BOOST_CHECK_EQUAL( settles.size(), 0u );\n         con.wallet_api_ptr->settle_asset( \"nathan\", \"0.2\", \"BOBCOIN\", true );\n         settles = con.wallet_api_ptr->get_settle_orders( \"BOBCOIN\", 10 );\n         BOOST_REQUIRE_EQUAL( settles.size(), 1u );\n         BOOST_CHECK_EQUAL( settles.front().balance.amount.value, 2000 );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_bobcoin_balance( 8000 );\n         check_nathan_last_history( \"Force-settle\" );\n      }\n\n      {\n         // Transfer\n         BOOST_TEST_MESSAGE(\"Transfer some BOBCOIN to init0\");\n         con.wallet_api_ptr->transfer2( \"nathan\", \"init0\", \"0.5\", \"BOBCOIN\", \"\" );\n         con.wallet_api_ptr->transfer( \"nathan\", \"init0\", \"10000\", \"1.3.0\", \"\" );\n\n         BOOST_CHECK(generate_block(app1));\n         check_bobcoin_balance( \"init0\", 5000 );\n         check_nathan_bobcoin_balance( 3000 );\n         check_nathan_last_history( \"Transfer\" );\n\n      }\n\n      {\n         // Nathan places an order\n         BOOST_TEST_MESSAGE(\"Nathan place an order to buy BOBCOIN\");\n         auto orders = con.wallet_api_ptr->get_limit_orders( \"BOBCOIN\", \"1.3.0\", 10 );\n         BOOST_CHECK_EQUAL( orders.size(), 0u );\n         con.wallet_api_ptr->sell_asset( \"nathan\", \"100\", \"1.3.0\", \"1\", \"BOBCOIN\", 300, false, true );\n         orders = con.wallet_api_ptr->get_limit_orders( \"BOBCOIN\", \"1.3.0\", 10 );\n         BOOST_REQUIRE_EQUAL( orders.size(), 1u );\n         BOOST_CHECK_EQUAL( orders.front().for_sale.value, 100 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         limit_order_id_type nathan_order_id = orders.front().get_id();\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_bobcoin_balance( 3000 );\n         check_nathan_last_history( \"Create limit order\" );\n\n         // init0 place an order to partially fill Nathan's order\n         BOOST_TEST_MESSAGE(\"init0 place an order to sell BOBCOIN\");\n         con.wallet_api_ptr->sell_asset( \"init0\", \"0.1\", \"BOBCOIN\", \"1\", \"1.3.0\", 200, true, true );\n         orders = con.wallet_api_ptr->get_limit_orders( \"BOBCOIN\", \"1.3.0\", 10 );\n         BOOST_REQUIRE_EQUAL( orders.size(), 1u );\n         BOOST_CHECK_EQUAL( orders.front().for_sale.value, 90 * GRAPHENE_BLOCKCHAIN_PRECISION );\n\n         BOOST_CHECK(generate_block(app1));\n         check_bobcoin_balance( \"init0\", 4000 );\n         check_nathan_bobcoin_balance( 4000 );\n         check_nathan_last_history( \"as maker\" );\n\n         // Nathan cancel order\n         BOOST_TEST_MESSAGE(\"Nathan cancel order\");\n         con.wallet_api_ptr->cancel_order( nathan_order_id, true );\n         orders = con.wallet_api_ptr->get_limit_orders( \"BOBCOIN\", \"1.3.0\", 10 );\n         BOOST_CHECK_EQUAL( orders.size(), 0u );\n\n         BOOST_CHECK(generate_block(app1));\n         check_nathan_bobcoin_balance( 4000 );\n         check_nathan_last_history( \"Cancel limit order\" );\n      }\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n///////////////////////\n// Start a server and connect using the same calls as the CLI\n// Vote for two witnesses, and make sure they both stay there\n// after a maintenance block\n///////////////////////\nBOOST_FIXTURE_TEST_CASE( cli_vote_for_2_witnesses, cli_fixture )\n{\n   try\n   {\n      BOOST_TEST_MESSAGE(\"Cli Vote Test for 2 Witnesses\");\n\n      INVOKE(create_new_account);\n\n      // get the details for init1\n      witness_object init1_obj = con.wallet_api_ptr->get_witness(\"init1\");\n      int init1_start_votes = init1_obj.total_votes;\n      // Vote for a witness\n      signed_transaction vote_witness1_tx = con.wallet_api_ptr->vote_for_witness(\"jmjatlanta\", \"init1\", true, true);\n\n      // generate a block to get things started\n      BOOST_CHECK(generate_block(app1));\n      // wait for a maintenance interval\n      BOOST_CHECK(generate_maintenance_block(app1));\n\n      // Verify that the vote is there\n      init1_obj = con.wallet_api_ptr->get_witness(\"init1\");\n      witness_object init2_obj = con.wallet_api_ptr->get_witness(\"init2\");\n      int init1_middle_votes = init1_obj.total_votes;\n      if( !is_hf2262_passed(app1) )\n         BOOST_CHECK(init1_middle_votes > init1_start_votes);\n\n      // Vote for a 2nd witness\n      int init2_start_votes = init2_obj.total_votes;\n      signed_transaction vote_witness2_tx = con.wallet_api_ptr->vote_for_witness(\"jmjatlanta\", \"init2\", true, true);\n\n      // send another block to trigger maintenance interval\n      BOOST_CHECK(generate_maintenance_block(app1));\n\n      // Verify that both the first vote and the 2nd are there\n      init2_obj = con.wallet_api_ptr->get_witness(\"init2\");\n      init1_obj = con.wallet_api_ptr->get_witness(\"init1\");\n\n      int init2_middle_votes = init2_obj.total_votes;\n      if( !is_hf2262_passed(app1) )\n         BOOST_CHECK(init2_middle_votes > init2_start_votes);\n      int init1_last_votes = init1_obj.total_votes;\n      if( !is_hf2262_passed(app1) )\n         BOOST_CHECK(init1_last_votes > init1_start_votes);\n\n      {\n         auto history = con.wallet_api_ptr->get_account_history_by_operations(\n                              \"jmjatlanta\", {6}, 0, 1); // 6 - account_update_operation\n         BOOST_REQUIRE_GT( history.details.size(), 0 );\n         BOOST_CHECK( history.details[0].description.find( \"Update Account 'jmjatlanta'\" ) != string::npos );\n\n         // Testing result formatter\n         auto formatters = con.wallet_api_ptr->get_result_formatters();\n         if( formatters.find(\"get_account_history_by_operations\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of get_account_history_by_operations\");\n            string output = formatters[\"get_account_history_by_operations\"](\n                  fc::variant(history, FC_PACK_MAX_DEPTH), fc::variants());\n            BOOST_CHECK( output.find(\"Update Account 'jmjatlanta'\") != string::npos );\n         }\n      }\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( cli_get_signed_transaction_signers, cli_fixture )\n{\n   try\n   {\n      INVOKE(upgrade_nathan_account);\n\n      // register account and transfer funds\n      const auto test_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n         \"test\", test_bki.pub_key, test_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      con.wallet_api_ptr->transfer(\"nathan\", \"test\", \"1000\", \"1.3.0\", \"\", true);\n\n      // import key and save wallet\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"test\", test_bki.wif_priv_key));\n      con.wallet_api_ptr->save_wallet_file(con.wallet_filename);\n\n      // create transaction and check expected result\n      auto signed_trx = con.wallet_api_ptr->transfer(\"test\", \"nathan\", \"10\", \"1.3.0\", \"\", true);\n\n      const auto &test_acc = con.wallet_api_ptr->get_account(\"test\");\n      flat_set<public_key_type> expected_signers = {test_bki.pub_key};\n      vector<flat_set<account_id_type> > expected_key_refs{{test_acc.get_id(), test_acc.get_id()}};\n\n      auto signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);\n      BOOST_CHECK(signers == expected_signers);\n\n      auto key_refs = con.wallet_api_ptr->get_key_references({expected_signers.begin(), expected_signers.end()});\n      BOOST_CHECK(key_refs == expected_key_refs);\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n\n///////////////////////\n// Wallet RPC\n// Test adding an unnecessary signature to a transaction\n///////////////////////\nBOOST_FIXTURE_TEST_CASE(cli_sign_tx_with_unnecessary_signature, cli_fixture) {\n   try {\n      auto db = app1->chain_database();\n\n      account_object nathan_acct = con.wallet_api_ptr->get_account(\"nathan\");\n      INVOKE(upgrade_nathan_account);\n\n      // Register Bob account\n      const auto bob_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"bob\", bob_bki.pub_key, bob_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n\n      // Register Charlie account\n      const graphene::wallet::brain_key_info charlie_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"charlie\", charlie_bki.pub_key, charlie_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      const account_object &charlie_acc = con.wallet_api_ptr->get_account(\"charlie\");\n\n      // Import Bob's key\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"bob\", bob_bki.wif_priv_key));\n\n      // Create transaction with a transfer operation from Nathan to Charlie\n      transfer_operation top;\n      top.from = nathan_acct.id;\n      top.to = charlie_acc.id;\n      top.amount = asset(5000);\n      top.fee = db->current_fee_schedule().calculate_fee(top);\n\n      signed_transaction test_tx;\n      test_tx.operations.push_back(top);\n\n      // Sign the transaction with the implied nathan's key and the explicitly yet unnecessary Bob's key\n      auto signed_trx = con.wallet_api_ptr->sign_transaction2(test_tx, {bob_bki.pub_key}, false);\n\n      // Check for two signatures on the transaction\n      BOOST_CHECK_EQUAL(signed_trx.signatures.size(), 2);\n      flat_set<public_key_type> signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);\n\n      // Check that the signed transaction contains both Nathan's required signature and Bob's unnecessary signature\n      BOOST_CHECK_EQUAL(nathan_acct.active.get_keys().size(), 1);\n      flat_set<public_key_type> expected_signers = {bob_bki.pub_key, nathan_acct.active.get_keys().front()};\n      flat_set<public_key_type> actual_signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);\n      BOOST_CHECK(signers == expected_signers);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n\n///////////////////////\n// Wallet RPC\n// Test adding an unnecessary signature to a transaction builder\n///////////////////////\nBOOST_FIXTURE_TEST_CASE(cli_sign_tx_builder_with_unnecessary_signature, cli_fixture) {\n   try {\n      auto db = app1->chain_database();\n\n      account_object nathan_acct = con.wallet_api_ptr->get_account(\"nathan\");\n      INVOKE(upgrade_nathan_account);\n\n      // Register Bob account\n      const auto bob_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"bob\", bob_bki.pub_key, bob_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n\n      // Register Charlie account\n      const graphene::wallet::brain_key_info charlie_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"charlie\", charlie_bki.pub_key, charlie_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      const account_object &charlie_acc = con.wallet_api_ptr->get_account(\"charlie\");\n\n      // Import Bob's key\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"bob\", bob_bki.wif_priv_key));\n\n      // Use transaction builder to build a transaction with a transfer operation from Nathan to Charlie\n      graphene::wallet::transaction_handle_type tx_handle = con.wallet_api_ptr->begin_builder_transaction();\n\n      transfer_operation top;\n      top.from = nathan_acct.id;\n      top.to = charlie_acc.id;\n      top.amount = asset(5000);\n\n      con.wallet_api_ptr->add_operation_to_builder_transaction(tx_handle, top);\n      con.wallet_api_ptr->set_fees_on_builder_transaction(tx_handle, GRAPHENE_SYMBOL);\n\n      // Sign the transaction with the implied nathan's key and the explicitly yet unnecessary Bob's key\n      auto signed_trx = con.wallet_api_ptr->sign_builder_transaction2(tx_handle, {bob_bki.pub_key}, false);\n\n      // Check for two signatures on the transaction\n      BOOST_CHECK_EQUAL(signed_trx.signatures.size(), 2);\n      flat_set<public_key_type> signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);\n\n      // Check that the signed transaction contains both Nathan's required signature and Bob's unnecessary signature\n      BOOST_CHECK_EQUAL(nathan_acct.active.get_keys().size(), 1);\n      flat_set<public_key_type> expected_signers = {bob_bki.pub_key, nathan_acct.active.get_keys().front()};\n      flat_set<public_key_type> actual_signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);\n      BOOST_CHECK(signers == expected_signers);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n\nBOOST_FIXTURE_TEST_CASE( cli_get_available_transaction_signers, cli_fixture )\n{\n   try\n   {\n      INVOKE(upgrade_nathan_account);\n\n      // register account\n      const auto test_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n         \"test\", test_bki.pub_key, test_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      const auto &test_acc = con.wallet_api_ptr->get_account(\"test\");\n\n      // create and sign transaction\n      signed_transaction trx;\n      trx.operations = {transfer_operation()};\n\n      // sign with test key\n      const auto test_privkey = wif_to_key( test_bki.wif_priv_key );\n      BOOST_REQUIRE( test_privkey );\n      trx.sign( *test_privkey, con.wallet_data.chain_id );\n\n      // sign with other keys\n      const auto privkey_1 = fc::ecc::private_key::generate();\n      trx.sign( privkey_1, con.wallet_data.chain_id );\n\n      const auto privkey_2 = fc::ecc::private_key::generate();\n      trx.sign( privkey_2, con.wallet_data.chain_id );\n\n      // verify expected result\n      flat_set<public_key_type> expected_signers = {test_bki.pub_key,\n                                                    privkey_1.get_public_key(),\n                                                    privkey_2.get_public_key()};\n\n      auto signers = con.wallet_api_ptr->get_transaction_signers(trx);\n      BOOST_CHECK(signers == expected_signers);\n\n      // blockchain has no references to unknown accounts (privkey_1, privkey_2)\n      // only test account available\n      vector<flat_set<account_id_type> > expected_key_refs;\n      expected_key_refs.push_back(flat_set<account_id_type>());\n      expected_key_refs.push_back(flat_set<account_id_type>());\n      expected_key_refs.push_back({test_acc.get_id()});\n\n      auto key_refs = con.wallet_api_ptr->get_key_references({expected_signers.begin(), expected_signers.end()});\n      std::sort(key_refs.begin(), key_refs.end());\n\n      BOOST_CHECK(key_refs == expected_key_refs);\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( cli_cant_get_signers_from_modified_transaction, cli_fixture )\n{\n   try\n   {\n      INVOKE(upgrade_nathan_account);\n\n      // register account\n      const auto test_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n         \"test\", test_bki.pub_key, test_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n\n      // create and sign transaction\n      signed_transaction trx;\n      trx.operations = {transfer_operation()};\n\n      // sign with test key\n      const auto test_privkey = wif_to_key( test_bki.wif_priv_key );\n      BOOST_REQUIRE( test_privkey );\n      trx.sign( *test_privkey, con.wallet_data.chain_id );\n\n      // modify transaction (MITM-attack)\n      trx.operations.clear();\n\n      // verify if transaction has no valid signature of test account\n      flat_set<public_key_type> expected_signers_of_valid_transaction = {test_bki.pub_key};\n      auto signers = con.wallet_api_ptr->get_transaction_signers(trx);\n      BOOST_CHECK(signers != expected_signers_of_valid_transaction);\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n///////////////////\n// Start a server and connect using the same calls as the CLI\n// Set a voting proxy and be assured that it sticks\n///////////////////\nBOOST_FIXTURE_TEST_CASE( cli_set_voting_proxy, cli_fixture )\n{\n   try {\n      INVOKE(create_new_account);\n\n      // grab account for comparison\n      account_object prior_voting_account = con.wallet_api_ptr->get_account(\"jmjatlanta\");\n      // set the voting proxy to nathan\n      BOOST_TEST_MESSAGE(\"About to set voting proxy.\");\n      signed_transaction voting_tx = con.wallet_api_ptr->set_voting_proxy(\"jmjatlanta\", \"nathan\", true);\n      account_object after_voting_account = con.wallet_api_ptr->get_account(\"jmjatlanta\");\n      // see if it changed\n      BOOST_CHECK(prior_voting_account.options.voting_account != after_voting_account.options.voting_account);\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n///////////////////\n// Test blind transactions and mantissa length of range proofs.\n///////////////////\nBOOST_FIXTURE_TEST_CASE( cli_confidential_tx_test, cli_fixture )\n{\n   using namespace graphene::wallet;\n   try {\n      // we need to increase the default max transaction size to run this test.\n      this->app1->chain_database()->modify(\n         this->app1->chain_database()->get_global_properties(),\n         []( global_property_object& p) {\n            p.parameters.maximum_transaction_size = 8192;\n      });\n      std::vector<signed_transaction> import_txs;\n\n      BOOST_TEST_MESSAGE(\"Importing nathan's balance\");\n      import_txs = con.wallet_api_ptr->import_balance(\"nathan\", nathan_keys, true);\n\n      unsigned int head_block = 0;\n      auto & W = *con.wallet_api_ptr; // Wallet alias\n\n      auto formatters = con.wallet_api_ptr->get_result_formatters();\n\n      BOOST_TEST_MESSAGE(\"Creating blind accounts\");\n      graphene::wallet::brain_key_info bki_nathan = W.suggest_brain_key();\n      graphene::wallet::brain_key_info bki_alice = W.suggest_brain_key();\n      graphene::wallet::brain_key_info bki_bob = W.suggest_brain_key();\n      W.create_blind_account(\"nathan\", bki_nathan.brain_priv_key);\n      W.create_blind_account(\"alice\", bki_alice.brain_priv_key);\n      W.create_blind_account(\"bob\", bki_bob.brain_priv_key);\n      BOOST_CHECK(W.get_blind_accounts().size() == 3);\n\n      // ** Block 1: Import Nathan account:\n      BOOST_TEST_MESSAGE(\"Importing nathan key and balance\");\n      std::vector<std::string> nathan_keys{\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"};\n      W.import_key(\"nathan\", nathan_keys[0]);\n      W.import_balance(\"nathan\", nathan_keys, true);\n      generate_block(app1); head_block++;\n\n      // ** Block 2: Nathan will blind 100M CORE token:\n      BOOST_TEST_MESSAGE(\"Blinding a large balance\");\n      {\n         auto result = W.transfer_to_blind(\"nathan\", GRAPHENE_SYMBOL, {{\"nathan\",\"100000000\"}}, true);\n         // Testing result formatter\n         if( formatters.find(\"transfer_to_blind\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of transfer_to_blind\");\n            string output = formatters[\"transfer_to_blind\"](\n                  fc::variant(result, FC_PACK_MAX_DEPTH), fc::variants());\n            BOOST_CHECK( output.find(\"receipt\") != string::npos );\n         }\n      }\n      BOOST_CHECK( W.get_blind_balances(\"nathan\")[0].amount == 10000000000000 );\n      generate_block(app1); head_block++;\n\n      // ** Block 3: Nathan will send 1M CORE token to alice and 10K CORE token to bob. We\n      // then confirm that balances are received, and then analyze the range\n      // prooofs to make sure the mantissa length does not reveal approximate\n      // balance (issue #480).\n      std::map<std::string, share_type> to_list = {{\"alice\",100000000000LL},\n                                                   {\"bob\",    1000000000LL}};\n      vector<blind_confirmation> bconfs;\n      auto core_asset = W.get_asset(\"1.3.0\");\n      BOOST_TEST_MESSAGE(\"Sending blind transactions to alice and bob\");\n      for (auto to : to_list) {\n         string amount = core_asset.amount_to_string(to.second);\n         bconfs.push_back(W.blind_transfer(\"nathan\",to.first,amount,core_asset.symbol,true));\n         BOOST_CHECK( W.get_blind_balances(to.first)[0].amount == to.second );\n      }\n      BOOST_TEST_MESSAGE(\"Inspecting range proof mantissa lengths\");\n      vector<int> rp_mantissabits;\n      for (auto conf : bconfs) {\n         for (auto out : conf.trx.operations[0].get<blind_transfer_operation>().outputs) {\n            rp_mantissabits.push_back(1+out.range_proof[1]); // 2nd byte encodes mantissa length\n         }\n      }\n      // We are checking the mantissa length of the range proofs for several Pedersen\n      // commitments of varying magnitude.  We don't want the mantissa lengths to give\n      // away magnitude.  Deprecated wallet behavior was to use \"just enough\" mantissa\n      // bits to prove range, but this gives away value to within a factor of two. As a\n      // naive test, we assume that if all mantissa lengths are equal, then they are not\n      // revealing magnitude.  However, future more-sophisticated wallet behavior\n      // *might* randomize mantissa length to achieve some space savings in the range\n      // proof.  The following test will fail in that case and a more sophisticated test\n      // will be needed.\n      auto adjacent_unequal = std::adjacent_find(rp_mantissabits.begin(),\n                                                 rp_mantissabits.end(),         // find unequal adjacent values\n                                                 std::not_equal_to<int>());\n      BOOST_CHECK(adjacent_unequal == rp_mantissabits.end());\n      generate_block(app1); head_block++;\n\n      // ** Check head block:\n      BOOST_TEST_MESSAGE(\"Check that all expected blocks have processed\");\n      dynamic_global_property_object dgp = W.get_dynamic_global_properties();\n      BOOST_CHECK(dgp.head_block_number == head_block);\n\n      // Receive blind transfer\n      {\n         auto result = W.receive_blind_transfer(bconfs[1].outputs[1].confirmation_receipt, \"\", \"bob_receive\");\n         BOOST_CHECK_EQUAL( result.amount.amount.value, 1000000000 );\n         // Testing result formatter\n         if( formatters.find(\"receive_blind_transfer\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of receive_blind_transfer\");\n            string output = formatters[\"receive_blind_transfer\"](\n                  fc::variant(result, FC_PACK_MAX_DEPTH), fc::variants());\n            BOOST_CHECK( output.find(\"bob_receive\") != string::npos );\n         }\n      }\n\n      // Check blind history\n      {\n         auto result = W.blind_history(\"nathan\");\n         BOOST_CHECK_EQUAL( result.size(), 5u ); // 1 transfer_to_blind + 2 outputs * 2 blind_transfers\n         // Testing result formatter\n         if( formatters.find(\"blind_history\") != formatters.end() )\n         {\n            BOOST_TEST_MESSAGE(\"Testing formatter of blind_history\");\n            string output = formatters[\"blind_history\"](\n                  fc::variant(result, FC_PACK_MAX_DEPTH), fc::variants());\n            BOOST_CHECK( output.find(\"WHEN\") != string::npos );\n            BOOST_TEST_MESSAGE( output );\n         }\n      }\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/******\n * Check account history pagination (see bitshares-core/issue/1176)\n */\nBOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture )\n{\n   try\n   {\n      INVOKE(create_new_account);\n\n      // attempt to give jmjatlanta some bitshares\n      BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to jmjatlanta\");\n      for(int i = 1; i <= 199; i++)\n      {\n         signed_transaction transfer_tx = con.wallet_api_ptr->transfer(\"nathan\", \"jmjatlanta\", std::to_string(i),\n                                                \"1.3.0\", \"Here are some CORE token for your new account\", true);\n      }\n\n      BOOST_CHECK(generate_block(app1));\n\n      // now get account history and make sure everything is there (and no duplicates)\n      std::vector<graphene::wallet::operation_detail> history = con.wallet_api_ptr->get_account_history(\"jmjatlanta\", 300);\n      BOOST_CHECK_EQUAL(201u, history.size() );\n\n      std::set<object_id_type> operation_ids;\n\n      for(auto& op : history)\n      {\n         if( operation_ids.find(op.op.id) != operation_ids.end() )\n         {\n            BOOST_FAIL(\"Duplicate found\");\n         }\n         operation_ids.insert(op.op.id);\n      }\n\n      // Testing result formatter\n      auto formatters = con.wallet_api_ptr->get_result_formatters();\n      if( formatters.find(\"get_account_history\") != formatters.end() )\n      {\n         BOOST_TEST_MESSAGE(\"Testing formatter of get_account_history\");\n         string output = formatters[\"get_account_history\"](\n               fc::variant(history, FC_PACK_MAX_DEPTH), fc::variants());\n         BOOST_CHECK( output.find(\"Here are some\") != string::npos );\n      }\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n\n///////////////////////\n// Create a multi-sig account and verify that only when all signatures are\n// signed, the transaction could be broadcast\n///////////////////////\nBOOST_AUTO_TEST_CASE( cli_multisig_transaction )\n{\n   using namespace graphene::chain;\n   using namespace graphene::app;\n   std::shared_ptr<graphene::app::application> app1;\n   try {\n      fc::temp_directory app_dir( graphene::utilities::temp_directory_path() );\n\n      int server_port_number = 0;\n      app1 = start_application(app_dir, server_port_number);\n\n      // connect to the server\n      client_connection con(app1, app_dir, server_port_number);\n\n      BOOST_TEST_MESSAGE(\"Setting wallet password\");\n      con.wallet_api_ptr->set_password(\"supersecret\");\n      con.wallet_api_ptr->unlock(\"supersecret\");\n\n      // import Nathan account\n      BOOST_TEST_MESSAGE(\"Importing nathan key\");\n      std::vector<std::string> nathan_keys{\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"};\n      BOOST_CHECK_EQUAL(nathan_keys[0], \"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\");\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"nathan\", nathan_keys[0]));\n\n      BOOST_TEST_MESSAGE(\"Importing nathan's balance\");\n      std::vector<signed_transaction> import_txs = con.wallet_api_ptr->import_balance(\"nathan\", nathan_keys, true);\n      account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // upgrade nathan\n      BOOST_TEST_MESSAGE(\"Upgrading Nathan to LTM\");\n      signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account(\"nathan\", true);\n      account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // verify that the upgrade was successful\n      BOOST_CHECK_PREDICATE( std::not_equal_to<uint32_t>(), (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch())(nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) );\n      BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member());\n\n      // create a new multisig account\n      graphene::wallet::brain_key_info bki1 = con.wallet_api_ptr->suggest_brain_key();\n      graphene::wallet::brain_key_info bki2 = con.wallet_api_ptr->suggest_brain_key();\n      graphene::wallet::brain_key_info bki3 = con.wallet_api_ptr->suggest_brain_key();\n      graphene::wallet::brain_key_info bki4 = con.wallet_api_ptr->suggest_brain_key();\n      BOOST_CHECK(!bki1.brain_priv_key.empty());\n      BOOST_CHECK(!bki2.brain_priv_key.empty());\n      BOOST_CHECK(!bki3.brain_priv_key.empty());\n      BOOST_CHECK(!bki4.brain_priv_key.empty());\n\n      signed_transaction create_multisig_acct_tx;\n      account_create_operation account_create_op;\n\n      account_create_op.referrer = nathan_acct_after_upgrade.id;\n      account_create_op.referrer_percent = nathan_acct_after_upgrade.referrer_rewards_percentage;\n      account_create_op.registrar = nathan_acct_after_upgrade.id;\n      account_create_op.name = \"cifer.test\";\n      account_create_op.owner = authority(1, bki1.pub_key, 1);\n      account_create_op.active = authority(2, bki2.pub_key, 1, bki3.pub_key, 1);\n      account_create_op.options.memo_key = bki4.pub_key;\n      account_create_op.fee = asset(1000000);  // should be enough for creating account\n\n      create_multisig_acct_tx.operations.push_back(account_create_op);\n      con.wallet_api_ptr->sign_transaction(create_multisig_acct_tx, true);\n\n      // attempt to give cifer.test some bitshares\n      BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to cifer.test\");\n      signed_transaction transfer_tx1 = con.wallet_api_ptr->transfer(\"nathan\", \"cifer.test\", \"10000\", \"1.3.0\", \"Here are some BTS for your new account\", true);\n\n      // transfer bts from cifer.test to nathan\n      BOOST_TEST_MESSAGE(\"Transferring bitshares from cifer.test to nathan\");\n      auto dyn_props = app1->chain_database()->get_dynamic_global_properties();\n      account_object cifer_test = con.wallet_api_ptr->get_account(\"cifer.test\");\n\n      // construct a transfer transaction\n      signed_transaction transfer_tx2;\n      transfer_operation xfer_op;\n      xfer_op.from = cifer_test.id;\n      xfer_op.to = nathan_acct_after_upgrade.id;\n      xfer_op.amount = asset(100000000);\n      xfer_op.fee = asset(3000000);  // should be enough for transfer\n      transfer_tx2.operations.push_back(xfer_op);\n\n      // case1: sign a transaction without TaPoS and expiration fields\n      // expect: return a transaction with TaPoS and expiration filled\n      transfer_tx2 =\n         con.wallet_api_ptr->add_transaction_signature( transfer_tx2, false );\n      BOOST_CHECK( ( transfer_tx2.ref_block_num != 0 &&\n                     transfer_tx2.ref_block_prefix != 0 ) ||\n                   ( transfer_tx2.expiration != fc::time_point_sec() ) );\n\n      // case2: broadcast without signature\n      // expect: exception with missing active authority\n      BOOST_CHECK_THROW(con.wallet_api_ptr->broadcast_transaction(transfer_tx2), fc::exception);\n\n      // case3:\n      // import one of the private keys for this new account in the wallet file,\n      // sign and broadcast with partial signatures\n      //\n      // expect: exception with missing active authority\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"cifer.test\", bki2.wif_priv_key));\n      BOOST_CHECK_THROW(con.wallet_api_ptr->add_transaction_signature(transfer_tx2, true), fc::exception);\n\n      // case4: sign again as signature exists\n      // expect: num of signatures not increase\n      transfer_tx2 = con.wallet_api_ptr->add_transaction_signature(transfer_tx2, false);\n      BOOST_CHECK_EQUAL(transfer_tx2.signatures.size(), 1);\n\n      // case5:\n      // import another private key, sign and broadcast without full signatures\n      //\n      // expect: transaction broadcast successfully\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"cifer.test\", bki3.wif_priv_key));\n      con.wallet_api_ptr->add_transaction_signature(transfer_tx2, true);\n      auto balances = con.wallet_api_ptr->list_account_balances( \"cifer.test\" );\n      for (auto b : balances) {\n         if (b.asset_id == asset_id_type()) {\n            BOOST_CHECK(b == asset(900000000 - 3000000));\n         }\n      }\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\ngraphene::wallet::plain_keys decrypt_keys( const std::string& password, const vector<char>& cipher_keys )\n{\n   auto pw = fc::sha512::hash( password.c_str(), password.size() );\n   vector<char> decrypted = fc::aes_decrypt( pw, cipher_keys );\n   return fc::raw::unpack<graphene::wallet::plain_keys>( decrypted );\n}\n\nBOOST_AUTO_TEST_CASE( saving_keys_wallet_test ) {\n   cli_fixture cli;\n\n   cli.con.wallet_api_ptr->import_balance( \"nathan\", cli.nathan_keys, true );\n   cli.con.wallet_api_ptr->upgrade_account( \"nathan\", true );\n   std::string brain_key( \"FICTIVE WEARY MINIBUS LENS HAWKIE MAIDISH MINTY GLYPH GYTE KNOT COCKSHY LENTIGO PROPS BIFORM KHUTBAH BRAZIL\" );\n   cli.con.wallet_api_ptr->create_account_with_brain_key( brain_key, \"account1\", \"nathan\", \"nathan\", true );\n\n   BOOST_CHECK_NO_THROW( cli.con.wallet_api_ptr->transfer( \"nathan\", \"account1\", \"9000\", \"1.3.0\", \"\", true ) );\n\n   std::string path( cli.app_dir.path().generic_string() + \"/wallet.json\" );\n   graphene::wallet::wallet_data wallet = fc::json::from_file( path ).as<graphene::wallet::wallet_data>( 2 * GRAPHENE_MAX_NESTED_OBJECTS );\n   BOOST_CHECK( wallet.extra_keys.size() == 1 ); // nathan\n   BOOST_CHECK( wallet.pending_account_registrations.size() == 1 ); // account1\n   BOOST_CHECK( wallet.pending_account_registrations[\"account1\"].size() == 2 ); // account1 active key + account1 memo key\n\n   graphene::wallet::plain_keys pk = decrypt_keys( \"supersecret\", wallet.cipher_keys );\n   BOOST_CHECK( pk.keys.size() == 1 ); // nathan key\n\n   BOOST_CHECK( generate_block( cli.app1 ) );\n   // Intentional delay\n   fc::usleep( fc::seconds(1) );\n\n   wallet = fc::json::from_file( path ).as<graphene::wallet::wallet_data>( 2 * GRAPHENE_MAX_NESTED_OBJECTS );\n   BOOST_CHECK( wallet.extra_keys.size() == 2 ); // nathan + account1\n   BOOST_CHECK( wallet.pending_account_registrations.empty() );\n   BOOST_CHECK_NO_THROW( cli.con.wallet_api_ptr->transfer( \"account1\", \"nathan\", \"1000\", \"1.3.0\", \"\", true ) );\n\n   pk = decrypt_keys( \"supersecret\", wallet.cipher_keys );\n   BOOST_CHECK( pk.keys.size() == 3 ); // nathan key + account1 active key + account1 memo key\n}\n\n\n///////////////////////\n// Start a server and connect using the same calls as the CLI\n// Create an HTLC\n///////////////////////\nBOOST_AUTO_TEST_CASE( cli_create_htlc )\n{\n   using namespace graphene::chain;\n   using namespace graphene::app;\n   std::shared_ptr<graphene::app::application> app1;\n   try {\n      fc::temp_directory app_dir( graphene::utilities::temp_directory_path() );\n\n      int server_port_number = 0;\n      app1 = start_application(app_dir, server_port_number);\n      // set committee parameters\n      app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p) {\n         graphene::chain::htlc_options params;\n         params.max_preimage_size = 1024;\n         params.max_timeout_secs = 60 * 60 * 24 * 28;\n         p.parameters.extensions.value.updatable_htlc_options = params;\n      });\n\n      // connect to the server\n      client_connection con(app1, app_dir, server_port_number);\n\n      BOOST_TEST_MESSAGE(\"Setting wallet password\");\n      con.wallet_api_ptr->set_password(\"supersecret\");\n      con.wallet_api_ptr->unlock(\"supersecret\");\n\n      // import Nathan account\n      BOOST_TEST_MESSAGE(\"Importing nathan key\");\n      std::vector<std::string> nathan_keys{\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"};\n      BOOST_CHECK_EQUAL(nathan_keys[0], \"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\");\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"nathan\", nathan_keys[0]));\n\n      BOOST_TEST_MESSAGE(\"Importing nathan's balance\");\n      std::vector<signed_transaction> import_txs = con.wallet_api_ptr->import_balance(\"nathan\", nathan_keys, true);\n      account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // upgrade nathan\n      BOOST_TEST_MESSAGE(\"Upgrading Nathan to LTM\");\n      signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account(\"nathan\", true);\n      account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // verify that the upgrade was successful\n      BOOST_CHECK_PREDICATE( std::not_equal_to<uint32_t>(), (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch())\n            (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) );\n      BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member());\n\n      // Create new asset called BOBCOIN\n      try\n      {\n         graphene::chain::asset_options asset_ops;\n         asset_ops.max_supply = 1000000;\n         asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1)));\n         fc::optional<graphene::chain::bitasset_options> bit_opts;\n         con.wallet_api_ptr->create_asset(\"nathan\", \"BOBCOIN\", 5, asset_ops, bit_opts, true);\n      }\n      catch(exception& e)\n      {\n         BOOST_FAIL(e.what());\n      }\n      catch(...)\n      {\n         BOOST_FAIL(\"Unknown exception creating BOBCOIN\");\n      }\n\n      // create a new account for Alice\n      {\n         graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key();\n         BOOST_CHECK(!bki.brain_priv_key.empty());\n         signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key,\n               \"alice\", \"nathan\", \"nathan\", true);\n         con.wallet_api_ptr->save_wallet_file(con.wallet_filename);\n         // attempt to give alice some bitshares\n         BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to alice\");\n         signed_transaction transfer_tx = con.wallet_api_ptr->transfer(\"nathan\", \"alice\", \"10000\", \"1.3.0\",\n               \"Here are some CORE token for your new account\", true);\n      }\n\n      // create a new account for Bob\n      {\n         graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key();\n         BOOST_CHECK(!bki.brain_priv_key.empty());\n         signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key,\n               \"bob\", \"nathan\", \"nathan\", true);\n         // this should cause resync which will import the keys of alice and bob\n         generate_block(app1);\n         // attempt to give bob some bitshares\n         BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to Bob\");\n         signed_transaction transfer_tx = con.wallet_api_ptr->transfer(\"nathan\", \"bob\", \"10000\", \"1.3.0\",\n               \"Here are some CORE token for your new account\", true);\n         con.wallet_api_ptr->issue_asset(\"bob\", \"5\", \"BOBCOIN\", \"Here are your BOBCOINs\", true);\n      }\n\n      BOOST_TEST_MESSAGE(\"Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC\");\n      // create an HTLC\n      std::string preimage_string = \"My Secret\";\n      fc::sha256 preimage_md = fc::sha256::hash(preimage_string);\n      std::stringstream ss;\n      for(size_t i = 0; i < preimage_md.data_size(); i++)\n      {\n         char d = preimage_md.data()[i];\n         unsigned char uc = static_cast<unsigned char>(d);\n         ss << std::setfill('0') << std::setw(2) << std::hex << (int)uc;\n      }\n      std::string hash_str = ss.str();\n      BOOST_TEST_MESSAGE(\"Secret is \" + preimage_string + \" and hash is \" + hash_str);\n      uint32_t timelock = fc::days(1).to_seconds();\n      graphene::chain::signed_transaction result_tx\n            = con.wallet_api_ptr->htlc_create(\"alice\", \"bob\",\n            \"3\", \"1.3.0\", \"SHA256\", hash_str, preimage_string.size(), timelock, \"\", true);\n\n      // normally, a wallet would watch block production, and find the transaction. Here, we can cheat:\n      std::string alice_htlc_id_as_string;\n      htlc_id_type alice_htlc_id;\n      {\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         graphene::chain::signed_block result_block;\n         BOOST_CHECK(generate_block(app1, result_block));\n\n         // get the ID:\n         auto tmp_hist = con.wallet_api_ptr->get_account_history(\"alice\", 1);\n         htlc_id_type htlc_id { tmp_hist[0].op.result.get<object_id_type>() };\n         alice_htlc_id = htlc_id;\n         alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id;\n         BOOST_TEST_MESSAGE(\"Alice shares the HTLC ID with Bob. The HTLC ID is: \" + alice_htlc_id_as_string);\n      }\n\n      // Bob can now look over Alice's HTLC, to see if it is what was agreed to.\n      BOOST_TEST_MESSAGE(\"Bob retrieves the HTLC Object by ID to examine it.\");\n      auto alice_htlc = con.wallet_api_ptr->get_htlc(alice_htlc_id);\n      BOOST_TEST_MESSAGE(\"The HTLC Object is: \" + fc::json::to_pretty_string(alice_htlc));\n\n      // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC\n      con.wallet_api_ptr->htlc_create(\"bob\", \"alice\",\n            \"3\", \"BOBCOIN\", \"SHA256\", hash_str, preimage_string.size(), timelock, \"\", true);\n\n      // normally, a wallet would watch block production, and find the transaction. Here, we can cheat:\n      std::string bob_htlc_id_as_string;\n      htlc_id_type bob_htlc_id;\n      {\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         graphene::chain::signed_block result_block;\n         BOOST_CHECK(generate_block(app1, result_block));\n\n         // get the ID:\n         auto tmp_hist = con.wallet_api_ptr->get_account_history(\"bob\", 1);\n         htlc_id_type htlc_id { tmp_hist[0].op.result.get<object_id_type>() };\n         bob_htlc_id = htlc_id;\n         bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id;\n         BOOST_TEST_MESSAGE(\"Bob shares the HTLC ID with Alice. The HTLC ID is: \" + bob_htlc_id_as_string);\n      }\n\n      // Alice can now look over Bob's HTLC, to see if it is what was agreed to:\n      BOOST_TEST_MESSAGE(\"Alice retrieves the HTLC Object by ID to examine it.\");\n      auto bob_htlc = con.wallet_api_ptr->get_htlc(bob_htlc_id);\n      BOOST_TEST_MESSAGE(\"The HTLC Object is: \" + fc::json::to_pretty_string(bob_htlc));\n\n      // Alice likes what she sees, so uses her preimage to get her BOBCOIN\n      {\n         BOOST_TEST_MESSAGE(\"Alice uses her preimage to retrieve the BOBCOIN\");\n         std::string secret = \"My Secret\";\n         con.wallet_api_ptr->htlc_redeem(bob_htlc_id, \"alice\", secret, true);\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         BOOST_CHECK(generate_block(app1));\n      }\n\n      // TODO: Bob can look at Alice's history to see her preimage\n      // Bob can use the preimage to retrieve his BTS\n      {\n         BOOST_TEST_MESSAGE(\"Bob uses Alice's preimage to retrieve the BOBCOIN\");\n         std::string secret = \"My Secret\";\n         con.wallet_api_ptr->htlc_redeem(alice_htlc_id, \"bob\", secret, true);\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         BOOST_CHECK(generate_block(app1));\n      }\n\n      // test operation_printer\n      auto hist = con.wallet_api_ptr->get_account_history(\"alice\", 10);\n      for(size_t i = 0; i < hist.size(); ++i)\n      {\n         auto obj = hist[i];\n         std::stringstream ss;\n         ss << \"Description: \" << obj.description << \"\\n\";\n         auto str = ss.str();\n         BOOST_TEST_MESSAGE( str );\n         if (i == 3 || i == 4)\n         {\n            BOOST_CHECK( str.find(\"SHA256 8a45f62f47\") != std::string::npos );\n         }\n      }\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nstatic string encapsulate( const graphene::wallet::signed_message& msg )\n{\n   fc::stringstream encapsulated;\n   encapsulated << \"-----BEGIN BITSHARES SIGNED MESSAGE-----\\n\"\n                << msg.message << '\\n'\n                << \"-----BEGIN META-----\\n\"\n                << \"account=\" << msg.meta.account << '\\n'\n                << \"memokey=\" << std::string( msg.meta.memo_key ) << '\\n'\n                << \"block=\" << msg.meta.block << '\\n'\n                << \"timestamp=\" << msg.meta.time << '\\n'\n                << \"-----BEGIN SIGNATURE-----\\n\"\n                << fc::to_hex( (const char*)msg.signature->data(), msg.signature->size() ) << '\\n'\n                << \"-----END BITSHARES SIGNED MESSAGE-----\";\n   return encapsulated.str();\n}\n\n/******\n * Check signing/verifying a message with a memo key\n */\nBOOST_FIXTURE_TEST_CASE( cli_sign_message, cli_fixture )\n{ try {\n   const auto nathan_priv = *wif_to_key( nathan_keys[0] );\n   const public_key_type nathan_pub( nathan_priv.get_public_key() );\n\n   // account does not exist\n   BOOST_REQUIRE_THROW( con.wallet_api_ptr->sign_message( \"dan\", \"123\" ), fc::assert_exception );\n\n   // success\n   graphene::wallet::signed_message msg = con.wallet_api_ptr->sign_message( \"nathan\", \"123\" );\n   BOOST_CHECK_EQUAL( \"123\", msg.message );\n   BOOST_CHECK_EQUAL( \"nathan\", msg.meta.account );\n   BOOST_CHECK_EQUAL( std::string( nathan_pub ), std::string( msg.meta.memo_key ) );\n   BOOST_CHECK( msg.signature.valid() );\n\n   // change message, verify failure\n   msg.message = \"124\";\n   BOOST_CHECK( !con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block, msg.meta.time,\n                                                     *msg.signature ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_signed_message( msg ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n   msg.message = \"123\";\n\n   // change account, verify failure\n   // nonexistent account:\n   msg.meta.account = \"dan\";\n   BOOST_REQUIRE_THROW( con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block,\n                                                            msg.meta.time, *msg.signature ), fc::assert_exception );\n   BOOST_REQUIRE_THROW( con.wallet_api_ptr->verify_signed_message( msg ), fc::assert_exception );\n   BOOST_REQUIRE_THROW( con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ), fc::assert_exception);\n   // existing, but wrong account:\n   msg.meta.account = \"committee-account\";\n   BOOST_CHECK( !con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block,\n                                                     msg.meta.time, *msg.signature ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_signed_message( msg ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n   msg.meta.account = \"nathan\";\n\n   // change key, verify failure\n   ++msg.meta.memo_key.key_data.data()[1];\n   //BOOST_CHECK( !con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block, msg.meta.time,\n   //                                                  *msg.signature ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_signed_message( msg ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n   --msg.meta.memo_key.key_data.data()[1];\n\n   // change block, verify failure\n   ++msg.meta.block;\n   BOOST_CHECK( !con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block, msg.meta.time,\n                                                     *msg.signature ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_signed_message( msg ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n   --msg.meta.block;\n\n   // change time, verify failure\n   ++msg.meta.time[0];\n   BOOST_CHECK( !con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block, msg.meta.time,\n                                                     *msg.signature ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_signed_message( msg ) );\n   BOOST_CHECK( !con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n   --msg.meta.time[0];\n\n   // change signature, verify failure\n   ++msg.signature->data()[1];\n   try {\n      BOOST_CHECK( !con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block, msg.meta.time,\n                                                        *msg.signature ) );\n   } catch( const fc::assert_exception& ) {} // failure to reconstruct key from signature is ok as well\n   try {\n      BOOST_CHECK( !con.wallet_api_ptr->verify_signed_message( msg ) );\n   } catch( const fc::assert_exception& ) {} // failure to reconstruct key from signature is ok as well\n   try {\n      BOOST_CHECK( !con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n   } catch( const fc::assert_exception& ) {} // failure to reconstruct key from signature is ok as well\n   --msg.signature->data()[1];\n\n   // verify success\n   BOOST_CHECK( con.wallet_api_ptr->verify_message( msg.message, msg.meta.account, msg.meta.block, msg.meta.time,\n                                                    *msg.signature ) );\n   BOOST_CHECK( con.wallet_api_ptr->verify_signed_message( msg ) );\n   BOOST_CHECK( con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) );\n\n} FC_LOG_AND_RETHROW() }\n\n///////////////////\n// Test the general storage by custom operations plugin\n///////////////////\nBOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture )\n{\n   try {\n      // create the taker account\n      INVOKE(create_new_account);\n\n      auto db = app1->chain_database();\n\n      BOOST_TEST_MESSAGE(\"Storing in a map.\");\n\n      flat_map<string, optional<string>> pairs;\n      pairs[\"key1\"] = fc::json::to_string(\"value1\");\n      pairs[\"key2\"] = fc::json::to_string(\"value2\");\n\n      con.wallet_api_ptr->account_store_map(\"nathan\", \"any\", false, pairs, true);\n\n      BOOST_TEST_MESSAGE(\"The system is generating a block.\");\n      BOOST_CHECK(generate_block(app1));\n\n      BOOST_TEST_MESSAGE(\"Get current map for nathan.\");\n      auto nathan_map = con.wallet_api_ptr->get_account_storage(\"nathan\", \"any\");\n\n      BOOST_CHECK_EQUAL(nathan_map[0].id.instance(), 0);\n      BOOST_CHECK_EQUAL(nathan_map[0].account.instance.value, 17);\n      BOOST_CHECK_EQUAL(nathan_map[0].catalog, \"any\");\n      BOOST_CHECK_EQUAL(nathan_map[0].key, \"key1\");\n      BOOST_CHECK_EQUAL(nathan_map[0].value->as_string(), \"value1\");\n      BOOST_CHECK_EQUAL(nathan_map[1].id.instance(), 1);\n      BOOST_CHECK_EQUAL(nathan_map[1].account.instance.value, 17);\n      BOOST_CHECK_EQUAL(nathan_map[1].catalog, \"any\");\n      BOOST_CHECK_EQUAL(nathan_map[1].key, \"key2\");\n      BOOST_CHECK_EQUAL(nathan_map[1].value->as_string(), \"value2\");\n\n      BOOST_TEST_MESSAGE(\"Storing in a list.\");\n\n      flat_map<string, optional<string>> favs;\n      favs[\"chocolate\"];\n      favs[\"milk\"];\n      favs[\"banana\"];\n\n      con.wallet_api_ptr->account_store_map(\"nathan\", \"favourites\", false, favs, true);\n\n      BOOST_TEST_MESSAGE(\"The system is generating a block.\");\n      BOOST_CHECK(generate_block(app1));\n\n      BOOST_TEST_MESSAGE(\"Get current list for nathan.\");\n      auto nathan_list = con.wallet_api_ptr->get_account_storage(\"nathan\", \"favourites\");\n\n      BOOST_CHECK_EQUAL(nathan_list[0].id.instance(), 2);\n      BOOST_CHECK_EQUAL(nathan_list[0].account.instance.value, 17);\n      BOOST_CHECK_EQUAL(nathan_list[0].catalog, \"favourites\");\n      BOOST_CHECK_EQUAL(nathan_list[0].key, \"banana\");\n      BOOST_CHECK_EQUAL(nathan_list[1].id.instance(), 3);\n      BOOST_CHECK_EQUAL(nathan_list[1].account.instance.value, 17);\n      BOOST_CHECK_EQUAL(nathan_list[1].catalog, \"favourites\");\n      BOOST_CHECK_EQUAL(nathan_list[1].key, \"chocolate\");\n      BOOST_CHECK_EQUAL(nathan_list[2].id.instance(), 4);\n      BOOST_CHECK_EQUAL(nathan_list[2].account.instance.value, 17);\n      BOOST_CHECK_EQUAL(nathan_list[2].catalog, \"favourites\");\n      BOOST_CHECK_EQUAL(nathan_list[2].key, \"milk\");\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n//////\n// Template copied\n//////\ntemplate<typename Object>\nunsigned_int member_index(string name) {\n   unsigned_int index;\n   fc::typelist::runtime::for_each(typename fc::reflector<Object>::native_members(), [&name, &index](auto t) mutable {\n      if (name == decltype(t)::type::get_name())\n      index = decltype(t)::type::index;\n   });\n   return index;\n}\n\n///////////////////////\n// Wallet RPC\n// Test sign_builder_transaction2 with an account (bob) that has received a custom authorization\n// to transfer funds from another account (alice)\n///////////////////////\nBOOST_FIXTURE_TEST_CASE(cli_use_authorized_transfer, cli_fixture) {\n   try {\n      //////\n      // Initialize the blockchain\n      //////\n      auto db = app1->chain_database();\n\n      account_object nathan_acct = con.wallet_api_ptr->get_account(\"nathan\");\n      INVOKE(upgrade_nathan_account);\n\n      // Register Alice account\n      const auto alice_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"alice\", alice_bki.pub_key, alice_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      const account_object &alice_acc = con.wallet_api_ptr->get_account(\"alice\");\n\n      // Register Bob account\n      const auto bob_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"bob\", bob_bki.pub_key, bob_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      const account_object &bob_acc = con.wallet_api_ptr->get_account(\"bob\");\n\n      // Register Charlie account\n      const graphene::wallet::brain_key_info charlie_bki = con.wallet_api_ptr->suggest_brain_key();\n      con.wallet_api_ptr->register_account(\n              \"charlie\", charlie_bki.pub_key, charlie_bki.pub_key, \"nathan\", \"nathan\", 0, true\n      );\n      const account_object &charlie_acc = con.wallet_api_ptr->get_account(\"charlie\");\n\n      // Fund Alice's account\n      con.wallet_api_ptr->transfer(\"nathan\", \"alice\", \"450000\", \"1.3.0\", \"\", true);\n\n      // Initialize common variables\n      signed_transaction signed_trx;\n\n\n      //////\n      // Initialize Alice's CLI wallet\n      //////\n      client_connection con_alice(app1, app_dir, server_port_number, \"wallet_alice.json\");\n      con_alice.wallet_api_ptr->set_password(\"supersecret\");\n      con_alice.wallet_api_ptr->unlock(\"supersecret\");\n\n      // Import Alice's key\n      BOOST_CHECK(con_alice.wallet_api_ptr->import_key(\"alice\", alice_bki.wif_priv_key));\n\n\n      //////\n      // Initialize the blockchain for BSIP 40\n      //////\n      generate_blocks(app1, HARDFORK_BSIP_40_TIME);\n      // Set committee parameters\n      app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p) {\n         p.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n      });\n\n\n      //////\n      // Alice authorizes Bob to transfer funds from her account to Charlie's account\n      //////\n      graphene::wallet::transaction_handle_type tx_alice_handle = con_alice.wallet_api_ptr->begin_builder_transaction();\n\n      custom_authority_create_operation caop;\n      caop.account = alice_acc.get_id();\n      caop.auth.add_authority(bob_acc.get_id(), 1);\n      caop.auth.weight_threshold = 1;\n      caop.enabled = true;\n      caop.valid_to = db->head_block_time() + 1000;\n      caop.operation_type = operation::tag<transfer_operation>::value;\n\n      // Restriction should have \"to\" equal Charlie\n      vector<restriction> restrictions;\n      auto to_index = member_index<transfer_operation>(\"to\");\n      restrictions.emplace_back(to_index, restriction::func_eq, charlie_acc.get_id());\n\n      con_alice.wallet_api_ptr->add_operation_to_builder_transaction(tx_alice_handle, caop);\n      asset ca_fee = con_alice.wallet_api_ptr->set_fees_on_builder_transaction(tx_alice_handle, GRAPHENE_SYMBOL);\n\n      // Sign the transaction with the inferred Alice key\n      signed_trx = con_alice.wallet_api_ptr->sign_builder_transaction2(tx_alice_handle, {}, true);\n\n      // Check for one signatures on the transaction\n      BOOST_CHECK_EQUAL(signed_trx.signatures.size(), 1);\n\n      // Check that the signed transaction contains Alice's signature\n      flat_set<public_key_type> expected_signers = {alice_bki.pub_key};\n      flat_set<public_key_type> actual_signers = con_alice.wallet_api_ptr->get_transaction_signers(signed_trx);\n      BOOST_CHECK(actual_signers == expected_signers);\n\n\n      //////\n      // Initialize Bob's CLI wallet\n      //////\n      client_connection con_bob(app1, app_dir, server_port_number, \"wallet_bob.json\");\n      con_bob.wallet_api_ptr->set_password(\"supersecret\");\n      con_bob.wallet_api_ptr->unlock(\"supersecret\");\n\n      // Import Bob's key\n      BOOST_CHECK(con_bob.wallet_api_ptr->import_key(\"bob\", bob_bki.wif_priv_key));\n\n\n      //////\n      // Bob attempt to transfer funds from Alice to Charlie while using Bob's wallet\n      // This should succeed because Bob is authorized to transfer by Alice\n      //////\n      graphene::wallet::transaction_handle_type tx_bob_handle = con_bob.wallet_api_ptr->begin_builder_transaction();\n\n      const asset transfer_amount = asset(123 * GRAPHENE_BLOCKCHAIN_PRECISION);\n      transfer_operation top;\n      top.from = alice_acc.id;\n      top.to = charlie_acc.id;\n      top.amount = transfer_amount;\n\n      con_bob.wallet_api_ptr->add_operation_to_builder_transaction(tx_bob_handle, top);\n      asset transfer_fee = con_bob.wallet_api_ptr->set_fees_on_builder_transaction(tx_bob_handle, GRAPHENE_SYMBOL);\n\n      // Sign the transaction with the explicit Bob key\n      signed_trx = con_bob.wallet_api_ptr->sign_builder_transaction2(tx_bob_handle, {bob_bki.pub_key}, true);\n\n      // Check for one signatures on the transaction\n      BOOST_CHECK_EQUAL(signed_trx.signatures.size(), 1);\n\n      // Check that the signed transaction contains Bob's signature\n      BOOST_CHECK_EQUAL(nathan_acct.active.get_keys().size(), 1);\n      expected_signers = {bob_bki.pub_key};\n      actual_signers = con_bob.wallet_api_ptr->get_transaction_signers(signed_trx);\n      BOOST_CHECK(actual_signers == expected_signers);\n\n\n      //////\n      // Check account balances\n      //////\n      // Check Charlie's balances\n      vector<asset> charlie_balances = con.wallet_api_ptr->list_account_balances(\"charlie\");\n      BOOST_CHECK_EQUAL(charlie_balances.size(), 1);\n      asset charlie_core_balance = charlie_balances.front();\n      asset expected_charlie_core_balance = transfer_amount;\n      BOOST_CHECK(charlie_core_balance == expected_charlie_core_balance);\n\n      // Check Bob's balances\n      vector<asset> bob_balances = con.wallet_api_ptr->list_account_balances(\"bob\");\n      BOOST_CHECK_EQUAL(bob_balances.size(), 0);\n\n      // Check Alice's balance\n      vector<asset> alice_balances = con.wallet_api_ptr->list_account_balances(\"alice\");\n      BOOST_CHECK_EQUAL(alice_balances.size(), 1);\n      asset alice_core_balance = alice_balances.front();\n      asset expected_alice_balance =  asset(450000 * GRAPHENE_BLOCKCHAIN_PRECISION)\n                                      - expected_charlie_core_balance\n                                      - ca_fee - transfer_fee;\n      BOOST_CHECK(alice_core_balance.asset_id == expected_alice_balance.asset_id);\n      BOOST_CHECK_EQUAL(alice_core_balance.amount.value, expected_alice_balance.amount.value);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( cli_create_htlc_bsip64 )\n{\n   using namespace graphene::chain;\n   using namespace graphene::app;\n   std::shared_ptr<graphene::app::application> app1;\n   try {\n      fc::temp_directory app_dir( graphene::utilities::temp_directory_path() );\n\n      int server_port_number = 0;\n      app1 = start_application(app_dir, server_port_number);\n      // set committee parameters\n      app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p)\n      {\n         graphene::chain::htlc_options params;\n         params.max_preimage_size = 1024;\n         params.max_timeout_secs = 60 * 60 * 24 * 28;\n         p.parameters.extensions.value.updatable_htlc_options = params;\n      });\n\n      // connect to the server\n      client_connection con(app1, app_dir, server_port_number);\n\n      // get past hardforks\n      generate_blocks( app1, HARDFORK_CORE_BSIP64_TIME + 10);\n\n      BOOST_TEST_MESSAGE(\"Setting wallet password\");\n      con.wallet_api_ptr->set_password(\"supersecret\");\n      con.wallet_api_ptr->unlock(\"supersecret\");\n\n      // import Nathan account\n      BOOST_TEST_MESSAGE(\"Importing nathan key\");\n      std::vector<std::string> nathan_keys{\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"};\n      BOOST_CHECK_EQUAL(nathan_keys[0], \"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\");\n      BOOST_CHECK(con.wallet_api_ptr->import_key(\"nathan\", nathan_keys[0]));\n\n      BOOST_TEST_MESSAGE(\"Importing nathan's balance\");\n      std::vector<signed_transaction> import_txs = con.wallet_api_ptr->import_balance(\"nathan\", nathan_keys, true);\n      account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // upgrade nathan\n      BOOST_TEST_MESSAGE(\"Upgrading Nathan to LTM\");\n      signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account(\"nathan\", true);\n      account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account(\"nathan\");\n\n      // verify that the upgrade was successful\n      BOOST_CHECK_PREDICATE( std::not_equal_to<uint32_t>(),\n            (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch())\n            (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) );\n      BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member());\n\n      // Create new asset called BOBCOIN\n      try\n      {\n         graphene::chain::asset_options asset_ops;\n         asset_ops.max_supply = 1000000;\n         asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1)));\n         fc::optional<graphene::chain::bitasset_options> bit_opts;\n         con.wallet_api_ptr->create_asset(\"nathan\", \"BOBCOIN\", 5, asset_ops, bit_opts, true);\n      }\n      catch(exception& e)\n      {\n         BOOST_FAIL(e.what());\n      }\n      catch(...)\n      {\n         BOOST_FAIL(\"Unknown exception creating BOBCOIN\");\n      }\n\n      // create a new account for Alice\n      {\n         graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key();\n         BOOST_CHECK(!bki.brain_priv_key.empty());\n         signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key,\n               \"alice\", \"nathan\", \"nathan\", true);\n         con.wallet_api_ptr->save_wallet_file(con.wallet_filename);\n         // attempt to give alice some bitshares\n         BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to alice\");\n         signed_transaction transfer_tx = con.wallet_api_ptr->transfer(\"nathan\", \"alice\", \"10000\", \"1.3.0\",\n               \"Here are some CORE token for your new account\", true);\n      }\n\n      // create a new account for Bob\n      {\n         graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key();\n         BOOST_CHECK(!bki.brain_priv_key.empty());\n         signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key,\n               \"bob\", \"nathan\", \"nathan\", true);\n         // this should cause resync which will import the keys of alice and bob\n         generate_block(app1);\n         // attempt to give bob some bitshares\n         BOOST_TEST_MESSAGE(\"Transferring bitshares from Nathan to Bob\");\n         signed_transaction transfer_tx = con.wallet_api_ptr->transfer(\"nathan\", \"bob\", \"10000\", \"1.3.0\",\n               \"Here are some CORE token for your new account\", true);\n         con.wallet_api_ptr->issue_asset(\"bob\", \"5\", \"BOBCOIN\", \"Here are your BOBCOINs\", true);\n      }\n\n      BOOST_TEST_MESSAGE(\"Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC\");\n      // create an HTLC\n      std::string preimage_string = \"My Super Long Secret that is larger than 50 charaters. How do I look?\\n\";\n      fc::hash160 preimage_md = fc::hash160::hash(preimage_string);\n      std::stringstream ss;\n      for(size_t i = 0; i < preimage_md.data_size(); i++)\n      {\n         char d = preimage_md.data()[i];\n         unsigned char uc = static_cast<unsigned char>(d);\n         ss << std::setfill('0') << std::setw(2) << std::hex << (int)uc;\n      }\n      std::string hash_str = ss.str();\n      BOOST_TEST_MESSAGE(\"Secret is \" + preimage_string + \" and hash is \" + hash_str);\n      uint32_t timelock = fc::days(1).to_seconds();\n      graphene::chain::signed_transaction result_tx\n            = con.wallet_api_ptr->htlc_create(\"alice\", \"bob\",\n            \"3\", \"1.3.0\", \"HASH160\", hash_str, preimage_string.size(), timelock, \"Alice to Bob\", true);\n\n      // normally, a wallet would watch block production, and find the transaction. Here, we can cheat:\n      std::string alice_htlc_id_as_string;\n      htlc_id_type alice_htlc_id;\n      {\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         graphene::chain::signed_block result_block;\n         BOOST_CHECK(generate_block(app1, result_block));\n\n         // get the ID:\n         auto tmp_hist = con.wallet_api_ptr->get_account_history(\"alice\", 1);\n         htlc_id_type htlc_id { tmp_hist[0].op.result.get<object_id_type>() };\n         alice_htlc_id = htlc_id;\n         alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id;\n         BOOST_TEST_MESSAGE(\"Alice shares the HTLC ID with Bob. The HTLC ID is: \" + alice_htlc_id_as_string);\n      }\n\n      // Bob can now look over Alice's HTLC, to see if it is what was agreed to.\n      BOOST_TEST_MESSAGE(\"Bob retrieves the HTLC Object by ID to examine it.\");\n      auto alice_htlc = con.wallet_api_ptr->get_htlc(alice_htlc_id);\n      BOOST_TEST_MESSAGE(\"The HTLC Object is: \" + fc::json::to_pretty_string(alice_htlc));\n\n      // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC\n      con.wallet_api_ptr->htlc_create(\"bob\", \"alice\",\n            \"3\", \"BOBCOIN\", \"HASH160\", hash_str, preimage_string.size(), fc::hours(12).to_seconds(),\n            \"Bob to Alice\", true);\n\n      // normally, a wallet would watch block production, and find the transaction. Here, we can cheat:\n      std::string bob_htlc_id_as_string;\n      htlc_id_type bob_htlc_id;\n      {\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         graphene::chain::signed_block result_block;\n         BOOST_CHECK(generate_block(app1, result_block));\n\n         // get the ID:\n         auto tmp_hist = con.wallet_api_ptr->get_account_history(\"bob\", 1);\n         htlc_id_type htlc_id { tmp_hist[0].op.result.get<object_id_type>() };\n         bob_htlc_id = htlc_id;\n         bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id;\n         BOOST_TEST_MESSAGE(\"Bob shares the HTLC ID with Alice. The HTLC ID is: \" + bob_htlc_id_as_string);\n      }\n\n      // Alice can now look over Bob's HTLC, to see if it is what was agreed to:\n      BOOST_TEST_MESSAGE(\"Alice retrieves the HTLC Object by ID to examine it.\");\n      auto bob_htlc = con.wallet_api_ptr->get_htlc(bob_htlc_id);\n      BOOST_TEST_MESSAGE(\"The HTLC Object is: \" + fc::json::to_pretty_string(bob_htlc));\n\n      // Alice likes what she sees, so uses her preimage to get her BOBCOIN\n      {\n         BOOST_TEST_MESSAGE(\"Alice uses her preimage to retrieve the BOBCOIN\");\n         con.wallet_api_ptr->htlc_redeem(bob_htlc_id, \"alice\", preimage_string, true);\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         BOOST_CHECK(generate_block(app1));\n      }\n\n      // Bob can look at Alice's history to see her preimage\n      {\n         BOOST_TEST_MESSAGE(\"Bob can look at the history of Alice to see the preimage\");\n         std::vector<graphene::wallet::operation_detail> hist = con.wallet_api_ptr->get_account_history(\"alice\", 1);\n         BOOST_CHECK( hist[0].description.find(\"with preimage \\\"4d792\") != hist[0].description.npos);\n      }\n\n      // Bob can also look at his own history to see Alice's preimage\n      {\n         BOOST_TEST_MESSAGE(\"Bob can look at his own history to see the preimage\");\n         std::vector<graphene::wallet::operation_detail> hist = con.wallet_api_ptr->get_account_history(\"bob\", 1);\n         BOOST_CHECK( hist[0].description.find(\"with preimage \\\"4d792\") != hist[0].description.npos);\n      }\n\n      // Bob can use the preimage to retrieve his BTS\n      {\n         BOOST_TEST_MESSAGE(\"Bob uses Alice's preimage to retrieve the BOBCOIN\");\n         con.wallet_api_ptr->htlc_redeem(alice_htlc_id, \"bob\", preimage_string, true);\n         BOOST_TEST_MESSAGE(\"The system is generating a block\");\n         BOOST_CHECK(generate_block(app1));\n      }\n\n      // test operation_printer\n      auto hist = con.wallet_api_ptr->get_account_history(\"alice\", 10);\n      for(size_t i = 0; i < hist.size(); ++i)\n      {\n         auto obj = hist[i];\n         std::stringstream ss;\n         ss << \"Description: \" << obj.description << \"\\n\";\n         auto str = ss.str();\n         BOOST_TEST_MESSAGE( str );\n         if (i == 3 || i == 4)\n         {\n            BOOST_CHECK( str.find(\"HASH160 620e4d5ba\") != std::string::npos );\n         }\n      }\n      con.wallet_api_ptr->lock();\n      hist = con.wallet_api_ptr->get_account_history(\"alice\", 10);\n      for(size_t i = 0; i < hist.size(); ++i)\n      {\n         auto obj = hist[i];\n         std::stringstream ss;\n         ss << \"Description: \" << obj.description << \"\\n\";\n         auto str = ss.str();\n         BOOST_TEST_MESSAGE( str );\n         if (i == 3 || i == 4)\n         {\n            BOOST_CHECK( str.find(\"HASH160 620e4d5ba\") != std::string::npos );\n         }\n      }\n      con.wallet_api_ptr->unlock(\"supersecret\");\n\n   } catch( fc::exception& e ) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n"
  },
  {
    "path": "tests/common/database_fixture.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <boost/test/unit_test.hpp>\n#include <boost/range/algorithm.hpp>\n\n#include <graphene/account_history/account_history_plugin.hpp>\n#include <graphene/market_history/market_history_plugin.hpp>\n#include <graphene/grouped_orders/grouped_orders_plugin.hpp>\n#include <graphene/elasticsearch/elasticsearch_plugin.hpp>\n#include <graphene/api_helper_indexes/api_helper_indexes.hpp>\n#include <graphene/es_objects/es_objects.hpp>\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n#include <graphene/debug_witness/debug_witness.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/hardfork_visitor.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include <iomanip>\n\n#include \"database_fixture.hpp\"\n#include \"elasticsearch.hpp\"\n\nusing namespace graphene::chain::test;\n\nextern uint32_t    GRAPHENE_TESTING_GENESIS_TIMESTAMP;\nextern std::string GRAPHENE_TESTING_ES_URL;\n\nnamespace graphene { namespace chain {\n\nusing std::cout;\nusing std::cerr;\n\nnamespace buf = boost::unit_test::framework;\n\nvoid clearable_block::clear()\n{\n   _calculated_merkle_root = checksum_type();\n   _signee = fc::ecc::public_key();\n   _block_id = block_id_type();\n}\n\ndatabase_fixture_base::database_fixture_base()\n   : app(), db( *app.chain_database() ),\n     private_key( fc::ecc::private_key::generate() ),\n     init_account_priv_key( fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) ) ),\n     init_account_pub_key( init_account_priv_key.get_public_key() ),\n     current_test_name( buf::current_test_case().p_name.value ),\n     current_suite_name( buf::get<boost::unit_test::test_suite>(buf::current_test_case().p_parent_id).p_name\n                                                                                                     .value )\n{ try {\n   int argc = buf::master_test_suite().argc;\n   char** argv = buf::master_test_suite().argv;\n   for( int i=1; i<argc; i++ )\n   {\n      const std::string arg = argv[i];\n      if( arg == \"--record-assert-trip\" )\n         fc::enable_record_assert_trip = true;\n      if( arg == \"--show-test-names\" )\n         std::cout << \"running test \" << current_test_name << std::endl;\n   }\n} FC_LOG_AND_RETHROW() }\n\ndatabase_fixture_base::~database_fixture_base()\n{\n   // cleanup data in ES\n   if( !es_index_prefix.empty() || !es_obj_index_prefix.empty() )\n   {\n      CURL *curl; // curl handler\n      curl = curl_easy_init();\n      curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);\n\n      graphene::utilities::ES es;\n      es.curl = curl;\n      es.elasticsearch_url = GRAPHENE_TESTING_ES_URL;\n\n      if( !es_index_prefix.empty() )\n      {\n         es.index_prefix = es_index_prefix;\n         // delete all\n         try {\n            graphene::utilities::deleteAll(es);\n         } catch (...) {\n            // nothing to do\n         }\n      }\n\n      if( !es_obj_index_prefix.empty() )\n      {\n         es.index_prefix = es_obj_index_prefix;\n         // delete all\n         try {\n            graphene::utilities::deleteAll(es);\n         } catch (...) {\n            // nothing to do\n         }\n      }\n   }\n\n   try {\n      // If we're unwinding due to an exception, don't do any more checks.\n      // This way, boost test's last checkpoint tells us approximately where the error was.\n      if( !std::uncaught_exception() )\n      {\n         verify_asset_supplies(db);\n         BOOST_CHECK( db.get_node_properties().skip_flags == database::skip_nothing );\n      }\n   } catch (fc::exception& ex) {\n      BOOST_FAIL( std::string(\"fc::exception in ~database_fixture: \") + ex.to_detail_string() );\n   } catch (std::exception& e) {\n      BOOST_FAIL( std::string(\"std::exception in ~database_fixture:\") + e.what() );\n   } catch (...) {\n      BOOST_FAIL( \"Uncaught exception in ~database_fixture\" );\n   }\n\n}\n\nvoid database_fixture_base::init_genesis( database_fixture_base& fixture )\n{\n   fixture.genesis_state.initial_timestamp = fc::time_point_sec(GRAPHENE_TESTING_GENESIS_TIMESTAMP);\n   if( fixture.current_test_name == \"hf_1270_test\" )\n   {\n      fixture.genesis_state.initial_active_witnesses = 20;\n   }\n   else {\n      fixture.genesis_state.initial_active_witnesses = 10;\n      fixture.genesis_state.immutable_parameters.min_committee_member_count = INITIAL_COMMITTEE_MEMBER_COUNT;\n      fixture.genesis_state.immutable_parameters.min_witness_count = INITIAL_WITNESS_COUNT;\n   }\n\n   for( unsigned int i = 0; i < fixture.genesis_state.initial_active_witnesses; ++i )\n   {\n      auto name = \"init\"+fc::to_string(i);\n      fixture.genesis_state.initial_accounts.emplace_back( name, fixture.init_account_pub_key,\n                                                           fixture.init_account_pub_key, true);\n      fixture.genesis_state.initial_committee_candidates.push_back({name});\n      fixture.genesis_state.initial_witness_candidates.push_back({ name, fixture.init_account_pub_key });\n   }\n   fixture.genesis_state.initial_parameters.get_mutable_fees().zero_all_fees();\n\n   genesis_state_type::initial_asset_type init_mpa1;\n   init_mpa1.symbol = \"INITMPA\";\n   init_mpa1.issuer_name = \"committee-account\";\n   init_mpa1.description = \"Initial MPA\";\n   init_mpa1.precision = 4;\n   init_mpa1.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n   init_mpa1.accumulated_fees = 0;\n   init_mpa1.is_bitasset = true;\n   fixture.genesis_state.initial_assets.push_back( init_mpa1 );\n   // TODO add initial UIA's; add initial short positions; test non-zero accumulated_fees\n}\n\nstd::shared_ptr<boost::program_options::variables_map> database_fixture_base::init_options(\n      database_fixture_base& fixture )\n{\n   auto sharable_options = std::make_shared<boost::program_options::variables_map>();\n   auto& options = *sharable_options;\n   fc::set_option( options, \"seed-nodes\", std::string(\"[]\") ); // Do not connect to default seed nodes\n   /**\n    * Test specific settings\n    */\n   if (fixture.current_test_name == \"broadcast_transaction_with_callback_test\")\n      fc::set_option( options, \"enable-p2p-network\", true );\n   else if (fixture.current_test_name == \"broadcast_transaction_disabled_p2p_test\")\n      fc::set_option( options, \"enable-p2p-network\", false );\n   else if( rand() % 100 >= 50 ) // Disable P2P network randomly for test cases\n      fc::set_option( options, \"enable-p2p-network\", false );\n   else\n   {\n      if( rand() % 100 >= 50 ) // this should lead to no change\n      {\n         fc::set_option( options, \"enable-p2p-network\", true );\n      }\n      fc::ip::endpoint ep;\n      ep.set_port( rand() % 20000 + 5000 );\n      idump( (ep)(std::string(ep)) );\n      fc::set_option( options, \"p2p-endpoint\", std::string( ep ) );\n   }\n\n   if (fixture.current_test_name == \"min_blocks_to_keep_test\")\n   {\n      fc::set_option( options, \"partial-operations\", true );\n      fc::set_option( options, \"max-ops-per-account\", (uint64_t)2 );\n      fc::set_option( options, \"min-blocks-to-keep\", (uint32_t)3 );\n      fc::set_option( options, \"max-ops-per-acc-by-min-blocks\", (uint64_t)5 );\n   }\n   if (fixture.current_test_name == \"get_account_history_operations\")\n   {\n      fc::set_option( options, \"max-ops-per-account\", (uint64_t)75 );\n      fc::set_option( options, \"min-blocks-to-keep\", (uint32_t)0 );\n   }\n   if (fixture.current_test_name == \"api_limit_get_account_history_operations\")\n   {\n      fc::set_option( options, \"max-ops-per-account\", (uint64_t)125 );\n      fc::set_option( options, \"min-blocks-to-keep\", (uint32_t)0 );\n      fc::set_option( options, \"api-limit-get-account-history-operations\", (uint32_t)300 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_account_history\")\n   {\n      fc::set_option( options, \"max-ops-per-account\", (uint64_t)125 );\n      fc::set_option( options, \"min-blocks-to-keep\", (uint32_t)0 );\n      fc::set_option( options, \"api-limit-get-account-history\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_grouped_limit_orders\")\n   {\n      fc::set_option( options, \"api-limit-get-grouped-limit-orders\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_relative_account_history\")\n   {\n      fc::set_option( options, \"max-ops-per-account\", (uint64_t)125 );\n      fc::set_option( options, \"min-blocks-to-keep\", (uint32_t)0 );\n      fc::set_option( options, \"api-limit-get-relative-account-history\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_account_history_by_operations\")\n   {\n      fc::set_option( options, \"api-limit-get-account-history-by-operations\", (uint32_t)250 );\n      fc::set_option( options, \"api-limit-get-relative-account-history\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_asset_holders\")\n   {\n      fc::set_option( options, \"api-limit-get-asset-holders\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_key_references\")\n   {\n      fc::set_option( options, \"api-limit-get-key-references\", (uint32_t)200 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_limit_orders\")\n   {\n      fc::set_option( options, \"api-limit-get-limit-orders\", (uint32_t)350 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_limit_orders_by_account\")\n   {\n      fc::set_option( options, \"api-limit-get-limit-orders-by-account\", (uint32_t)150 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_call_orders\")\n   {\n      fc::set_option( options, \"api-limit-get-call-orders\", (uint32_t)350 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_settle_orders\")\n   {\n      fc::set_option( options, \"api-limit-get-settle-orders\", (uint32_t)350 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_order_book\")\n   {\n      fc::set_option( options, \"api-limit-get-order-book\", (uint32_t)80 );\n   }\n   if(fixture.current_test_name ==\"api_limit_lookup_accounts\")\n   {\n      fc::set_option( options, \"api-limit-lookup-accounts\", (uint32_t)200 );\n   }\n   if(fixture.current_test_name ==\"api_limit_lookup_witness_accounts\")\n   {\n      fc::set_option( options, \"api-limit-lookup-witness-accounts\", (uint32_t)200 );\n   }\n   if(fixture.current_test_name ==\"api_limit_lookup_committee_member_accounts\")\n   {\n      fc::set_option( options, \"api-limit-lookup-committee-member-accounts\", (uint32_t)200 );\n   }\n   if(fixture.current_test_name ==\"api_limit_lookup_committee_member_accounts\")\n   {\n      fc::set_option( options, \"api-limit-lookup-committee-member-accounts\", (uint32_t)200 );\n   }\n   if(fixture.current_test_name ==\"api_limit_lookup_vote_ids\")\n   {\n      fc::set_option( options, \"api-limit-lookup-vote-ids\", (uint32_t)2 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_account_limit_orders\")\n   {\n      fc::set_option( options, \"api-limit-get-account-limit-orders\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_collateral_bids\")\n   {\n      fc::set_option( options, \"api-limit-get-collateral-bids\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_top_markets\")\n   {\n      fc::set_option( options, \"api-limit-get-top-markets\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_trade_history\")\n   {\n      fc::set_option( options, \"api-limit-get-trade-history\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_trade_history_by_sequence\")\n   {\n      fc::set_option( options, \"api-limit-get-trade-history-by-sequence\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_withdraw_permissions_by_giver\")\n   {\n      fc::set_option( options, \"api-limit-get-withdraw-permissions-by-giver\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_withdraw_permissions_by_recipient\")\n   {\n      fc::set_option( options, \"api-limit-get-withdraw-permissions-by-recipient\", (uint32_t)250 );\n   }\n   if(fixture.current_test_name ==\"api_limit_get_full_accounts2\")\n   {\n      fc::set_option( options, \"api-limit-get-full-accounts\", (uint32_t)200 );\n      fc::set_option( options, \"api-limit-get-full-accounts-lists\", (uint32_t)120 );\n   }\n\n   if( fixture.current_suite_name == \"login_api_tests\" )\n   {\n      if( fixture.current_test_name ==\"get_config_test\" )\n      {\n         fc::set_option( options, \"api-node-info\", string(\"Test API node\") );\n         fc::set_option( options, \"api-limit-get-full-accounts-subscribe\", (uint32_t)120 );\n      }\n      if( fixture.current_test_name ==\"login_test\" )\n      {\n         // bytemaster/supersecret, user2/superpassword2\n         string api_access_config = R\"(\n         {\n            \"permission_map\" :\n            [\n               [\n                  \"bytemaster\",\n                  {\n                     \"password_hash_b64\" : \"9e9GF7ooXVb9k4BoSfNIPTelXeGOZ5DrgOYMj94elaY=\",\n                     \"password_salt_b64\" : \"INDdM6iCi/8=\",\n                     \"allowed_apis\" : [\"database_api\", \"network_broadcast_api\", \"history_api\", \"network_node_api\",\n                                       \"asset_api\", \"crypto_api\", \"block_api\", \"orders_api\", \"custom_operations_api\"\n                                       \"debug_api\"]\n                  }\n               ],\n               [\n                  \"user2\",\n                  {\n                     \"password_hash_b64\" : \"myadjRISnFOWn2TTd91zqbY50q0w2j/oJGlcdQkUB0Y=\",\n                     \"password_salt_b64\" : \"Zb8JrQDKNIQ=\",\n                     \"allowed_apis\" : [\"history_api\"]\n                  }\n               ],\n               [\n                  \"*\",\n                  {\n                     \"password_hash_b64\" : \"*\",\n                     \"password_salt_b64\" : \"*\",\n                     \"allowed_apis\" : [\"database_api\", \"network_broadcast_api\", \"history_api\"]\n                  }\n               ]\n            ]\n         }\n         )\";\n\n         fc::json::save_to_file( fc::json::from_string( api_access_config ),\n                                 fixture.data_dir.path() / \"api-access.json\" );\n         fc::set_option( options, \"api-access\",\n                         boost::filesystem::path(fixture.data_dir.path() / \"api-access.json\") );\n\n         fixture.app.register_plugin<graphene::debug_witness_plugin::debug_witness_plugin>(true);\n         fixture.app.register_plugin<graphene::custom_operations::custom_operations_plugin>(true);\n         fc::set_option( options, \"custom-operations-start-block\", uint32_t(1) );\n      }\n   }\n\n   // add account tracking for ahplugin for special test case with track-account enabled\n   if( !options.count(\"track-account\") && fixture.current_test_name == \"track_account\") {\n      std::vector<std::string> track_account;\n      std::string track = \"\\\"1.2.17\\\"\";\n      track_account.push_back(track);\n      fc::set_option( options, \"track-account\", track_account );\n      fc::set_option( options, \"partial-operations\", true );\n   }\n   // account tracking 2 accounts\n   if( !options.count(\"track-account\") && fixture.current_test_name == \"track_account2\") {\n      std::vector<std::string> track_account;\n      std::string track = \"\\\"1.2.0\\\"\";\n      track_account.push_back(track);\n      track = \"\\\"1.2.16\\\"\";\n      track_account.push_back(track);\n      fc::set_option( options, \"track-account\", track_account );\n   }\n   // standby votes tracking\n   if( fixture.current_test_name == \"track_votes_witnesses_disabled\"\n          || fixture.current_test_name == \"track_votes_committee_disabled\") {\n      fixture.app.chain_database()->enable_standby_votes_tracking( false );\n   }\n   // load ES or AH, but not both\n   if(fixture.current_test_name == \"elasticsearch_account_history\" ||\n         fixture.current_test_name == \"elasticsearch_history_api\") {\n      fixture.app.register_plugin<graphene::elasticsearch::elasticsearch_plugin>(true);\n\n      fc::set_option( options, \"elasticsearch-node-url\", GRAPHENE_TESTING_ES_URL );\n      fc::set_option( options, \"elasticsearch-bulk-replay\", uint32_t(2) );\n      fc::set_option( options, \"elasticsearch-bulk-sync\", uint32_t(2) );\n      fc::set_option( options, \"elasticsearch-start-es-after-block\", uint32_t(0) );\n      fc::set_option( options, \"elasticsearch-visitor\", false );\n      fc::set_option( options, \"elasticsearch-operation-object\", true );\n      fc::set_option( options, \"elasticsearch-operation-string\", true );\n      fc::set_option( options, \"elasticsearch-mode\", uint16_t(2) );\n\n      fixture.es_index_prefix = string(\"bitshares-\") + fc::to_string(uint64_t(rand())) + \"-\";\n      BOOST_TEST_MESSAGE( string(\"ES index prefix is \") + fixture.es_index_prefix );\n      fc::set_option( options, \"elasticsearch-index-prefix\", fixture.es_index_prefix );\n   }\n   else if( fixture.current_suite_name != \"performance_tests\" )\n   {\n      fixture.app.register_plugin<graphene::account_history::account_history_plugin>(true);\n   }\n\n   if( fixture.current_test_name == \"elasticsearch_objects\" ) {\n      fixture.app.register_plugin<graphene::es_objects::es_objects_plugin>(true);\n\n      fc::set_option( options, \"es-objects-elasticsearch-url\", GRAPHENE_TESTING_ES_URL );\n      fc::set_option( options, \"es-objects-bulk-replay\", uint32_t(1) );\n      fc::set_option( options, \"es-objects-bulk-sync\", uint32_t(1) );\n      fc::set_option( options, \"es-objects-proposals\", true );\n      fc::set_option( options, \"es-objects-accounts\", true );\n      fc::set_option( options, \"es-objects-assets\", true );\n      fc::set_option( options, \"es-objects-balances\", true );\n      fc::set_option( options, \"es-objects-limit-orders\", true );\n      fc::set_option( options, \"es-objects-asset-bitasset\", true );\n\n      fixture.es_obj_index_prefix = string(\"objects-\") + fc::to_string(uint64_t(rand())) + \"-\";\n      BOOST_TEST_MESSAGE( string(\"ES_OBJ index prefix is \") + fixture.es_obj_index_prefix );\n      fc::set_option( options, \"es-objects-index-prefix\", fixture.es_obj_index_prefix );\n   }\n\n   if( fixture.current_test_name == \"asset_in_collateral\"\n            || fixture.current_test_name == \"htlc_database_api\"\n            || fixture.current_test_name == \"liquidity_pool_apis_test\"\n            || fixture.current_suite_name == \"database_api_tests\"\n            || fixture.current_suite_name == \"api_limit_tests\" )\n   {\n      fixture.app.register_plugin<graphene::api_helper_indexes::api_helper_indexes>(true);\n   }\n\n   if(fixture.current_test_name == \"custom_operations_account_storage_map_test\" ||\n      fixture.current_test_name == \"custom_operations_account_storage_list_test\") {\n      fixture.app.register_plugin<graphene::custom_operations::custom_operations_plugin>(true);\n      fc::set_option( options, \"custom-operations-start-block\", uint32_t(1) );\n      if( fixture.current_test_name == \"custom_operations_account_storage_map_test\" )\n         // Set a small limit\n         fc::set_option( options, \"api-limit-get-storage-info\", uint32_t(6) );\n   }\n\n   fc::set_option( options, \"bucket-size\", string(\"[15]\") );\n\n   fixture.app.register_plugin<graphene::market_history::market_history_plugin>(true);\n   fixture.app.register_plugin<graphene::grouped_orders::grouped_orders_plugin>(true);\n\n   return sharable_options;\n}\n\nvoid database_fixture_base::vote_for_committee_and_witnesses(uint16_t num_committee, uint16_t num_witness)\n{ try {\n\n   auto &init0 = get_account(\"init0\");\n   fund(init0, asset(10));\n\n   flat_set<vote_id_type> votes;\n\n   const auto& wits = db.get_index_type<witness_index>().indices().get<by_id>();\n   num_witness = std::min(num_witness, (uint16_t) wits.size());\n   auto wit_end = wits.begin();\n   std::advance(wit_end, num_witness);\n   std::transform(wits.begin(), wit_end,\n                  std::inserter(votes, votes.end()),\n                  [](const witness_object& w) { return w.vote_id; });\n\n   const auto& comms = db.get_index_type<committee_member_index>().indices().get<by_id>();\n   num_committee = std::min(num_committee, (uint16_t) comms.size());\n   auto comm_end = comms.begin();\n   std::advance(comm_end, num_committee);\n   std::transform(comms.begin(), comm_end,\n                  std::inserter(votes, votes.end()),\n                  [](const committee_member_object& cm) { return cm.vote_id; });\n\n   account_update_operation op;\n   op.account = init0.get_id();\n   op.new_options = init0.options;\n   op.new_options->votes = votes;\n   op.new_options->num_witness = num_witness;\n   op.new_options->num_committee = num_committee;\n\n   op.fee = db.current_fee_schedule().calculate_fee( op );\n\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n\n} FC_CAPTURE_AND_RETHROW() }\n\nfc::ecc::private_key database_fixture_base::generate_private_key(string seed)\n{\n   static const fc::ecc::private_key committee = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")));\n   if( seed == \"null_key\" )\n      return committee;\n   return fc::ecc::private_key::regenerate(fc::sha256::hash(seed));\n}\n\nstring database_fixture_base::generate_anon_acct_name()\n{\n   // names of the form \"anon-acct-x123\" ; the \"x\" is necessary\n   //    to workaround issue #46\n   return \"anon-acct-x\" + std::to_string( anon_acct_count++ );\n}\n\nvoid database_fixture_base::verify_asset_supplies( const database& db )\n{\n   //wlog(\"*** Begin asset supply verification ***\");\n   const asset_dynamic_data_object& core_asset_data = db.get_core_asset().dynamic_asset_data_id(db);\n   BOOST_CHECK(core_asset_data.fee_pool == 0);\n\n   const auto& statistics_index = db.get_index_type<account_stats_index>().indices();\n   const auto& acct_balance_index = db.get_index_type<account_balance_index>().indices();\n   const auto& settle_index = db.get_index_type<force_settlement_index>().indices();\n   const auto& bids = db.get_index_type<collateral_bid_index>().indices();\n   map<asset_id_type,share_type> total_balances;\n   map<asset_id_type,share_type> total_debts;\n   share_type core_in_orders;\n   share_type core_inactive;\n   share_type core_pob;\n   share_type core_pol;\n   share_type pob_value;\n   share_type pol_value;\n   share_type reported_core_in_orders;\n   share_type reported_core_inactive;\n   share_type reported_core_pob;\n   share_type reported_core_pol;\n   share_type reported_pob_value;\n   share_type reported_pol_value;\n\n   for( const account_balance_object& b : acct_balance_index )\n      total_balances[b.asset_type] += b.balance;\n   for( const force_settlement_object& s : settle_index )\n      total_balances[s.balance.asset_id] += s.balance.amount;\n   for( const collateral_bid_object& b : bids )\n      total_balances[b.inv_swan_price.base.asset_id] += b.inv_swan_price.base.amount;\n   for( const account_statistics_object& a : statistics_index )\n   {\n      reported_core_in_orders += a.total_core_in_orders;\n      reported_core_inactive += a.total_core_inactive;\n      reported_core_pob += a.total_core_pob;\n      reported_core_pol += a.total_core_pol;\n      reported_pob_value += a.total_pob_value;\n      reported_pol_value += a.total_pol_value;\n      total_balances[asset_id_type()] += a.pending_fees + a.pending_vested_fees;\n   }\n   for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )\n   {\n      asset for_sale = o.amount_for_sale();\n      if( for_sale.asset_id == asset_id_type() && !o.is_settled_debt )\n         // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders\n         core_in_orders += for_sale.amount;\n      total_balances[asset_id_type()] += o.deferred_fee;\n      total_balances[o.deferred_paid_fee.asset_id] += o.deferred_paid_fee.amount;\n      if( o.is_settled_debt )\n      {\n         const auto& bitasset = o.receive_asset_id()(db).bitasset_data(db);\n         BOOST_CHECK_LE( o.for_sale.value, bitasset.individual_settlement_fund.value );\n         auto settled_debt = asset( bitasset.individual_settlement_debt, o.receive_asset_id() );\n         BOOST_CHECK_EQUAL( settled_debt.multiply_and_round_up( o.sell_price ).amount.value, o.for_sale.value );\n      }\n      else\n         total_balances[for_sale.asset_id] += for_sale.amount;\n   }\n   for( const call_order_object& o : db.get_index_type<call_order_index>().indices() )\n   {\n      asset col = o.get_collateral();\n      if( col.asset_id == asset_id_type() ) core_in_orders += col.amount;\n      total_balances[col.asset_id] += col.amount;\n      total_debts[o.get_debt().asset_id] += o.get_debt().amount;\n   }\n   for( const asset_object& asset_obj : db.get_index_type<asset_index>().indices() )\n   {\n      const auto& dasset_obj = asset_obj.dynamic_asset_data_id(db);\n      total_balances[asset_obj.get_id()] += dasset_obj.accumulated_fees;\n      total_balances[asset_id_type()] += dasset_obj.fee_pool;\n      if( asset_obj.is_market_issued() )\n      {\n         const auto& bad = asset_obj.bitasset_data(db);\n         total_balances[bad.options.short_backing_asset] += bad.settlement_fund;\n         total_balances[bad.options.short_backing_asset] += bad.individual_settlement_fund;\n         total_balances[bad.options.short_backing_asset] += dasset_obj.accumulated_collateral_fees;\n         if( !bad.is_globally_settled() ) // Note: if asset has been globally settled, do not check total debt\n            total_debts[bad.asset_id] += bad.individual_settlement_debt;\n      }\n      total_balances[asset_obj.get_id()] += dasset_obj.confidential_supply.value;\n   }\n   for( const vesting_balance_object& vbo : db.get_index_type< vesting_balance_index >().indices() )\n      total_balances[ vbo.balance.asset_id ] += vbo.balance.amount;\n   for( const fba_accumulator_object& fba : db.get_index_type< simple_index< fba_accumulator_object > >() )\n      total_balances[ asset_id_type() ] += fba.accumulated_fba_fees;\n   for( const balance_object& bo : db.get_index_type< balance_index >().indices() )\n      total_balances[ bo.balance.asset_id ] += bo.balance.amount;\n   for( const ticket_object& to : db.get_index_type< ticket_index >().indices() )\n   {\n      if( to.amount.asset_id == asset_id_type() )\n      {\n         if( to.current_type == lock_forever && to.value == 0 )\n            core_inactive += to.amount.amount;\n         else if( to.current_type == lock_forever && to.value != 0 )\n         {\n            core_pob += to.amount.amount;\n            pob_value += to.value;\n         }\n         else\n         {\n            core_pol += to.amount.amount;\n            pol_value += to.value;\n         }\n      }\n      total_balances[ to.amount.asset_id ] += to.amount.amount;\n   }\n   for( const liquidity_pool_object& o : db.get_index_type<liquidity_pool_index>().indices() )\n   {\n      total_balances[o.asset_a] += o.balance_a;\n      total_balances[o.asset_b] += o.balance_b;\n   }\n   for( const samet_fund_object& o : db.get_index_type<samet_fund_index>().indices() )\n   {\n      total_balances[o.asset_type] += (o.balance - o.unpaid_amount);\n   }\n\n   map<asset_id_type,share_type> credit_offer_debts_in_offers;\n   map<asset_id_type,share_type> credit_offer_debts_in_deals;\n   map<asset_id_type,share_type> credit_offer_debts_in_summs;\n   for( const credit_offer_object& o : db.get_index_type<credit_offer_index>().indices() )\n   {\n      total_balances[o.asset_type] += o.current_balance;\n      if( o.total_balance != o.current_balance)\n         credit_offer_debts_in_offers[o.asset_type] += (o.total_balance - o.current_balance);\n   }\n   for( const credit_deal_object& o : db.get_index_type<credit_deal_index>().indices() )\n   {\n      total_balances[o.collateral_asset] += o.collateral_amount;\n      credit_offer_debts_in_deals[o.debt_asset] += o.debt_amount;\n   }\n   for( const credit_deal_summary_object& o : db.get_index_type<credit_deal_summary_index>().indices() )\n   {\n      credit_offer_debts_in_summs[o.debt_asset] += o.total_debt_amount;\n   }\n\n   BOOST_CHECK_EQUAL( credit_offer_debts_in_offers.size(), credit_offer_debts_in_deals.size() );\n   BOOST_CHECK_EQUAL( credit_offer_debts_in_offers.size(), credit_offer_debts_in_summs.size() );\n   for( const auto& item : credit_offer_debts_in_offers )\n   {\n      BOOST_CHECK_EQUAL( item.second.value, credit_offer_debts_in_deals[item.first].value );\n      BOOST_CHECK_EQUAL( item.second.value, credit_offer_debts_in_summs[item.first].value );\n   }\n\n   total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget;\n\n   for( const auto& item : total_debts )\n   {\n      BOOST_CHECK_EQUAL(item.first(db).dynamic_asset_data_id(db).current_supply.value, item.second.value);\n   }\n\n   // htlc\n   const auto& htlc_idx = db.get_index_type< htlc_index >().indices().get< by_id >();\n   for( auto itr = htlc_idx.begin(); itr != htlc_idx.end(); ++itr )\n   {\n      total_balances[itr->transfer.asset_id] += itr->transfer.amount;\n   }\n\n   for( const asset_object& asset_obj : db.get_index_type<asset_index>().indices() )\n   {\n      BOOST_CHECK_EQUAL( total_balances[asset_obj.get_id()].value,\n                         asset_obj.dynamic_asset_data_id(db).current_supply.value );\n   }\n\n   BOOST_CHECK_EQUAL( core_in_orders.value , reported_core_in_orders.value );\n   BOOST_CHECK_EQUAL( core_inactive.value , reported_core_inactive.value );\n   BOOST_CHECK_EQUAL( core_pob.value , reported_core_pob.value );\n   BOOST_CHECK_EQUAL( core_pol.value , reported_core_pol.value );\n   BOOST_CHECK_EQUAL( pob_value.value , reported_pob_value.value );\n   BOOST_CHECK_EQUAL( pol_value.value , reported_pol_value.value );\n   BOOST_CHECK_EQUAL( core_pob.value , db.get_dynamic_global_properties().total_pob.value );\n   BOOST_CHECK_EQUAL( core_inactive.value , db.get_dynamic_global_properties().total_inactive.value );\n//   wlog(\"***  End  asset supply verification ***\");\n}\n\nsigned_block database_fixture_base::generate_block(uint32_t skip, const fc::ecc::private_key& key, int miss_blocks)\n{\n   skip |= database::skip_undo_history_check;\n   // skip == ~0 will skip checks specified in database::validation_steps\n   auto block = db.generate_block(db.get_slot_time(miss_blocks + 1),\n                            db.get_scheduled_witness(miss_blocks + 1),\n                            key, skip);\n   db.clear_pending();\n   verify_asset_supplies(db);\n   return block;\n}\n\nvoid database_fixture_base::generate_blocks( uint32_t block_count )\n{\n   for( uint32_t i = 0; i < block_count; ++i )\n      generate_block();\n}\n\nuint32_t database_fixture_base::generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks, uint32_t skip)\n{\n   if( miss_intermediate_blocks )\n   {\n      generate_block(skip);\n      auto slots_to_miss = db.get_slot_at_time(timestamp);\n      if( slots_to_miss <= 1 )\n         return 1;\n      --slots_to_miss;\n      generate_block(skip, init_account_priv_key, slots_to_miss);\n      return 2;\n   }\n   uint32_t blocks = 0;\n   while( db.head_block_time() < timestamp )\n   {\n      generate_block(skip);\n      ++blocks;\n   }\n   return blocks;\n}\n\naccount_create_operation database_fixture_base::make_account(\n   const std::string& name /* = \"nathan\" */,\n   public_key_type key /* = key_id_type() */\n   )\n{ try {\n   account_create_operation create_account;\n   create_account.registrar = account_id_type();\n\n   create_account.name = name;\n   create_account.owner = authority(123, key, 123);\n   create_account.active = authority(321, key, 321);\n   create_account.options.memo_key = key;\n   create_account.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n\n   auto& active_committee_members = db.get_global_properties().active_committee_members;\n   if( active_committee_members.size() > 0 )\n   {\n      set<vote_id_type> votes;\n      votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n      votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n      votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n      votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n      votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n      create_account.options.votes = flat_set<vote_id_type>(votes.begin(), votes.end());\n   }\n   create_account.options.num_committee = create_account.options.votes.size();\n\n   create_account.fee = db.current_fee_schedule().calculate_fee( create_account );\n   return create_account;\n} FC_CAPTURE_AND_RETHROW() }\n\naccount_create_operation database_fixture_base::make_account(\n   const std::string& name,\n   const account_object& registrar,\n   const account_object& referrer,\n   uint16_t referrer_percent /* = 100 */,\n   public_key_type key /* = public_key_type() */\n   )\n{\n   try\n   {\n      account_create_operation          create_account;\n\n      create_account.registrar          = registrar.id;\n      create_account.referrer           = referrer.id;\n      create_account.referrer_percent   = referrer_percent;\n\n      create_account.name = name;\n      create_account.owner = authority(123, key, 123);\n      create_account.active = authority(321, key, 321);\n      create_account.options.memo_key = key;\n      create_account.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n\n      const vector<committee_member_id_type>& active_committee_members = db.get_global_properties().active_committee_members;\n      if( active_committee_members.size() > 0 )\n      {\n         set<vote_id_type> votes;\n         votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n         votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n         votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n         votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n         votes.insert(active_committee_members[rand() % active_committee_members.size()](db).vote_id);\n         create_account.options.votes = flat_set<vote_id_type>(votes.begin(), votes.end());\n      }\n      create_account.options.num_committee = create_account.options.votes.size();\n\n      create_account.fee = db.current_fee_schedule().calculate_fee( create_account );\n      return create_account;\n   }\n   FC_CAPTURE_AND_RETHROW((name)(referrer_percent))\n}\n\nconst asset_object& database_fixture_base::get_asset( const string& symbol )const\n{\n   const auto& idx = db.get_index_type<asset_index>().indices().get<by_symbol>();\n   const auto itr = idx.find(symbol);\n   assert( itr != idx.end() );\n   return *itr;\n}\n\nconst account_object& database_fixture_base::get_account( const string& name )const\n{\n   const auto& idx = db.get_index_type<account_index>().indices().get<by_name>();\n   const auto itr = idx.find(name);\n   assert( itr != idx.end() );\n   return *itr;\n}\n\nasset_create_operation database_fixture_base::make_bitasset(\n   const string& name,\n   account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */,\n   uint16_t market_fee_percent /* = 100 */ /* 1% */,\n   uint16_t flags /* = charge_market_fee */,\n   uint16_t precision /* = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS */,\n   asset_id_type backing_asset /* = CORE */,\n   share_type max_supply,  /* = GRAPHENE_MAX_SHARE_SUPPLY */\n   optional<uint16_t> initial_cr, /* = {} */\n   optional<uint16_t> margin_call_fee_ratio /* = {} */\n   )\n{\n   asset_create_operation creator;\n   creator.issuer = issuer;\n   creator.fee = asset();\n   creator.symbol = name;\n   creator.common_options.max_supply = max_supply;\n   creator.precision = precision;\n   creator.common_options.market_fee_percent = market_fee_percent;\n   if( issuer == GRAPHENE_WITNESS_ACCOUNT )\n      flags |= witness_fed_asset;\n   creator.common_options.issuer_permissions = flags;\n   creator.common_options.flags = flags & ~global_settle;\n   creator.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n   creator.bitasset_opts = bitasset_options();\n   creator.bitasset_opts->short_backing_asset = backing_asset;\n   creator.bitasset_opts->extensions.value.initial_collateral_ratio = initial_cr;\n   creator.bitasset_opts->extensions.value.margin_call_fee_ratio = margin_call_fee_ratio;\n   return creator;\n}\n\nconst asset_object& database_fixture_base::create_bitasset(\n   const string& name,\n   account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */,\n   uint16_t market_fee_percent /* = 100 */ /* 1% */,\n   uint16_t flags /* = charge_market_fee */,\n   uint16_t precision /* = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS */,\n   asset_id_type backing_asset /* = CORE */,\n   share_type max_supply,  /* = GRAPHENE_MAX_SHARE_SUPPLY */\n   optional<uint16_t> initial_cr, /* = {} */\n   optional<uint16_t> margin_call_fee_ratio /* = {} */\n   )\n{ try {\n   asset_create_operation creator = make_bitasset( name, issuer, market_fee_percent, flags,\n                                                   precision, backing_asset, max_supply, initial_cr,\n                                                   margin_call_fee_ratio );\n   trx.operations.clear();\n   trx.operations.push_back(std::move(creator));\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   return db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n} FC_CAPTURE_AND_RETHROW( (name)(flags) ) }\n\nconst asset_object& database_fixture_base::create_prediction_market(\n   const string& name,\n   account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */,\n   uint16_t market_fee_percent /* = 100 */ /* 1% */,\n   uint16_t flags /* = charge_market_fee */,\n   uint16_t precision /* = 2, which seems arbitrary, but historically chosen */,\n   asset_id_type backing_asset /* = CORE */\n   )\n{ try {\n   asset_create_operation creator;\n   creator.issuer = issuer;\n   creator.fee = asset();\n   creator.symbol = name;\n   creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n   creator.precision = precision;\n   creator.common_options.market_fee_percent = market_fee_percent;\n   creator.common_options.issuer_permissions = flags | global_settle;\n   creator.common_options.flags = flags & ~global_settle;\n   if( issuer == GRAPHENE_WITNESS_ACCOUNT )\n      creator.common_options.flags |= witness_fed_asset;\n   creator.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n   creator.bitasset_opts = bitasset_options();\n   creator.bitasset_opts->short_backing_asset = backing_asset;\n   creator.is_prediction_market = true;\n   trx.operations.clear();\n   trx.operations.push_back(std::move(creator));\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   return db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n} FC_CAPTURE_AND_RETHROW( (name)(flags) ) }\n\n\nconst asset_object& database_fixture_base::create_user_issued_asset( const string& name )\n{\n   asset_create_operation creator;\n   creator.issuer = account_id_type();\n   creator.fee = asset();\n   creator.symbol = name;\n   creator.common_options.max_supply = 0;\n   creator.precision = 2;\n   creator.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n   creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n   creator.common_options.flags = charge_market_fee;\n   creator.common_options.issuer_permissions = charge_market_fee;\n   trx.operations.clear();\n   trx.operations.push_back(std::move(creator));\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   return db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n}\n\nconst asset_object& database_fixture_base::create_user_issued_asset( const string& name, const account_object& issuer,\n                                                               uint16_t flags, const price& core_exchange_rate,\n                                                               uint8_t precision, uint16_t market_fee_percent,\n                                                               additional_asset_options_t additional_options)\n{\n   asset_create_operation creator;\n   creator.issuer = issuer.id;\n   creator.fee = asset();\n   creator.symbol = name;\n   creator.common_options.max_supply = 0;\n   creator.precision = precision;\n   creator.common_options.core_exchange_rate = core_exchange_rate;\n   creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n   creator.common_options.flags = flags;\n   creator.common_options.issuer_permissions = flags;\n   creator.common_options.market_fee_percent = market_fee_percent;\n   creator.common_options.extensions = std::move(additional_options);\n   trx.operations.clear();\n   trx.operations.push_back(std::move(creator));\n   set_expiration( db, trx );\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   return db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n}\n\nvoid database_fixture_base::issue_uia( const account_object& recipient, asset amount )\n{\n   BOOST_TEST_MESSAGE( \"Issuing UIA\" );\n   asset_issue_operation op;\n   op.issuer = amount.asset_id(db).issuer;\n   op.asset_to_issue = amount;\n   op.issue_to_account = recipient.id;\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   PUSH_TX( db, trx, ~0 );\n   trx.operations.clear();\n}\n\nvoid database_fixture_base::issue_uia( account_id_type recipient_id, asset amount )\n{\n   issue_uia( recipient_id(db), amount );\n}\n\nvoid database_fixture_base::reserve_asset( account_id_type account, asset amount )\n{\n   BOOST_TEST_MESSAGE( \"Reserving asset\" );\n   asset_reserve_operation op;\n   op.payer = account;\n   op.amount_to_reserve = amount;\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   set_expiration( db, trx );\n   trx.validate();\n   PUSH_TX( db, trx, ~0 );\n   trx.operations.clear();\n}\n\nvoid database_fixture_base::change_fees(\n   const fee_parameters::flat_set_type& new_params,\n   uint32_t new_scale /* = 0 */\n   )\n{\n   const chain_parameters& current_chain_params = db.get_global_properties().parameters;\n   const fee_schedule& current_fees = current_chain_params.get_current_fees();\n\n   flat_map< int, fee_parameters > fee_map;\n   fee_map.reserve( current_fees.parameters.size() );\n   for( const fee_parameters& op_fee : current_fees.parameters )\n      fee_map[ op_fee.which() ] = op_fee;\n   for( const fee_parameters& new_fee : new_params )\n      fee_map[ new_fee.which() ] = new_fee;\n\n   fee_schedule_type new_fees;\n\n   for( const std::pair< int, fee_parameters >& item : fee_map )\n      new_fees.parameters.insert( item.second );\n   if( new_scale != 0 )\n      new_fees.scale = new_scale;\n\n   chain_parameters new_chain_params = current_chain_params;\n   new_chain_params.get_mutable_fees() = new_fees;\n\n   db.modify(db.get_global_properties(), [&](global_property_object& p) {\n      p.parameters = new_chain_params;\n   });\n}\n\nconst account_object& database_fixture_base::create_account(\n   const string& name,\n   const public_key_type& key /* = public_key_type() */\n   )\n{\n   trx.operations.clear();\n   trx.operations.push_back(make_account(name, key));\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   auto& result = db.get<account_object>(ptx.operation_results[0].get<object_id_type>());\n   trx.operations.clear();\n   return result;\n}\n\nconst account_object& database_fixture_base::create_account(\n   const string& name,\n   const account_object& registrar,\n   const account_object& referrer,\n   uint16_t referrer_percent /* = 100 (1%)*/,\n   const public_key_type& key /*= public_key_type()*/\n   )\n{\n   try\n   {\n      trx.operations.resize(1);\n      trx.operations.back() = (make_account(name, registrar, referrer, referrer_percent, key));\n      trx.validate();\n      auto r = PUSH_TX(db, trx, ~0);\n      const auto& result = db.get<account_object>(r.operation_results[0].get<object_id_type>());\n      trx.operations.clear();\n      return result;\n   }\n   FC_CAPTURE_AND_RETHROW( (name)(registrar)(referrer) )\n}\n\nconst account_object& database_fixture_base::create_account(\n   const string& name,\n   const private_key_type& key,\n   const account_id_type& registrar_id /* = account_id_type() */,\n   const account_id_type& referrer_id /* = account_id_type() */,\n   uint16_t referrer_percent /* = 100 (1%)*/\n   )\n{\n   try\n   {\n      trx.operations.clear();\n\n      account_create_operation account_create_op;\n\n      account_create_op.registrar = registrar_id;\n      account_create_op.referrer = referrer_id;\n      account_create_op.referrer_percent = referrer_percent;\n      account_create_op.name = name;\n      account_create_op.owner = authority(1234, public_key_type(key.get_public_key()), 1234);\n      account_create_op.active = authority(5678, public_key_type(key.get_public_key()), 5678);\n      account_create_op.options.memo_key = key.get_public_key();\n      account_create_op.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n      trx.operations.push_back( account_create_op );\n\n      trx.validate();\n\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const account_object& result = db.get<account_object>(ptx.operation_results[0].get<object_id_type>());\n      trx.operations.clear();\n      return result;\n   }\n   FC_CAPTURE_AND_RETHROW( (name)(registrar_id)(referrer_id) )\n}\n\nconst committee_member_object& database_fixture_base::create_committee_member( const account_object& owner )\n{\n   committee_member_create_operation op;\n   op.committee_member_account = owner.id;\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   return db.get<committee_member_object>(ptx.operation_results[0].get<object_id_type>());\n}\n\nconst witness_object& database_fixture_base::create_witness(account_id_type owner,\n                                                        const fc::ecc::private_key& signing_private_key,\n                                                        uint32_t skip_flags )\n{\n   return create_witness(owner(db), signing_private_key, skip_flags );\n}\n\nconst witness_object& database_fixture_base::create_witness( const account_object& owner,\n                                                        const fc::ecc::private_key& signing_private_key,\n                                                        uint32_t skip_flags )\n{ try {\n   witness_create_operation op;\n   op.witness_account = owner.id;\n   op.block_signing_key = signing_private_key.get_public_key();\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, skip_flags );\n   trx.clear();\n   return db.get<witness_object>(ptx.operation_results[0].get<object_id_type>());\n} FC_CAPTURE_AND_RETHROW() }\n\nconst worker_object& database_fixture_base::create_worker( const account_id_type owner, const share_type daily_pay, const fc::microseconds& duration )\n{ try {\n   worker_create_operation op;\n   op.owner = owner;\n   op.daily_pay = daily_pay;\n   op.initializer = burn_worker_initializer();\n   op.work_begin_date = db.head_block_time();\n   op.work_end_date = op.work_begin_date + duration;\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   trx.clear();\n   return db.get<worker_object>(ptx.operation_results[0].get<object_id_type>());\n} FC_CAPTURE_AND_RETHROW() }\n\nuint64_t database_fixture_base::fund(\n   const account_object& account,\n   const asset& amount /* = asset(500000) */\n   )\n{\n   transfer(account_id_type()(db), account, amount);\n   return get_balance(account, amount.asset_id(db));\n}\n\nvoid database_fixture_base::sign(signed_transaction& trx, const fc::ecc::private_key& key)\n{\n   trx.sign( key, db.get_chain_id() );\n}\n\ndigest_type database_fixture_base::digest( const transaction& tx )\n{\n   return tx.digest();\n}\n\nlimit_order_create_operation database_fixture_base::make_limit_order_create_op(\n                                                const account_id_type& user, const asset& amount, const asset& recv,\n                                                const time_point_sec& order_expiration,\n                                                const optional< vector< limit_order_auto_action > >& on_fill ) const\n{\n   limit_order_create_operation buy_order;\n   buy_order.seller = user;\n   buy_order.amount_to_sell = amount;\n   buy_order.min_to_receive = recv;\n   buy_order.expiration = order_expiration;\n   buy_order.extensions.value.on_fill = on_fill;\n   return buy_order;\n}\n\nconst limit_order_object* database_fixture_base::create_sell_order(\n                                                const account_id_type& user, const asset& amount, const asset& recv,\n                                                const time_point_sec& order_expiration,\n                                                const price& fee_core_exchange_rate,\n                                                const optional< vector< limit_order_auto_action > >& on_fill )\n{\n   set_expiration( db, trx );\n   trx.operations.clear();\n\n   limit_order_create_operation buy_order = make_limit_order_create_op( user, amount, recv, order_expiration,\n                                                                        on_fill );\n   trx.operations = {buy_order};\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate);\n   trx.validate();\n   auto processed = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return db.find<limit_order_object>( processed.operation_results[0].get<object_id_type>() );\n}\n\nconst limit_order_object* database_fixture_base::create_sell_order(\n                                                const account_object& user, const asset& amount, const asset& recv,\n                                                const time_point_sec& order_expiration,\n                                                const price& fee_core_exchange_rate,\n                                                const optional< vector< limit_order_auto_action > >& on_fill )\n{\n   return create_sell_order( user.get_id(), amount, recv, order_expiration, fee_core_exchange_rate );\n}\n\nlimit_order_update_operation database_fixture_base::make_limit_order_update_op(\n                           const account_id_type& seller_id,\n                           const limit_order_id_type& order_id,\n                           const fc::optional<price>& new_price,\n                           const fc::optional<asset>& delta_amount,\n                           const fc::optional<time_point_sec>& new_expiration,\n                           const optional< vector< limit_order_auto_action > >& on_fill )const\n{\n   limit_order_update_operation update_order;\n   update_order.seller = seller_id;\n   update_order.order = order_id;\n   update_order.new_price = new_price;\n   update_order.delta_amount_to_sell = delta_amount;\n   update_order.new_expiration = new_expiration;\n   update_order.on_fill = on_fill;\n   return update_order;\n}\n\nvoid database_fixture_base::update_limit_order(const limit_order_object& order,\n                                          const fc::optional<price>& new_price,\n                                          const fc::optional<asset>& delta_amount,\n                                          const fc::optional<time_point_sec>& new_expiration,\n                                          const price& fee_core_exchange_rate,\n                                          const optional< vector< limit_order_auto_action > >& on_fill )\n{\n   limit_order_update_operation update_order = make_limit_order_update_op( order.seller, order.get_id(), new_price,\n                                                                           delta_amount, new_expiration, on_fill );\n   trx.operations = {update_order};\n   for(auto& op : trx.operations) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate);\n   trx.validate();\n   auto processed = PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n}\n\nvoid database_fixture_base::update_limit_order(const limit_order_id_type& order_id,\n                                          const fc::optional<price>& new_price,\n                                          const fc::optional<asset>& delta_amount,\n                                          const fc::optional<time_point_sec>& new_expiration,\n                                          const price& fee_core_exchange_rate,\n                                          const optional< vector< limit_order_auto_action > >& on_fill )\n{\n   update_limit_order( order_id(db), new_price, delta_amount, new_expiration, fee_core_exchange_rate, on_fill );\n}\n\nasset database_fixture_base::cancel_limit_order( const limit_order_object& order )\n{\n  limit_order_cancel_operation cancel_order;\n  cancel_order.fee_paying_account = order.seller;\n  cancel_order.order = order.id;\n  trx.operations.clear();\n  trx.operations.push_back(cancel_order);\n  for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n  trx.validate();\n  auto processed = PUSH_TX(db, trx, ~0);\n  trx.operations.clear();\n  verify_asset_supplies(db);\n  return processed.operation_results[0].get<asset>();\n}\n\nvoid database_fixture_base::transfer(\n   account_id_type from,\n   account_id_type to,\n   const asset& amount,\n   const asset& fee /* = asset() */\n   )\n{\n   transfer(from(db), to(db), amount, fee);\n}\n\nvoid database_fixture_base::transfer(\n   const account_object& from,\n   const account_object& to,\n   const asset& amount,\n   const asset& fee /* = asset() */ )\n{\n   try\n   {\n      set_expiration( db, trx );\n      transfer_operation trans;\n      trans.from = from.id;\n      trans.to   = to.id;\n      trans.amount = amount;\n      trx.operations.clear();\n      trx.operations.push_back(trans);\n\n      if( fee == asset() )\n      {\n         for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n      }\n      trx.validate();\n      PUSH_TX(db, trx, ~0);\n      verify_asset_supplies(db);\n      trx.operations.clear();\n   } FC_CAPTURE_AND_RETHROW( (from.id)(to.id)(amount)(fee) )\n}\n\nvoid database_fixture_base::update_feed_producers( const asset_object& mia, flat_set<account_id_type> producers )\n{ try {\n   set_expiration( db, trx );\n   trx.operations.clear();\n   asset_update_feed_producers_operation op;\n   op.asset_to_update = mia.id;\n   op.issuer = mia.issuer;\n   op.new_feed_producers = std::move(producers);\n   trx.operations = {std::move(op)};\n\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n} FC_CAPTURE_AND_RETHROW( (mia)(producers) ) }\n\nvoid database_fixture_base::publish_feed( const asset_object& mia, const account_object& by, const price_feed& f,\n                                     const optional<uint16_t> icr )\n{\n   set_expiration( db, trx );\n   trx.operations.clear();\n\n   asset_publish_feed_operation op;\n   op.publisher = by.id;\n   op.asset_id = mia.id;\n   op.feed = f;\n   if( op.feed.core_exchange_rate.is_null() )\n   {\n      op.feed.core_exchange_rate = op.feed.settlement_price;\n      if( db.head_block_time() > HARDFORK_480_TIME )\n         op.feed.core_exchange_rate.quote.asset_id = asset_id_type();\n   }\n   op.extensions.value.initial_collateral_ratio = icr;\n   trx.operations.emplace_back( std::move(op) );\n\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n}\n\nvoid database_fixture_base::publish_feed(const account_id_type& publisher,\n      const asset_id_type& asset1, int64_t amount1,\n      const asset_id_type& asset2, int64_t amount2,\n      const asset_id_type& core_id, const optional<uint16_t> icr)\n{\n   const asset_object& a1 = asset1(db);\n   const asset_object& a2 = asset2(db);\n   const asset_object& core = core_id(db);\n   asset_publish_feed_operation op;\n   op.publisher = publisher;\n   op.asset_id = asset2;\n   op.feed.settlement_price = ~price(a1.amount(amount1),a2.amount(amount2));\n   op.feed.core_exchange_rate = ~price(core.amount(amount1), a2.amount(amount2));\n   op.extensions.value.initial_collateral_ratio = icr;\n   trx.operations.clear();\n   trx.operations.emplace_back(std::move(op));\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   set_expiration( db, trx );\n   PUSH_TX( db, trx, ~0);\n   verify_asset_supplies(db);\n   generate_block();\n   trx.clear();\n}\n\nvoid database_fixture_base::force_global_settle( const asset_object& what, const price& p )\n{ try {\n   set_expiration( db, trx );\n   trx.operations.clear();\n   asset_global_settle_operation sop;\n   sop.issuer = what.issuer;\n   sop.asset_to_settle = what.id;\n   sop.settle_price = p;\n   trx.operations.push_back(sop);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n} FC_CAPTURE_AND_RETHROW( (what)(p) ) }\n\noperation_result database_fixture_base::force_settle( const account_object& who, asset what )\n{ try {\n   set_expiration( db, trx );\n   trx.operations.clear();\n   asset_settle_operation sop;\n   sop.account = who.id;\n   sop.amount = what;\n   trx.operations.push_back(sop);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result;\n} FC_CAPTURE_AND_RETHROW( (who)(what) ) }\n\nconst call_order_object* database_fixture_base::borrow( const account_object& who, asset what, asset collateral,\n                                                   optional<uint16_t> target_cr )\n{ try {\n   set_expiration( db, trx );\n   trx.operations.clear();\n   call_order_update_operation update = {};\n   update.funding_account = who.id;\n   update.delta_collateral = collateral;\n   update.delta_debt = what;\n   update.extensions.value.target_collateral_ratio = target_cr;\n   trx.operations.push_back(update);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n\n   auto& call_idx = db.get_index_type<call_order_index>().indices().get<by_account>();\n   auto itr = call_idx.find( boost::make_tuple(who.get_id(), what.asset_id) );\n   const call_order_object* call_obj = nullptr;\n\n   if( itr != call_idx.end() )\n      call_obj = &*itr;\n   return call_obj;\n} FC_CAPTURE_AND_RETHROW( (who.name)(what)(collateral)(target_cr) ) }\n\nvoid database_fixture_base::cover(const account_object& who, asset what, asset collateral, optional<uint16_t> target_cr)\n{ try {\n   set_expiration( db, trx );\n   trx.operations.clear();\n   call_order_update_operation update = {};\n   update.funding_account = who.id;\n   update.delta_collateral = -collateral;\n   update.delta_debt = -what;\n   update.extensions.value.target_collateral_ratio = target_cr;\n   trx.operations.push_back(update);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n} FC_CAPTURE_AND_RETHROW( (who.name)(what)(collateral)(target_cr) ) }\n\nvoid database_fixture_base::bid_collateral(const account_object& who, const asset& to_bid, const asset& to_cover)\n{ try {\n   set_expiration( db, trx );\n   trx.operations.clear();\n   bid_collateral_operation bid;\n   bid.bidder = who.id;\n   bid.additional_collateral = to_bid;\n   bid.debt_covered = to_cover;\n   trx.operations.push_back(bid);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n} FC_CAPTURE_AND_RETHROW( (who.name)(to_bid)(to_cover) ) }\n\nvoid database_fixture_base::fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount )\n{\n   asset_fund_fee_pool_operation fund;\n   fund.from_account = from.id;\n   fund.asset_id = asset_to_fund.id;\n   fund.amount = amount;\n   trx.operations.clear();\n   trx.operations.push_back( fund );\n\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   set_expiration( db, trx );\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n}\n\nticket_create_operation database_fixture_base::make_ticket_create_op( account_id_type account, ticket_type type,\n                                                                 const asset& amount ) const\n{\n   ticket_create_operation op;\n   op.account = account;\n   op.target_type = static_cast<uint8_t>(type);\n   op.amount = amount;\n   return op;\n}\n\nconst ticket_object& database_fixture_base::create_ticket( account_id_type account, ticket_type type,\n                                                      const asset& amount )\n{\n   ticket_create_operation op = make_ticket_create_op( account, type, amount );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return db.get<ticket_object>( op_result.get<object_id_type>() );\n}\n\nticket_update_operation database_fixture_base::make_ticket_update_op( const ticket_object& ticket, ticket_type type,\n                                                                 const optional<asset>& amount ) const\n{\n   ticket_update_operation op;\n   op.ticket = ticket.id;\n   op.account = ticket.account;\n   op.target_type = static_cast<uint8_t>(type);\n   op.amount_for_new_target = amount;\n   return op;\n}\n\ngeneric_operation_result database_fixture_base::update_ticket( const ticket_object& ticket, ticket_type type,\n                                                          const optional<asset>& amount )\n{\n   ticket_update_operation op = make_ticket_update_op( ticket, type, amount );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<generic_operation_result>();\n}\n\nliquidity_pool_create_operation database_fixture_base::make_liquidity_pool_create_op(\n                                                  account_id_type account, asset_id_type asset_a,\n                                                  asset_id_type asset_b, asset_id_type share_asset,\n                                                  uint16_t taker_fee_percent, uint16_t withdrawal_fee_percent )const\n{\n   liquidity_pool_create_operation op;\n   op.account = account;\n   op.asset_a = asset_a;\n   op.asset_b = asset_b;\n   op.share_asset = share_asset;\n   op.taker_fee_percent = taker_fee_percent;\n   op.withdrawal_fee_percent = withdrawal_fee_percent;\n   return op;\n}\n\nconst liquidity_pool_object& database_fixture_base::create_liquidity_pool( account_id_type account, asset_id_type asset_a,\n                                                  asset_id_type asset_b, asset_id_type share_asset,\n                                                  uint16_t taker_fee_percent, uint16_t withdrawal_fee_percent )\n{\n   liquidity_pool_create_operation op = make_liquidity_pool_create_op( account, asset_a, asset_b, share_asset,\n                                                                       taker_fee_percent, withdrawal_fee_percent );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return db.get<liquidity_pool_object>( *op_result.get<generic_operation_result>().new_objects.begin() );\n}\n\nliquidity_pool_delete_operation database_fixture_base::make_liquidity_pool_delete_op( account_id_type account,\n                                                  liquidity_pool_id_type pool )const\n{\n   liquidity_pool_delete_operation op;\n   op.account = account;\n   op.pool = pool;\n   return op;\n}\n\ngeneric_operation_result database_fixture_base::delete_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool )\n{\n   liquidity_pool_delete_operation op = make_liquidity_pool_delete_op( account, pool );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<generic_operation_result>();\n}\n\nliquidity_pool_update_operation database_fixture_base::make_liquidity_pool_update_op( account_id_type account,\n                                                  liquidity_pool_id_type pool,\n                                                  optional<uint16_t> taker_fee_percent,\n                                                  optional<uint16_t> withdrawal_fee_percent )const\n{\n   liquidity_pool_update_operation op;\n   op.account = account;\n   op.pool = pool;\n   op.taker_fee_percent = taker_fee_percent;\n   op.withdrawal_fee_percent = withdrawal_fee_percent;\n   return op;\n}\n\nvoid database_fixture_base::update_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool,\n                                                  optional<uint16_t> taker_fee_percent,\n                                                  optional<uint16_t> withdrawal_fee_percent )\n{\n   liquidity_pool_update_operation op = make_liquidity_pool_update_op( account, pool, taker_fee_percent,\n                                                                       withdrawal_fee_percent );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   PUSH_TX(db, trx, ~0);\n}\n\nliquidity_pool_deposit_operation database_fixture_base::make_liquidity_pool_deposit_op( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_a,\n                                                  const asset& amount_b )const\n{\n   liquidity_pool_deposit_operation op;\n   op.account = account;\n   op.pool = pool;\n   op.amount_a = amount_a;\n   op.amount_b = amount_b;\n   return op;\n}\n\ngeneric_exchange_operation_result database_fixture_base::deposit_to_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_a,\n                                                  const asset& amount_b )\n{\n   liquidity_pool_deposit_operation op = make_liquidity_pool_deposit_op( account, pool, amount_a, amount_b );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<generic_exchange_operation_result>();\n}\n\nliquidity_pool_withdraw_operation database_fixture_base::make_liquidity_pool_withdraw_op( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& share_amount )const\n{\n   liquidity_pool_withdraw_operation op;\n   op.account = account;\n   op.pool = pool;\n   op.share_amount = share_amount;\n   return op;\n}\n\ngeneric_exchange_operation_result database_fixture_base::withdraw_from_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& share_amount )\n{\n   liquidity_pool_withdraw_operation op = make_liquidity_pool_withdraw_op( account, pool, share_amount );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<generic_exchange_operation_result>();\n}\n\nliquidity_pool_exchange_operation database_fixture_base::make_liquidity_pool_exchange_op( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_to_sell,\n                                                  const asset& min_to_receive )const\n{\n   liquidity_pool_exchange_operation op;\n   op.account = account;\n   op.pool = pool;\n   op.amount_to_sell = amount_to_sell;\n   op.min_to_receive = min_to_receive;\n   return op;\n}\n\ngeneric_exchange_operation_result database_fixture_base::exchange_with_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_to_sell,\n                                                  const asset& min_to_receive )\n{\n   liquidity_pool_exchange_operation op = make_liquidity_pool_exchange_op( account, pool, amount_to_sell,\n                                                                           min_to_receive );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<generic_exchange_operation_result>();\n}\n\nsamet_fund_create_operation database_fixture_base::make_samet_fund_create_op(\n                                                  account_id_type account, asset_id_type asset_type,\n                                                  share_type balance, uint32_t fee_rate )const\n{\n   samet_fund_create_operation op;\n   op.owner_account = account;\n   op.asset_type = asset_type;\n   op.balance = balance;\n   op.fee_rate = fee_rate;\n   return op;\n}\n\nconst samet_fund_object& database_fixture_base::create_samet_fund(\n                                                  account_id_type account, asset_id_type asset_type,\n                                                  share_type balance, uint32_t fee_rate )\n{\n   samet_fund_create_operation op = make_samet_fund_create_op( account, asset_type, balance, fee_rate );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return db.get<samet_fund_object>( op_result.get<object_id_type>() );\n}\n\nsamet_fund_delete_operation database_fixture_base::make_samet_fund_delete_op(\n                                                  account_id_type account, samet_fund_id_type fund_id )const\n{\n   samet_fund_delete_operation op;\n   op.owner_account = account;\n   op.fund_id = fund_id;\n   return op;\n}\n\nasset database_fixture_base::delete_samet_fund( account_id_type account,  samet_fund_id_type fund_id )\n{\n   samet_fund_delete_operation op = make_samet_fund_delete_op( account, fund_id );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<asset>();\n}\n\nsamet_fund_update_operation database_fixture_base::make_samet_fund_update_op(\n                                                  account_id_type account, samet_fund_id_type fund_id,\n                                                  const optional<asset>& delta_amount,\n                                                  const optional<uint32_t>& new_fee_rate )const\n{\n   samet_fund_update_operation op;\n   op.owner_account = account;\n   op.fund_id = fund_id;\n   op.delta_amount = delta_amount;\n   op.new_fee_rate = new_fee_rate;\n   return op;\n}\n\nvoid database_fixture_base::update_samet_fund( account_id_type account, samet_fund_id_type fund_id,\n                                                  const optional<asset>& delta_amount,\n                                                  const optional<uint32_t>& new_fee_rate )\n{\n   samet_fund_update_operation op = make_samet_fund_update_op( account, fund_id, delta_amount, new_fee_rate );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n}\n\nsamet_fund_borrow_operation database_fixture_base::make_samet_fund_borrow_op(\n                                                  account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& borrow_amount )const\n{\n   samet_fund_borrow_operation op;\n   op.borrower = account;\n   op.fund_id = fund_id;\n   op.borrow_amount = borrow_amount;\n   return op;\n}\n\nvoid database_fixture_base::borrow_from_samet_fund( account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& borrow_amount )\n{\n   samet_fund_borrow_operation op = make_samet_fund_borrow_op( account, fund_id, borrow_amount );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   const auto& result_dtl = op_result.get<extendable_operation_result>().value;\n   BOOST_REQUIRE( result_dtl.impacted_accounts.valid() );\n   BOOST_CHECK( *result_dtl.impacted_accounts == flat_set<account_id_type>({ fund_id(db).owner_account }) );\n}\n\nsamet_fund_repay_operation database_fixture_base::make_samet_fund_repay_op(\n                                                  account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& repay_amount, const asset& fund_fee )const\n{\n   samet_fund_repay_operation op;\n   op.account = account;\n   op.fund_id = fund_id;\n   op.repay_amount = repay_amount;\n   op.fund_fee = fund_fee;\n   return op;\n}\n\nvoid database_fixture_base::repay_to_samet_fund( account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& repay_amount, const asset& fund_fee )\n{\n   samet_fund_repay_operation op = make_samet_fund_repay_op( account, fund_id, repay_amount, fund_fee );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   const auto& result_dtl = op_result.get<extendable_operation_result>().value;\n   BOOST_REQUIRE( result_dtl.impacted_accounts.valid() );\n   BOOST_CHECK( *result_dtl.impacted_accounts == flat_set<account_id_type>({ fund_id(db).owner_account }) );\n}\n\ncredit_offer_create_operation database_fixture_base::make_credit_offer_create_op(\n                                       account_id_type account, asset_id_type asset_type,\n                                       share_type balance, uint32_t fee_rate, uint32_t max_duration,\n                                       share_type min_amount, bool enabled, time_point_sec disable_time,\n                                       flat_map<asset_id_type, price>          acceptable_collateral,\n                                       flat_map<account_id_type, share_type>   acceptable_borrowers )const\n{\n   credit_offer_create_operation op;\n   op.owner_account = account;\n   op.asset_type = asset_type;\n   op.balance = balance;\n   op.fee_rate = fee_rate;\n   op.max_duration_seconds = max_duration;\n   op.min_deal_amount = min_amount;\n   op.enabled = enabled;\n   op.auto_disable_time = disable_time;\n   op.acceptable_collateral = acceptable_collateral;\n   op.acceptable_borrowers = acceptable_borrowers;\n   return op;\n}\n\nconst credit_offer_object& database_fixture_base::create_credit_offer(\n                                       account_id_type account, asset_id_type asset_type,\n                                       share_type balance, uint32_t fee_rate, uint32_t max_duration,\n                                       share_type min_amount, bool enabled, time_point_sec disable_time,\n                                       flat_map<asset_id_type, price>          acceptable_collateral,\n                                       flat_map<account_id_type, share_type>   acceptable_borrowers )\n{\n   credit_offer_create_operation op = make_credit_offer_create_op( account, asset_type, balance, fee_rate,\n                                         max_duration, min_amount, enabled, disable_time,\n                                         acceptable_collateral, acceptable_borrowers );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return db.get<credit_offer_object>( op_result.get<object_id_type>() );\n}\n\ncredit_offer_delete_operation database_fixture_base::make_credit_offer_delete_op( account_id_type account,\n                                                  credit_offer_id_type offer_id )const\n{\n   credit_offer_delete_operation op;\n   op.owner_account = account;\n   op.offer_id = offer_id;\n   return op;\n}\n\nasset database_fixture_base::delete_credit_offer( account_id_type account,  credit_offer_id_type offer_id )\n{\n   credit_offer_delete_operation op = make_credit_offer_delete_op( account, offer_id );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<asset>();\n}\n\ncredit_offer_update_operation database_fixture_base::make_credit_offer_update_op(\n                                       account_id_type account, credit_offer_id_type offer_id,\n                                       const optional<asset>& delta_amount,\n                                       const optional<uint32_t>& new_fee_rate,\n                                       const optional<uint32_t>& max_duration_seconds,\n                                       const optional<share_type>& min_deal_amount,\n                                       const optional<bool>& enabled,\n                                       const optional<time_point_sec>& auto_disable_time,\n                                       const optional<flat_map<asset_id_type, price>>& acceptable_collateral,\n                                       const optional<flat_map<account_id_type, share_type>>& acceptable_borrowers\n                                    )const\n{\n   credit_offer_update_operation op;\n   op.owner_account = account;\n   op.offer_id = offer_id;\n   op.delta_amount = delta_amount;\n   op.fee_rate = new_fee_rate;\n   op.max_duration_seconds = max_duration_seconds;\n   op.min_deal_amount = min_deal_amount;\n   op.enabled = enabled;\n   op.auto_disable_time = auto_disable_time;\n   op.acceptable_collateral = acceptable_collateral;\n   op.acceptable_borrowers = acceptable_borrowers;\n   return op;\n}\n\nvoid database_fixture_base::update_credit_offer( account_id_type account, credit_offer_id_type offer_id,\n                                       const optional<asset>& delta_amount,\n                                       const optional<uint32_t>& new_fee_rate,\n                                       const optional<uint32_t>& max_duration_seconds,\n                                       const optional<share_type>& min_deal_amount,\n                                       const optional<bool>& enabled,\n                                       const optional<time_point_sec>& auto_disable_time,\n                                       const optional<flat_map<asset_id_type, price>>& acceptable_collateral,\n                                       const optional<flat_map<account_id_type, share_type>>& acceptable_borrowers )\n{\n   credit_offer_update_operation op = make_credit_offer_update_op( account, offer_id, delta_amount, new_fee_rate,\n                                         max_duration_seconds, min_deal_amount, enabled, auto_disable_time,\n                                         acceptable_collateral, acceptable_borrowers );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n}\n\ncredit_offer_accept_operation database_fixture_base::make_credit_offer_accept_op(\n                                       account_id_type account, credit_offer_id_type offer_id,\n                                       const asset& borrow_amount, const asset& collateral,\n                                       uint32_t max_fee_rate, uint32_t min_duration,\n                                       const optional<uint8_t>& auto_repay )const\n{\n   credit_offer_accept_operation op;\n   op.borrower = account;\n   op.offer_id = offer_id;\n   op.borrow_amount = borrow_amount;\n   op.collateral = collateral;\n   op.max_fee_rate = max_fee_rate;\n   op.min_duration_seconds = min_duration;\n   op.extensions.value.auto_repay = auto_repay;\n   return op;\n}\n\nconst credit_deal_object& database_fixture_base::borrow_from_credit_offer(\n                                       account_id_type account, credit_offer_id_type offer_id,\n                                       const asset& borrow_amount, const asset& collateral,\n                                       uint32_t max_fee_rate, uint32_t min_duration,\n                                       const optional<uint8_t>& auto_repay )\n{\n   credit_offer_accept_operation op = make_credit_offer_accept_op( account, offer_id, borrow_amount, collateral,\n                                                                   max_fee_rate, min_duration, auto_repay );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   const auto& result_dtl = op_result.get<extendable_operation_result>().value;\n   BOOST_REQUIRE( result_dtl.impacted_accounts.valid() );\n   BOOST_CHECK( *result_dtl.impacted_accounts == flat_set<account_id_type>({ offer_id(db).owner_account }) );\n   BOOST_REQUIRE( result_dtl.new_objects.valid() );\n   BOOST_REQUIRE_EQUAL( result_dtl.new_objects->size(), 1u );\n   return db.get<credit_deal_object>( *result_dtl.new_objects->begin() );\n}\n\ncredit_deal_repay_operation database_fixture_base::make_credit_deal_repay_op(\n                                       account_id_type account, credit_deal_id_type deal_id,\n                                       const asset& repay_amount, const asset& credit_fee )const\n{\n   credit_deal_repay_operation op;\n   op.account = account;\n   op.deal_id = deal_id;\n   op.repay_amount = repay_amount;\n   op.credit_fee = credit_fee;\n   return op;\n}\n\nextendable_operation_result_dtl database_fixture_base::repay_credit_deal(\n                                       account_id_type account, credit_deal_id_type deal_id,\n                                       const asset& repay_amount, const asset& credit_fee )\n{\n   credit_deal_repay_operation op = make_credit_deal_repay_op( account, deal_id, repay_amount, credit_fee );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   processed_transaction ptx = PUSH_TX(db, trx, ~0);\n   const operation_result& op_result = ptx.operation_results.front();\n   trx.operations.clear();\n   verify_asset_supplies(db);\n   return op_result.get<extendable_operation_result>().value;\n}\n\ncredit_deal_update_operation database_fixture_base::make_credit_deal_update_op(\n                                       account_id_type account, credit_deal_id_type deal_id,\n                                       uint8_t auto_repay )const\n{\n   credit_deal_update_operation op;\n   op.account = account;\n   op.deal_id = deal_id;\n   op.auto_repay = auto_repay;\n   return op;\n}\n\nvoid database_fixture_base::update_credit_deal(\n                                       account_id_type account, credit_deal_id_type deal_id,\n                                       uint8_t auto_repay )\n{\n   credit_deal_update_operation op = make_credit_deal_update_op( account, deal_id, auto_repay );\n   trx.operations.clear();\n   trx.operations.push_back( op );\n\n   for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n   trx.validate();\n   set_expiration( db, trx );\n   PUSH_TX(db, trx, ~0);\n   trx.operations.clear();\n   verify_asset_supplies(db);\n}\n\n\nvoid database_fixture_base::enable_fees()\n{\n   db.modify(global_property_id_type()(db), [](global_property_object& gpo)\n   {\n      gpo.parameters.get_mutable_fees() = fee_schedule::get_default();\n   });\n}\n\nvoid database_fixture_base::upgrade_to_lifetime_member(account_id_type account)\n{\n   upgrade_to_lifetime_member(account(db));\n}\n\nvoid database_fixture_base::upgrade_to_lifetime_member( const account_object& account )\n{\n   try\n   {\n      account_upgrade_operation op;\n      op.account_to_upgrade = account.get_id();\n      op.upgrade_to_lifetime_member = true;\n      op.fee = db.get_global_properties().parameters.get_current_fees().calculate_fee(op);\n      trx.operations = {op};\n      PUSH_TX(db, trx, ~0);\n      FC_ASSERT( op.account_to_upgrade(db).is_lifetime_member() );\n      trx.clear();\n      verify_asset_supplies(db);\n   }\n   FC_CAPTURE_AND_RETHROW((account))\n}\n\nvoid database_fixture_base::upgrade_to_annual_member(account_id_type account)\n{\n   upgrade_to_annual_member(account(db));\n}\n\nvoid database_fixture_base::upgrade_to_annual_member(const account_object& account)\n{\n   try {\n      account_upgrade_operation op;\n      op.account_to_upgrade = account.get_id();\n      op.fee = db.get_global_properties().parameters.get_current_fees().calculate_fee(op);\n      trx.operations = {op};\n      PUSH_TX(db, trx, ~0);\n      FC_ASSERT( op.account_to_upgrade(db).is_member(db.head_block_time()) );\n      trx.clear();\n      verify_asset_supplies(db);\n   } FC_CAPTURE_AND_RETHROW((account))\n}\n\nvoid database_fixture_base::print_market( const string& syma, const string& symb )const\n{\n   const auto& limit_idx = db.get_index_type<limit_order_index>();\n   const auto& price_idx = limit_idx.indices().get<by_price>();\n\n   cerr << std::fixed;\n   cerr.precision(5);\n   cerr << std::setw(10) << std::left  << \"NAME\"      << \" \";\n   cerr << std::setw(16) << std::right << \"FOR SALE\"  << \" \";\n   cerr << std::setw(16) << std::right << \"FOR WHAT\"  << \" \";\n   cerr << std::setw(10) << std::right << \"PRICE (S/W)\"   << \" \";\n   cerr << std::setw(10) << std::right << \"1/PRICE (W/S)\" << \"\\n\";\n   cerr << string(70, '=') << std::endl;\n   auto cur = price_idx.begin();\n   while( cur != price_idx.end() )\n   {\n      cerr << std::setw( 10 ) << std::left   << cur->seller(db).name << \" \";\n      cerr << std::setw( 10 ) << std::right  << cur->for_sale.value << \" \";\n      cerr << std::setw( 5 )  << std::left   << cur->amount_for_sale().asset_id(db).symbol << \" \";\n      cerr << std::setw( 10 ) << std::right  << cur->amount_to_receive().amount.value << \" \";\n      cerr << std::setw( 5 )  << std::left   << cur->amount_to_receive().asset_id(db).symbol << \" \";\n      cerr << std::setw( 10 ) << std::right  << cur->sell_price.to_real() << \" \";\n      cerr << std::setw( 10 ) << std::right  << (~cur->sell_price).to_real() << \" \";\n      cerr << \"\\n\";\n      ++cur;\n   }\n}\n\nstring database_fixture_base::pretty( const asset& a )const\n{\n  std::stringstream ss;\n  ss << a.amount.value << \" \";\n  ss << a.asset_id(db).symbol;\n  return ss.str();\n}\n\nvoid database_fixture_base::print_limit_order( const limit_order_object& cur )const\n{\n  std::cout << std::setw(10) << cur.seller(db).name << \" \";\n  std::cout << std::setw(10) << \"LIMIT\" << \" \";\n  std::cout << std::setw(16) << pretty( cur.amount_for_sale() ) << \" \";\n  std::cout << std::setw(16) << pretty( cur.amount_to_receive() ) << \" \";\n  std::cout << std::setw(16) << cur.sell_price.to_real() << \" \";\n}\n\nvoid database_fixture_base::print_call_orders()const\n{\n  cout << std::fixed;\n  cout.precision(5);\n  cout << std::setw(10) << std::left  << \"NAME\"      << \" \";\n  cout << std::setw(10) << std::right << \"TYPE\"      << \" \";\n  cout << std::setw(16) << std::right << \"DEBT\"  << \" \";\n  cout << std::setw(16) << std::right << \"COLLAT\"  << \" \";\n  cout << std::setw(16) << std::right << \"CALL PRICE(D/C)\"     << \" \";\n  cout << std::setw(16) << std::right << \"~CALL PRICE(C/D)\"     << \" \";\n  cout << std::setw(16) << std::right << \"SWAN(D/C)\"     << \" \";\n  cout << std::setw(16) << std::right << \"SWAN(C/D)\"     << \"\\n\";\n  cout << string(70, '=');\n\n  for( const call_order_object& o : db.get_index_type<call_order_index>().indices() )\n  {\n     std::cout << \"\\n\";\n     cout << std::setw( 10 ) << std::left   << o.borrower(db).name << \" \";\n     cout << std::setw( 16 ) << std::right  << pretty( o.get_debt() ) << \" \";\n     cout << std::setw( 16 ) << std::right  << pretty( o.get_collateral() ) << \" \";\n     cout << std::setw( 16 ) << std::right  << o.call_price.to_real() << \" \";\n     cout << std::setw( 16 ) << std::right  << (~o.call_price).to_real() << \" \";\n     cout << std::setw( 16 ) << std::right  << (o.get_debt()/o.get_collateral()).to_real() << \" \";\n     cout << std::setw( 16 ) << std::right  << (~(o.get_debt()/o.get_collateral())).to_real() << \" \";\n  }\n     std::cout << \"\\n\";\n}\n\nvoid database_fixture_base::print_joint_market( const string& syma, const string& symb )const\n{\n  cout << std::fixed;\n  cout.precision(5);\n\n  cout << std::setw(10) << std::left  << \"NAME\"      << \" \";\n  cout << std::setw(10) << std::right << \"TYPE\"      << \" \";\n  cout << std::setw(16) << std::right << \"FOR SALE\"  << \" \";\n  cout << std::setw(16) << std::right << \"FOR WHAT\"  << \" \";\n  cout << std::setw(16) << std::right << \"PRICE (S/W)\" << \"\\n\";\n  cout << string(70, '=');\n\n  const auto& limit_idx = db.get_index_type<limit_order_index>();\n  const auto& limit_price_idx = limit_idx.indices().get<by_price>();\n\n  auto limit_itr = limit_price_idx.begin();\n  while( limit_itr != limit_price_idx.end() )\n  {\n     std::cout << std::endl;\n     print_limit_order( *limit_itr );\n     ++limit_itr;\n  }\n}\n\nint64_t database_fixture_base::get_balance( account_id_type account, asset_id_type a )const\n{\n  return db.get_balance(account, a).amount.value;\n}\n\nint64_t database_fixture_base::get_balance( const account_object& account, const asset_object& a )const\n{\n  return db.get_balance(account.get_id(), a.get_id()).amount.value;\n}\n\nint64_t database_fixture_base::get_market_fee_reward( account_id_type account_id, asset_id_type asset_id)const\n{\n   return db.get_market_fee_vesting_balance(account_id, asset_id).amount.value;\n}\n\nint64_t database_fixture_base::get_market_fee_reward( const account_object& account, const asset_object& asset )const\n{\n  return get_market_fee_reward(account.get_id(), asset.get_id());\n}\n\nvector< operation_history_object > database_fixture_base::get_operation_history( account_id_type account_id )const\n{\n   vector< operation_history_object > result;\n   const auto& stats = account_id(db).statistics(db);\n   if(stats.most_recent_op == account_history_id_type())\n      return result;\n\n   const account_history_object* node = &stats.most_recent_op(db);\n   while( true )\n   {\n      result.push_back( node->operation_id(db) );\n      if(node->next == account_history_id_type())\n         break;\n      node = db.find(node->next);\n   }\n   return result;\n}\n\nvector< graphene::market_history::order_history_object > database_fixture_base::get_market_order_history( asset_id_type a, asset_id_type b )const\n{\n   const auto& history_idx = db.get_index_type<graphene::market_history::history_index>().indices().get<graphene::market_history::by_key>();\n   graphene::market_history::history_key hkey;\n   if( a > b ) std::swap(a,b);\n   hkey.base = a;\n   hkey.quote = b;\n   hkey.sequence = std::numeric_limits<int64_t>::min();\n   auto itr = history_idx.lower_bound( hkey );\n   vector<graphene::market_history::order_history_object> result;\n   while( itr != history_idx.end())\n   {\n       result.push_back( *itr );\n       ++itr;\n   }\n   return result;\n}\n\nflat_map< uint64_t, graphene::chain::fee_parameters > database_fixture_base::get_htlc_fee_parameters()\n{\n   flat_map<uint64_t, graphene::chain::fee_parameters> ret_val;\n\n   htlc_create_operation::fee_params_t create_param;\n   create_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   create_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   ret_val[((operation)htlc_create_operation()).which()] = create_param;\n\n   htlc_redeem_operation::fee_params_t redeem_param;\n   redeem_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   redeem_param.fee_per_kb = 2 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   ret_val[((operation)htlc_redeem_operation()).which()] = redeem_param;\n\n   htlc_extend_operation::fee_params_t extend_param;\n   extend_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   extend_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   ret_val[((operation)htlc_extend_operation()).which()] = extend_param;\n\n   // set the transfer kb fee to something other than default, to verify we're looking\n   // at the correct fee\n   transfer_operation::fee_params_t transfer_param;\n   transfer_param.price_per_kbyte *= 2;\n   ret_val[ ((operation)transfer_operation()).which() ] = transfer_param;\n\n   return ret_val;\n}\n\nvoid database_fixture_base::set_htlc_committee_parameters()\n{\n   // htlc fees\n   // get existing fee_schedule\n   const chain_parameters& existing_params = db.get_global_properties().parameters;\n   const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees);\n   // create a new fee_shedule\n   std::shared_ptr<fee_schedule_type> new_fee_schedule = std::make_shared<fee_schedule_type>();\n   new_fee_schedule->scale = GRAPHENE_100_PERCENT;\n   // replace the old with the new\n   flat_map<uint64_t, graphene::chain::fee_parameters> htlc_fees = get_htlc_fee_parameters();\n   for(auto param : existing_fee_schedule.parameters)\n   {\n      auto itr = htlc_fees.find(param.which());\n      if (itr == htlc_fees.end()) {\n         // Only define fees for operations which are already forked in!\n         if (hardfork_visitor(db.head_block_time()).visit(param.which()))\n            new_fee_schedule->parameters.insert(param);\n      } else {\n         new_fee_schedule->parameters.insert( (*itr).second);\n      }\n   }\n   // htlc parameters\n   proposal_create_operation cop = proposal_create_operation::committee_proposal(\n         db.get_global_properties().parameters, db.head_block_time());\n   cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n   cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n   committee_member_update_global_parameters_operation uop;\n   graphene::chain::htlc_options new_params;\n   new_params.max_preimage_size = 19200;\n   new_params.max_timeout_secs = 60 * 60 * 24 * 28;\n   uop.new_parameters.extensions.value.updatable_htlc_options = new_params;\n   uop.new_parameters.current_fees = new_fee_schedule;\n   cop.proposed_ops.emplace_back(uop);\n\n   trx.operations.clear();\n   trx.operations.push_back(cop);\n   graphene::chain::processed_transaction proc_trx = db.push_transaction(trx);\n   trx.clear();\n   proposal_id_type good_proposal_id { proc_trx.operation_results[0].get<object_id_type>() };\n\n   proposal_update_operation puo;\n   puo.proposal = good_proposal_id;\n   puo.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n   puo.key_approvals_to_add.emplace( init_account_priv_key.get_public_key() );\n   trx.operations.push_back(puo);\n   sign( trx, init_account_priv_key );\n   db.push_transaction(trx);\n   trx.clear();\n\n   generate_blocks( good_proposal_id( db ).expiration_time + 5 );\n   generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n   generate_block();   // get the maintenance skip slots out of the way\n\n}\n\nnamespace test {\n\nvoid set_expiration( const database& db, transaction& tx )\n{\n   const chain_parameters& params = db.get_global_properties().parameters;\n   tx.set_reference_block(db.head_block_id());\n   tx.set_expiration( db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3 ) );\n   return;\n}\n\nbool _push_block( database& db, const signed_block& b, uint32_t skip_flags /* = 0 */ )\n{\n   return db.push_block( b, skip_flags);\n}\n\nprocessed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags /* = 0 */ )\n{ try {\n   auto pt = db.push_transaction( precomputable_transaction(tx), skip_flags );\n   database_fixture_base::verify_asset_supplies(db);\n   return pt;\n} FC_CAPTURE_AND_RETHROW((tx)) }\n\n} // graphene::chain::test\n\n} } // graphene::chain\n"
  },
  {
    "path": "tests/common/database_fixture.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <fc/io/json.hpp>\n\n#include <boost/filesystem/path.hpp>\n#include <boost/program_options.hpp>\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/protocol/types.hpp>\n#include <graphene/protocol/market.hpp>\n\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/operation_history_object.hpp>\n#include <graphene/chain/database.hpp>\n#include <graphene/app/application.hpp>\n#include <graphene/market_history/market_history_plugin.hpp>\n#include <graphene/utilities/tempdir.hpp>\n\n#include <iostream>\n\n#include \"program_options_util.hpp\"\n\nusing namespace graphene::db;\n\nextern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP;\n\n#define PUSH_TX \\\n   graphene::chain::test::_push_transaction\n\n#define PUSH_BLOCK \\\n   graphene::chain::test::_push_block\n\n// See below\n#define REQUIRE_OP_VALIDATION_SUCCESS( op, field, value ) \\\n{ \\\n   const auto temp = op.field; \\\n   op.field = value; \\\n   op.validate(); \\\n   op.field = temp; \\\n}\n#define REQUIRE_OP_EVALUATION_SUCCESS( op, field, value ) \\\n{ \\\n   const auto temp = op.field; \\\n   op.field = value; \\\n   trx.operations.back() = op; \\\n   op.field = temp; \\\n   db.push_transaction( trx, ~0 ); \\\n}\n\n#define GRAPHENE_REQUIRE_THROW( expr, exc_type )          \\\n{                                                         \\\n   std::string req_throw_info = fc::json::to_string(      \\\n      fc::mutable_variant_object()                        \\\n      (\"source_file\", __FILE__)                           \\\n      (\"source_lineno\", __LINE__)                         \\\n      (\"expr\", #expr)                                     \\\n      (\"exc_type\", #exc_type)                             \\\n      );                                                  \\\n   if( fc::enable_record_assert_trip )                    \\\n      std::cout << \"GRAPHENE_REQUIRE_THROW begin \"        \\\n         << req_throw_info << std::endl;                  \\\n   BOOST_REQUIRE_THROW( expr, exc_type );                 \\\n   if( fc::enable_record_assert_trip )                    \\\n      std::cout << \"GRAPHENE_REQUIRE_THROW end \"          \\\n         << req_throw_info << std::endl;                  \\\n}\n\n#define GRAPHENE_CHECK_THROW( expr, exc_type )            \\\n{                                                         \\\n   std::string req_throw_info = fc::json::to_string(      \\\n      fc::mutable_variant_object()                        \\\n      (\"source_file\", __FILE__)                           \\\n      (\"source_lineno\", __LINE__)                         \\\n      (\"expr\", #expr)                                     \\\n      (\"exc_type\", #exc_type)                             \\\n      );                                                  \\\n   if( fc::enable_record_assert_trip )                    \\\n      std::cout << \"GRAPHENE_CHECK_THROW begin \"          \\\n         << req_throw_info << std::endl;                  \\\n   BOOST_CHECK_THROW( expr, exc_type );                   \\\n   if( fc::enable_record_assert_trip )                    \\\n      std::cout << \"GRAPHENE_CHECK_THROW end \"            \\\n         << req_throw_info << std::endl;                  \\\n}\n\n#define REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, exc_type ) \\\n{ \\\n   const auto temp = op.field; \\\n   op.field = value; \\\n   GRAPHENE_REQUIRE_THROW( op.validate(), exc_type ); \\\n   op.field = temp; \\\n}\n#define REQUIRE_OP_VALIDATION_FAILURE( op, field, value ) \\\n   REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, fc::exception )\n\n#define REQUIRE_EXCEPTION_WITH_TEXT(op, exc_text)                 \\\n{                                                                 \\\n   try                                                            \\\n   {                                                              \\\n      op;                                                         \\\n      BOOST_FAIL(std::string(\"Expected an exception with \\\"\") +   \\\n         std::string(exc_text) +                                  \\\n         std::string(\"\\\" but none thrown\"));                      \\\n   }                                                              \\\n   catch (fc::exception& ex)                                      \\\n   {                                                              \\\n      std::string what = ex.to_string(                            \\\n            fc::log_level(fc::log_level::all));                   \\\n      if (what.find(exc_text) == std::string::npos)               \\\n      {                                                           \\\n         BOOST_FAIL( std::string(\"Expected \\\"\") +                 \\\n            std::string(exc_text) +                               \\\n            std::string(\"\\\" but got \\\"\") +                        \\\n            std::string(what) );                                  \\\n      }                                                           \\\n   }                                                              \\\n}                                                                 \\\n\n#define REQUIRE_THROW_WITH_VALUE_2(op, field, value, exc_type) \\\n{ \\\n   auto bak = op.field; \\\n   op.field = value; \\\n   trx.operations.back() = op; \\\n   op.field = bak; \\\n   GRAPHENE_REQUIRE_THROW(db.push_transaction(trx, ~0), exc_type); \\\n}\n\n#define REQUIRE_THROW_WITH_VALUE( op, field, value ) \\\n   REQUIRE_THROW_WITH_VALUE_2( op, field, value, fc::exception )\n\n///This simply resets v back to its default-constructed value. Requires v to have a working assingment operator and\n/// default constructor.\n#define RESET(v) v = decltype(v)()\n///This allows me to build consecutive test cases. It's pretty ugly, but it works well enough for unit tests.\n/// i.e. This allows a test on update_account to begin with the database at the end state of create_account.\n#define INVOKE(test) ((struct test*)this)->test_method(); trx.clear()\n\n#define PREP_ACTOR(name) \\\n   fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name));   \\\n   graphene::chain::public_key_type name ## _public_key = name ## _private_key.get_public_key(); \\\n   BOOST_CHECK( name ## _public_key != public_key_type() );\n\n#define ACTOR(name) \\\n   PREP_ACTOR(name) \\\n   const auto name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \\\n   graphene::chain::account_id_type name ## _id = name.get_id(); (void)name ## _id;\n\n#define GET_ACTOR(name) \\\n   fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \\\n   const account_object& name = get_account(BOOST_PP_STRINGIZE(name)); \\\n   graphene::chain::account_id_type name ## _id = name.get_id(); \\\n   (void)name ##_id\n\n#define ACTORS_IMPL(r, data, elem) ACTOR(elem)\n#define ACTORS(names) BOOST_PP_SEQ_FOR_EACH(ACTORS_IMPL, ~, names)\n\n#define INITIAL_WITNESS_COUNT (9u)\n#define INITIAL_COMMITTEE_MEMBER_COUNT INITIAL_WITNESS_COUNT\n\nnamespace graphene { namespace chain {\n\nclass clearable_block : public signed_block {\npublic:\n   /** @brief Clears internal cached values like ID, signing key, Merkle root etc. */\n   void clear();\n};\n\nnamespace test {\n/// set a reasonable expiration time for the transaction\nvoid set_expiration( const database& db, transaction& tx );\n\nbool _push_block( database& db, const signed_block& b, uint32_t skip_flags = 0 );\nprocessed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags = 0 );\n} // namespace test\n\nstruct database_fixture_base {\n   // the reason we use an app is to exercise the indexes of built-in\n   //   plugins\n   graphene::app::application app;\n   genesis_state_type genesis_state;\n   chain::database &db;\n   signed_transaction trx;\n   public_key_type committee_key;\n   account_id_type committee_account;\n   const fc::ecc::private_key private_key;\n   const fc::ecc::private_key init_account_priv_key;\n   const public_key_type init_account_pub_key;\n\n   fc::temp_directory data_dir;\n   bool skip_key_index_test = false;\n   uint32_t anon_acct_count;\n   bool hf1270 = false;\n   bool hf2467 = false; // Note: used by hf core-2281 too, assuming hf core-2281 and core-2467 occur at the same time\n   bool hf2481 = false;\n   bool bsip77 = false;\n   bool hf2595 = false;\n\n   string es_index_prefix; ///< Index prefix for elasticsearch plugin\n   string es_obj_index_prefix; ///< Index prefix for es_objects plugin\n\n   const std::string current_test_name;\n   const std::string current_suite_name;\n\n   database_fixture_base();\n   virtual ~database_fixture_base();\n\n   static void init_genesis( database_fixture_base& fixture );\n   static std::shared_ptr<boost::program_options::variables_map> init_options( database_fixture_base& fixture );\n\n   static fc::ecc::private_key generate_private_key(string seed);\n   string generate_anon_acct_name();\n   static void verify_asset_supplies( const database& db );\n   void vote_for_committee_and_witnesses(uint16_t num_committee, uint16_t num_witness);\n   signed_block generate_block(uint32_t skip = ~0,\n                               const fc::ecc::private_key& key = generate_private_key(\"null_key\"),\n                               int miss_blocks = 0);\n\n   /**\n    * @brief Generates block_count blocks\n    * @param block_count number of blocks to generate\n    */\n   void generate_blocks(uint32_t block_count);\n\n   /**\n    * @brief Generates blocks until the head block time matches or exceeds timestamp\n    * @param timestamp target time to generate blocks until\n    * @return number of blocks generated\n    */\n   uint32_t generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = true, uint32_t skip = ~0);\n\n   account_create_operation make_account(\n      const std::string& name = \"nathan\",\n      public_key_type = public_key_type()\n      );\n\n   account_create_operation make_account(\n      const std::string& name,\n      const account_object& registrar,\n      const account_object& referrer,\n      uint16_t referrer_percent = 100,\n      public_key_type key = public_key_type()\n      );\n\n   void force_global_settle(const asset_object& what, const price& p);\n   operation_result force_settle(account_id_type who, asset what)\n   { return force_settle(who(db), what); }\n   operation_result force_settle(const account_object& who, asset what);\n   void update_feed_producers(asset_id_type mia, flat_set<account_id_type> producers)\n   { update_feed_producers(mia(db), producers); }\n   void update_feed_producers(const asset_object& mia, flat_set<account_id_type> producers);\n   void publish_feed(asset_id_type mia, account_id_type by, const price_feed& f, const optional<uint16_t> icr = {})\n   { publish_feed(mia(db), by(db), f, icr); }\n\n   /***\n    * @brief helper method to add a price feed\n    *\n    * Adds a price feed for asset2, pushes the transaction, and generates a block\n    *\n    * @param publisher who is publishing the feed\n    * @param asset1 the base asset\n    * @param amount1 the amount of the base asset\n    * @param asset2 the quote asset\n    * @param amount2 the amount of the quote asset\n    * @param core_id id of core (helps with core_exchange_rate)\n    * @param icr initial collateral ratio\n    */\n   void publish_feed(const account_id_type& publisher,\n         const asset_id_type& asset1, int64_t amount1,\n         const asset_id_type& asset2, int64_t amount2,\n         const asset_id_type& core_id, const optional<uint16_t> icr = {});\n\n   void publish_feed( const asset_object& mia, const account_object& by, const price_feed& f,\n                      const optional<uint16_t> icr = {} );\n\n   const call_order_object* borrow( account_id_type who, asset what, asset collateral,\n                                    optional<uint16_t> target_cr = {} )\n   { return borrow(who(db), what, collateral, target_cr); }\n   const call_order_object* borrow( const account_object& who, asset what, asset collateral,\n                                    optional<uint16_t> target_cr = {} );\n   void cover(account_id_type who, asset what, asset collateral_freed,\n                                    optional<uint16_t> target_cr = {} )\n   { cover(who(db), what, collateral_freed, target_cr); }\n   void cover(const account_object& who, asset what, asset collateral_freed,\n                                    optional<uint16_t> target_cr = {} );\n   void bid_collateral(const account_object& who, const asset& to_bid, const asset& to_cover);\n\n   const asset_object& get_asset( const string& symbol )const;\n   const account_object& get_account( const string& name )const;\n   asset_create_operation make_bitasset( const string& name,\n                                       account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT,\n                                       uint16_t market_fee_percent = 100 /*1%*/,\n                                       uint16_t flags = charge_market_fee,\n                                       uint16_t precision = 2,\n                                       asset_id_type backing_asset = {},\n                                       share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY,\n                                       optional<uint16_t> initial_cr = {},\n                                       optional<uint16_t> margin_call_fee_ratio = {} );\n   const asset_object& create_bitasset(const string& name,\n                                       account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT,\n                                       uint16_t market_fee_percent = 100 /*1%*/,\n                                       uint16_t flags = charge_market_fee,\n                                       uint16_t precision = 2,\n                                       asset_id_type backing_asset = {},\n                                       share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY,\n                                       optional<uint16_t> initial_cr = {},\n                                       optional<uint16_t> margin_call_fee_ratio = {} );\n   const asset_object& create_prediction_market(const string& name,\n                                       account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT,\n                                       uint16_t market_fee_percent = 100 /*1%*/,\n                                       uint16_t flags = charge_market_fee,\n                                       uint16_t precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS,\n                                       asset_id_type backing_asset = {});\n   const asset_object& create_user_issued_asset( const string& name );\n   const asset_object& create_user_issued_asset( const string& name,\n                                                 const account_object& issuer,\n                                                 uint16_t flags,\n                                                 const price& core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)),\n                                                 uint8_t precision = 2 /* traditional precision for tests */,\n                                                 uint16_t market_fee_percent = 0,\n                                                 additional_asset_options_t options = additional_asset_options_t());\n   void issue_uia( const account_object& recipient, asset amount );\n   void issue_uia( account_id_type recipient_id, asset amount );\n   void reserve_asset( account_id_type account, asset amount );\n\n   const account_object& create_account(\n      const string& name,\n      const public_key_type& key = public_key_type()\n      );\n\n   const account_object& create_account(\n      const string& name,\n      const account_object& registrar,\n      const account_object& referrer,\n      uint16_t referrer_percent = 100,\n      const public_key_type& key = public_key_type()\n      );\n\n   const account_object& create_account(\n      const string& name,\n      const private_key_type& key,\n      const account_id_type& registrar_id = account_id_type(),\n      const account_id_type& referrer_id = account_id_type(),\n      uint16_t referrer_percent = 100\n      );\n\n   const committee_member_object& create_committee_member( const account_object& owner );\n   const witness_object& create_witness(account_id_type owner,\n                                        const fc::ecc::private_key& signing_private_key = generate_private_key(\"null_key\"),\n                                        uint32_t skip_flags = ~0);\n   const witness_object& create_witness(const account_object& owner,\n                                        const fc::ecc::private_key& signing_private_key = generate_private_key(\"null_key\"),\n                                        uint32_t skip_flags = ~0);\n   const worker_object& create_worker(account_id_type owner, const share_type daily_pay = 1000, const fc::microseconds& duration = fc::days(2));\n   template<typename T>\n   proposal_create_operation make_proposal_create_op( const T& op, account_id_type proposer = GRAPHENE_TEMP_ACCOUNT,\n                                                      uint32_t timeout = 300, optional<uint32_t> review_period = {} ) const\n   {\n      proposal_create_operation cop;\n      cop.fee_paying_account = proposer;\n      cop.expiration_time = db.head_block_time() + timeout;\n      cop.review_period_seconds = review_period;\n      cop.proposed_ops.emplace_back( op );\n      for( auto& o : cop.proposed_ops ) db.current_fee_schedule().set_fee(o.op);\n      return cop;\n   }\n   template<typename T>\n   const proposal_object& propose( const T& op, account_id_type proposer = GRAPHENE_TEMP_ACCOUNT,\n                                   uint32_t timeout = 300, uint32_t review_period = 0 )\n   {\n      proposal_create_operation cop = make_proposal_create_op( op, proposer, timeout, review_period );\n      trx.operations.clear();\n      trx.operations.push_back( cop );\n      for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n      trx.validate();\n      test::set_expiration( db, trx );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const operation_result& op_result = ptx.operation_results.front();\n      trx.operations.clear();\n      verify_asset_supplies(db);\n      return db.get<proposal_object>( op_result.get<object_id_type>() );\n   }\n   uint64_t fund( const account_object& account, const asset& amount = asset(500000) );\n   digest_type digest( const transaction& tx );\n   void sign( signed_transaction& trx, const fc::ecc::private_key& key );\n   limit_order_create_operation make_limit_order_create_op(\n                                                const account_id_type& user, const asset& amount, const asset& recv,\n                                                const time_point_sec& order_expiration = time_point_sec::maximum(),\n                                                const optional< vector< limit_order_auto_action > >& on_fill = {} ) const;\n   const limit_order_object* create_sell_order( const account_id_type& user, const asset& amount, const asset& recv,\n                                                const time_point_sec& order_expiration = time_point_sec::maximum(),\n                                                const price& fee_core_exchange_rate = price::unit_price(),\n                                                const optional< vector< limit_order_auto_action > >& on_fill = {} );\n   const limit_order_object* create_sell_order( const account_object& user, const asset& amount, const asset& recv,\n                                                const time_point_sec& order_expiration = time_point_sec::maximum(),\n                                                const price& fee_core_exchange_rate = price::unit_price(),\n                                                const optional< vector< limit_order_auto_action > >& on_fill = {} );\n   limit_order_update_operation make_limit_order_update_op(\n                           const account_id_type& seller_id,\n                           const limit_order_id_type& order_id,\n                           const fc::optional<price>& new_price = {},\n                           const fc::optional<asset>& delta_amount = {},\n                           const fc::optional<time_point_sec>& new_expiration = {},\n                           const optional< vector< limit_order_auto_action > >& on_fill = {} )const;\n   void update_limit_order(const limit_order_object& order,\n                           const fc::optional<price>& new_price = {},\n                           const fc::optional<asset>& delta_amount = {},\n                           const fc::optional<time_point_sec>& new_expiration = {},\n                           const price& fee_core_exchange_rate = price::unit_price(),\n                           const optional< vector< limit_order_auto_action > >& on_fill = {} );\n   void update_limit_order(const limit_order_id_type& order_id,\n                           const fc::optional<price>& new_price = {},\n                           const fc::optional<asset>& delta_amount = {},\n                           const fc::optional<time_point_sec>& new_expiration = {},\n                           const price& fee_core_exchange_rate = price::unit_price(),\n                           const optional< vector< limit_order_auto_action > >& on_fill = {} );\n   asset cancel_limit_order( const limit_order_object& order );\n   void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() );\n   void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() );\n   void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount );\n\n   // Tickets\n   ticket_create_operation make_ticket_create_op( account_id_type account, ticket_type type,\n                                                  const asset& amount )const;\n   const ticket_object& create_ticket( account_id_type account, ticket_type type, const asset& amount );\n   ticket_update_operation make_ticket_update_op( const ticket_object& ticket, ticket_type type,\n                                                  const optional<asset>& amount )const;\n   generic_operation_result update_ticket( const ticket_object& ticket, ticket_type type,\n                                           const optional<asset>& amount );\n\n   // Liquidity Pools\n   liquidity_pool_create_operation make_liquidity_pool_create_op( account_id_type account, asset_id_type asset_a,\n                                                  asset_id_type asset_b, asset_id_type share_asset,\n                                                  uint16_t taker_fee_percent, uint16_t withdrawal_fee_percent )const;\n   const liquidity_pool_object& create_liquidity_pool( account_id_type account, asset_id_type asset_a,\n                                                  asset_id_type asset_b, asset_id_type share_asset,\n                                                  uint16_t taker_fee_percent, uint16_t withdrawal_fee_percent );\n   liquidity_pool_delete_operation make_liquidity_pool_delete_op( account_id_type account,\n                                                  liquidity_pool_id_type pool )const;\n   generic_operation_result delete_liquidity_pool( account_id_type account, liquidity_pool_id_type pool );\n   liquidity_pool_update_operation make_liquidity_pool_update_op( account_id_type account,\n                                                  liquidity_pool_id_type pool,\n                                                  optional<uint16_t> taker_fee_percent,\n                                                  optional<uint16_t> withdrawal_fee_percent )const;\n   void update_liquidity_pool( account_id_type account, liquidity_pool_id_type pool,\n                                                  optional<uint16_t> taker_fee_percent,\n                                                  optional<uint16_t> withdrawal_fee_percent );\n   liquidity_pool_deposit_operation make_liquidity_pool_deposit_op( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_a,\n                                                  const asset& amount_b )const;\n   generic_exchange_operation_result deposit_to_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_a,\n                                                  const asset& amount_b );\n   liquidity_pool_withdraw_operation make_liquidity_pool_withdraw_op( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& share_amount )const;\n   generic_exchange_operation_result withdraw_from_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& share_amount );\n   liquidity_pool_exchange_operation make_liquidity_pool_exchange_op( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_to_sell,\n                                                  const asset& min_to_receive )const;\n   generic_exchange_operation_result exchange_with_liquidity_pool( account_id_type account,\n                                                  liquidity_pool_id_type pool, const asset& amount_to_sell,\n                                                  const asset& min_to_receive );\n\n   // SameT Funds\n   samet_fund_create_operation make_samet_fund_create_op( account_id_type account, asset_id_type asset_type,\n                                                  share_type balance, uint32_t fee_rate )const;\n   const samet_fund_object& create_samet_fund( account_id_type account, asset_id_type asset_type,\n                                                  share_type balance, uint32_t fee_rate );\n   samet_fund_delete_operation make_samet_fund_delete_op( account_id_type account, samet_fund_id_type fund_id )const;\n   asset delete_samet_fund( account_id_type account,  samet_fund_id_type fund_id );\n   samet_fund_update_operation make_samet_fund_update_op( account_id_type account, samet_fund_id_type fund_id,\n                                                  const optional<asset>& delta_amount,\n                                                  const optional<uint32_t>& new_fee_rate )const;\n   void update_samet_fund( account_id_type account, samet_fund_id_type fund_id,\n                                                  const optional<asset>& delta_amount,\n                                                  const optional<uint32_t>& new_fee_rate );\n   samet_fund_borrow_operation make_samet_fund_borrow_op( account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& borrow_amount )const;\n   void borrow_from_samet_fund( account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& borrow_amount );\n   samet_fund_repay_operation make_samet_fund_repay_op( account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& repay_amount, const asset& fund_fee )const;\n   void repay_to_samet_fund( account_id_type account, samet_fund_id_type fund_id,\n                                                  const asset& repay_amount, const asset& fund_fee );\n\n   // credit offer / deal\n   credit_offer_create_operation make_credit_offer_create_op(\n                                       account_id_type account, asset_id_type asset_type,\n                                       share_type balance, uint32_t fee_rate, uint32_t max_duration,\n                                       share_type min_amount, bool enabled, time_point_sec disable_time,\n                                       flat_map<asset_id_type, price>          acceptable_collateral,\n                                       flat_map<account_id_type, share_type>   acceptable_borrowers )const;\n   const credit_offer_object& create_credit_offer(\n                                       account_id_type account, asset_id_type asset_type,\n                                       share_type balance, uint32_t fee_rate, uint32_t max_duration,\n                                       share_type min_amount, bool enabled, time_point_sec disable_time,\n                                       flat_map<asset_id_type, price>          acceptable_collateral,\n                                       flat_map<account_id_type, share_type>   acceptable_borrowers );\n   credit_offer_delete_operation make_credit_offer_delete_op( account_id_type account,\n                                                  credit_offer_id_type offer_id )const;\n   asset delete_credit_offer( account_id_type account,  credit_offer_id_type offer_id );\n   credit_offer_update_operation make_credit_offer_update_op(\n                                       account_id_type account, credit_offer_id_type offer_id,\n                                       const optional<asset>& delta_amount,\n                                       const optional<uint32_t>& new_fee_rate,\n                                       const optional<uint32_t>& max_duration_seconds,\n                                       const optional<share_type>& min_deal_amount,\n                                       const optional<bool>& enabled,\n                                       const optional<time_point_sec>& auto_disable_time,\n                                       const optional<flat_map<asset_id_type, price>>& acceptable_collateral,\n                                       const optional<flat_map<account_id_type, share_type>>& acceptable_borrowers\n                                    )const;\n   void update_credit_offer( account_id_type account, credit_offer_id_type offer_id,\n                                       const optional<asset>& delta_amount,\n                                       const optional<uint32_t>& new_fee_rate,\n                                       const optional<uint32_t>& max_duration_seconds,\n                                       const optional<share_type>& min_deal_amount,\n                                       const optional<bool>& enabled,\n                                       const optional<time_point_sec>& auto_disable_time,\n                                       const optional<flat_map<asset_id_type, price>>& acceptable_collateral,\n                                       const optional<flat_map<account_id_type, share_type>>& acceptable_borrowers );\n   credit_offer_accept_operation make_credit_offer_accept_op(\n                                       account_id_type account, credit_offer_id_type offer_id,\n                                       const asset& borrow_amount, const asset& collateral,\n                                       uint32_t max_fee_rate = GRAPHENE_FEE_RATE_DENOM,\n                                       uint32_t min_duration = 0, const optional<uint8_t>& auto_repay = {} )const;\n   const credit_deal_object& borrow_from_credit_offer(\n                                       account_id_type account, credit_offer_id_type offer_id,\n                                       const asset& borrow_amount, const asset& collateral,\n                                       uint32_t max_fee_rate = GRAPHENE_FEE_RATE_DENOM,\n                                       uint32_t min_duration = 0, const optional<uint8_t>& auto_repay = {} );\n   credit_deal_repay_operation make_credit_deal_repay_op(\n                                       account_id_type account, credit_deal_id_type deal_id,\n                                       const asset& repay_amount, const asset& credit_fee )const;\n   extendable_operation_result_dtl repay_credit_deal( account_id_type account, credit_deal_id_type deal_id,\n                                       const asset& repay_amount, const asset& credit_fee );\n   credit_deal_update_operation make_credit_deal_update_op(\n                                       account_id_type account, credit_deal_id_type deal_id,\n                                       uint8_t auto_repay )const;\n   void update_credit_deal( account_id_type account, credit_deal_id_type deal_id,\n                                       uint8_t auto_repay );\n\n   /**\n    * NOTE: This modifies the database directly. You will probably have to call this each time you\n    * finish creating a block\n    */\n   void enable_fees();\n   void change_fees( const fee_parameters::flat_set_type& new_params, uint32_t new_scale = 0 );\n   void upgrade_to_lifetime_member( account_id_type account );\n   void upgrade_to_lifetime_member( const account_object& account );\n   void upgrade_to_annual_member( account_id_type account );\n   void upgrade_to_annual_member( const account_object& account );\n   void print_market( const string& syma, const string& symb )const;\n   string pretty( const asset& a )const;\n   void print_limit_order( const limit_order_object& cur )const;\n   void print_call_orders( )const;\n   void print_joint_market( const string& syma, const string& symb )const;\n   int64_t get_balance( account_id_type account, asset_id_type a )const;\n   int64_t get_balance( const account_object& account, const asset_object& a )const;\n\n   int64_t get_market_fee_reward( account_id_type account, asset_id_type asset )const;\n   int64_t get_market_fee_reward( const account_object& account, const asset_object& asset )const;\n\n   vector< operation_history_object > get_operation_history( account_id_type account_id )const;\n   vector< graphene::market_history::order_history_object > get_market_order_history( asset_id_type a, asset_id_type b )const;\n\n   /****\n    * @brief return htlc fee parameters\n    */\n   flat_map< uint64_t, graphene::chain::fee_parameters > get_htlc_fee_parameters();\n   /****\n    * @brief push through a proposal that sets htlc parameters and fees\n    */\n   void set_htlc_committee_parameters();\n   /****\n    * Hash the preimage and put it in a vector\n    * @param preimage the preimage\n    * @returns a vector that cointains the sha256 hash of the preimage\n    */\n   template<typename H>\n   H hash_it(std::vector<char> preimage)\n   {\n      return H::hash( (char*)preimage.data(), preimage.size() );\n   }\n\n};\n\ntemplate<typename F>\nstruct database_fixture_init : database_fixture_base {\n   database_fixture_init()\n   {\n      F::init( *this );\n\n      asset_id_type mpa1_id(1);\n      BOOST_REQUIRE( mpa1_id(db).is_market_issued() );\n      BOOST_CHECK( mpa1_id(db).bitasset_data(db).asset_id == mpa1_id );\n\n      BOOST_CHECK_EQUAL( account_id_type()(db).creation_block_num, 0 );\n      BOOST_CHECK( account_id_type()(db).creation_time == genesis_state.initial_timestamp );\n\n      BOOST_CHECK_EQUAL( asset_id_type()(db).creation_block_num, 0 );\n      BOOST_CHECK( asset_id_type()(db).creation_time == genesis_state.initial_timestamp );\n\n      BOOST_CHECK_EQUAL( mpa1_id(db).creation_block_num, 0 );\n      BOOST_CHECK( mpa1_id(db).creation_time == genesis_state.initial_timestamp );\n   }\n\n   static void init( database_fixture_init<F>& fixture )\n   { try {\n      fixture.data_dir = fc::temp_directory( graphene::utilities::temp_directory_path() );\n      fc::create_directories( fixture.data_dir.path() );\n      F::init_genesis( fixture );\n      fc::json::save_to_file( fixture.genesis_state, fixture.data_dir.path() / \"genesis.json\" );\n      auto options = F::init_options( fixture );\n      fc::set_option( *options, \"genesis-json\", boost::filesystem::path(fixture.data_dir.path() / \"genesis.json\") );\n      fixture.app.initialize( fixture.data_dir.path(), options );\n      fixture.app.startup();\n\n      fixture.generate_block();\n\n      test::set_expiration( fixture.db, fixture.trx );\n   } FC_LOG_AND_RETHROW() }\n};\n\nstruct database_fixture : database_fixture_init<database_fixture>\n{\n};\n\n} }\n"
  },
  {
    "path": "tests/common/elasticsearch.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"elasticsearch.hpp\"\n\n#include <fc/exception/exception.hpp>\n#include <fc/io/json.hpp>\n#include <fc/time.hpp>\n#include <fc/variant_object.hpp>\n\nstatic size_t curl_write_function(void *contents, size_t size, size_t nmemb, void *userp)\n{\n   ((std::string*)userp)->append((char*)contents, size * nmemb);\n   return size * nmemb;\n}\n\nnamespace graphene { namespace utilities {\n\nbool checkES(ES& es)\n{\n   graphene::utilities::CurlRequest curl_request;\n   curl_request.handler = es.curl;\n   curl_request.url = es.elasticsearch_url + \"_nodes\";\n   curl_request.auth = es.auth;\n   curl_request.type = \"GET\";\n\n   if(doCurl(curl_request).empty())\n      return false;\n   return true;\n}\n\nstd::string getESVersion(ES& es)\n{\n   graphene::utilities::CurlRequest curl_request;\n   curl_request.handler = es.curl;\n   curl_request.url = es.elasticsearch_url;\n   curl_request.auth = es.auth;\n   curl_request.type = \"GET\";\n\n   fc::variant response = fc::json::from_string(doCurl(curl_request));\n\n   return response[\"version\"][\"number\"].as_string();\n}\n\nvoid checkESVersion7OrAbove(ES& es, bool& result) noexcept\n{\n   static const int64_t version_7 = 7;\n   try {\n      const auto es_version = graphene::utilities::getESVersion(es);\n      ilog( \"ES version detected: ${v}\", (\"v\", es_version) );\n      auto dot_pos = es_version.find('.');\n      result = ( std::stoi(es_version.substr(0,dot_pos)) >= version_7 );\n   }\n   catch( ... )\n   {\n      wlog( \"Unable to get ES version, assuming it is 7 or above\" );\n      result = true;\n   }\n}\n\nstd::string simpleQuery(ES& es)\n{\n   graphene::utilities::CurlRequest curl_request;\n   curl_request.handler = es.curl;\n   curl_request.url = es.elasticsearch_url + es.endpoint;\n   curl_request.auth = es.auth;\n   curl_request.type = \"POST\";\n   curl_request.query = es.query;\n\n   return doCurl(curl_request);\n}\n\nbool deleteAll(ES& es)\n{\n   graphene::utilities::CurlRequest curl_request;\n   curl_request.handler = es.curl;\n   curl_request.url = es.elasticsearch_url + es.index_prefix + \"*\";\n   curl_request.auth = es.auth;\n   curl_request.type = \"DELETE\";\n\n   auto curl_response = doCurl(curl_request);\n   if( curl_response.empty() )\n   {\n      wlog( \"Empty ES response\" );\n      return false;\n   }\n\n   // Check errors in response\n   try\n   {\n      fc::variant j = fc::json::from_string(curl_response);\n      if( j.is_object() && j.get_object().contains(\"error\") )\n      {\n         wlog( \"ES returned an error: ${r}\", (\"r\", curl_response) );\n         return false;\n      }\n   }\n   catch( const fc::exception& e )\n   {\n      wlog( \"Error while checking ES response ${r}\", (\"r\", curl_response) );\n      wdump( (e.to_detail_string()) );\n      return false;\n   }\n   return true;\n}\n\nstd::string getEndPoint(ES& es)\n{\n   graphene::utilities::CurlRequest curl_request;\n   curl_request.handler = es.curl;\n   curl_request.url = es.elasticsearch_url + es.endpoint;\n   curl_request.auth = es.auth;\n   curl_request.type = \"GET\";\n\n   return doCurl(curl_request);\n}\n\nstd::string doCurl(CurlRequest& curl)\n{\n   std::string CurlReadBuffer;\n   struct curl_slist *headers = NULL;\n   headers = curl_slist_append(headers, \"Content-Type: application/json\");\n\n   // Note: the variable curl.handler has a long lifetime, it only gets initialized once, then be used many times,\n   //       thus we need to clear old data\n   curl_easy_setopt(curl.handler, CURLOPT_HTTPHEADER, headers);\n   curl_easy_setopt(curl.handler, CURLOPT_URL, curl.url.c_str());\n   curl_easy_setopt(curl.handler, CURLOPT_CUSTOMREQUEST, curl.type.c_str()); // this is OK\n   if(curl.type == \"POST\")\n   {\n      curl_easy_setopt(curl.handler, CURLOPT_HTTPGET, false);\n      curl_easy_setopt(curl.handler, CURLOPT_POST, true);\n      curl_easy_setopt(curl.handler, CURLOPT_POSTFIELDS, curl.query.c_str());\n   }\n   else // GET or DELETE (only these are used in this file)\n   {\n      curl_easy_setopt(curl.handler, CURLOPT_POSTFIELDS, NULL);\n      curl_easy_setopt(curl.handler, CURLOPT_POST, false);\n      curl_easy_setopt(curl.handler, CURLOPT_HTTPGET, true);\n   }\n   curl_easy_setopt(curl.handler, CURLOPT_WRITEFUNCTION, curl_write_function);\n   curl_easy_setopt(curl.handler, CURLOPT_WRITEDATA, (void *)&CurlReadBuffer);\n   curl_easy_setopt(curl.handler, CURLOPT_USERAGENT, \"libcrp/0.1\");\n   if(!curl.auth.empty())\n      curl_easy_setopt(curl.handler, CURLOPT_USERPWD, curl.auth.c_str());\n   curl_easy_perform(curl.handler);\n\n   long code;\n   curl_easy_getinfo( curl.handler, CURLINFO_RESPONSE_CODE, &code );\n\n   if( 200 != code )\n      wlog( \"doCurl response [${code}] ${msg}\", (\"code\", ((int64_t)code))(\"msg\", CurlReadBuffer) );\n\n   return CurlReadBuffer;\n}\n\n} } // graphene::utilities\n"
  },
  {
    "path": "tests/common/elasticsearch.hpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <string>\n#include <vector>\n\n#include <curl/curl.h>\n\nnamespace graphene { namespace utilities {\n\n   class ES {\n      public:\n         CURL *curl;\n         std::vector <std::string> bulk_lines;\n         std::string elasticsearch_url;\n         std::string index_prefix;\n         std::string auth;\n         std::string endpoint;\n         std::string query;\n   };\n\n   class CurlRequest {\n      public:\n         CURL *handler;\n         std::string url;\n         std::string type;\n         std::string auth;\n         std::string query;\n   };\n\n   bool checkES(ES& es);\n   std::string getESVersion(ES& es);\n   void checkESVersion7OrAbove(ES& es, bool& result) noexcept;\n   std::string simpleQuery(ES& es);\n   bool deleteAll(ES& es);\n   std::string getEndPoint(ES& es);\n\n   std::string doCurl(CurlRequest& curl);\n\n} } // graphene::utilities\n"
  },
  {
    "path": "tests/common/genesis_file_util.hpp",
    "content": "#pragma once\n\n#include <boost/filesystem.hpp>\n#include <fc/io/json.hpp>\n#include <graphene/chain/genesis_state.hpp>\n\nnamespace graphene { namespace app { namespace detail {\n   /////////\n   /// @brief forward declaration, using as a hack to generate a genesis.json file\n   /// for testing\n   /////////\n   graphene::chain::genesis_state_type create_example_genesis();\n} } } // graphene::app::detail\n\n/////////\n/// @brief create a genesis_json file\n/// @param directory the directory to place the file \"genesis.json\"\n/// @returns the full path to the file\n////////\nboost::filesystem::path create_genesis_file(fc::temp_directory& directory) {\n   boost::filesystem::path genesis_path = boost::filesystem::path{directory.path().generic_string()} / \"genesis.json\";\n   fc::path genesis_out = genesis_path;\n   graphene::chain::genesis_state_type genesis_state = graphene::app::detail::create_example_genesis();\n\n   /* Work In Progress: Place some accounts in the Genesis file so as to pre-make some accounts to play with\n   std::string test_prefix = \"test\";\n   // helper lambda\n   auto get_test_key = [&]( std::string prefix, uint32_t i ) -> public_key_type\n   {\n      return fc::ecc::private_key::regenerate( fc::sha256::hash( test_prefix + prefix + std::to_string(i) ) ).get_public_key();\n   };\n\n   // create 2 accounts to use\n   for (int i = 1; i <= 2; ++i )\n   {\n      genesis_state_type::initial_account_type dev_account(\n            test_prefix + std::to_string(i),\n            get_test_key(\"owner-\", i),\n            get_test_key(\"active-\", i),\n            false);\n\n      genesis_state.initial_accounts.push_back(dev_account);\n      // give her some coin\n\n   }\n   */\n\n   fc::json::save_to_file(genesis_state, genesis_out);\n   return genesis_path;\n}\n"
  },
  {
    "path": "tests/common/init_unit_test_suite.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <cstdlib>\n#include <iostream>\n#include <boost/test/included/unit_test.hpp>\n#include <chrono>\n#include <string>\n\nuint32_t    GRAPHENE_TESTING_GENESIS_TIMESTAMP = 1431700000;\nstd::string GRAPHENE_TESTING_ES_URL            = \"http://127.0.0.1:9200/\";\n\nboost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {\n   const auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();\n   std::srand( seed );\n   std::cout << \"Random number generator seeded to \" << seed << std::endl;\n   const char* genesis_timestamp_str = getenv(\"GRAPHENE_TESTING_GENESIS_TIMESTAMP\");\n   if( genesis_timestamp_str != nullptr )\n   {\n      GRAPHENE_TESTING_GENESIS_TIMESTAMP = std::stoul( genesis_timestamp_str );\n   }\n   std::cout << \"GRAPHENE_TESTING_GENESIS_TIMESTAMP is \" << GRAPHENE_TESTING_GENESIS_TIMESTAMP << std::endl;\n   const char* env_es_url = getenv(\"GRAPHENE_TESTING_ES_URL\");\n   if( env_es_url != nullptr )\n   {\n      std::string tmp_es_url( env_es_url );\n      if( tmp_es_url.substr(0, 7) == \"http://\" || tmp_es_url.substr(0, 8) == \"https://\" )\n         GRAPHENE_TESTING_ES_URL = tmp_es_url;\n   }\n   std::cout << \"GRAPHENE_TESTING_ES_URL is \" << GRAPHENE_TESTING_ES_URL << std::endl;\n   return nullptr;\n}\n"
  },
  {
    "path": "tests/common/program_options_util.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <boost/program_options.hpp>\n\nnamespace fc {\n\n   /**\n    * Set value of a named variable in the program options map if the variable has not been set.\n    */\n   template<typename T>\n   static void set_option( boost::program_options::variables_map& options, const std::string& name, const T& value )\n   {\n      options.emplace( name, boost::program_options::variable_value( value, false ) );\n   }\n\n} // namespace fc\n"
  },
  {
    "path": "tests/common/simulated_network.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <fc/io/raw.hpp>\n#include <fc/thread/thread.hpp>\n#include <fc/thread/future.hpp>\n\n#include \"simulated_network.hpp\"\n\n#include <queue>\n\nnamespace graphene { namespace net {\n\n  struct simulated_network::node_info\n  {\n    std::shared_ptr<node_delegate> delegate;\n    fc::future<void> message_sender_task_done;\n    std::queue<message> messages_to_deliver;\n    explicit node_info(std::shared_ptr<node_delegate> del) : delegate(del) {}\n  };\n\n  simulated_network::~simulated_network()\n  {\n    for( auto network_node_info : network_nodes )\n    {\n      network_node_info->message_sender_task_done.cancel_and_wait(\"~simulated_network()\");\n    }\n  }\n\n  void simulated_network::message_sender(std::shared_ptr<node_info> destination_node) const\n  {\n    while (!destination_node->messages_to_deliver.empty())\n    {\n      try\n      {\n        const message& message_to_deliver = destination_node->messages_to_deliver.front();\n        if (message_to_deliver.msg_type.value() == trx_message_type)\n          destination_node->delegate->handle_transaction(message_to_deliver.as<trx_message>());\n        else if (message_to_deliver.msg_type.value() == block_message_type)\n        {\n          std::vector<message_hash_type> contained_transaction_msg_ids;\n          destination_node->delegate->handle_block(message_to_deliver.as<block_message>(), false,\n                                                   contained_transaction_msg_ids);\n        }\n        else\n          destination_node->delegate->handle_message(message_to_deliver);\n      }\n      catch ( const fc::exception& e )\n      {\n        elog( \"${r}\", (\"r\",e) );\n      }\n      destination_node->messages_to_deliver.pop();\n    }\n  }\n\n  void simulated_network::broadcast( const message& item_to_broadcast  ) const\n  {\n    for (auto network_node_info : network_nodes)\n    {\n      network_node_info->messages_to_deliver.emplace(item_to_broadcast);\n      if (!network_node_info->message_sender_task_done.valid() || network_node_info->message_sender_task_done.ready())\n        network_node_info->message_sender_task_done = fc::async([=](){ message_sender(network_node_info); },\n                                                                \"simulated_network_sender\");\n    }\n  }\n\n  void simulated_network::add_node_delegate( std::shared_ptr<node_delegate> node_delegate_to_add )\n  {\n    network_nodes.push_back(std::make_shared<node_info>(node_delegate_to_add));\n  }\n\n} } // graphene::net\n"
  },
  {
    "path": "tests/common/simulated_network.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#include <graphene/net/node.hpp>\n\n#include <list>\n\nnamespace graphene { namespace net {\n\nclass simulated_network : public node\n{\npublic:\n   ~simulated_network() override;\n   explicit simulated_network(const std::string& user_agent) : node(user_agent) {}\n   void      listen_to_p2p_network() const override {}\n   void      connect_to_p2p_network() const override {}\n   void      connect_to_endpoint(const fc::ip::endpoint& ep) const override {}\n\n   fc::ip::endpoint get_actual_listening_endpoint() const override { return fc::ip::endpoint(); }\n\n   void      sync_from(const item_id& current_head_block,\n                       const std::vector<uint32_t>& hard_fork_block_numbers) const override {}\n   void      broadcast(const message& item_to_broadcast) const override;\n   void      add_node_delegate(std::shared_ptr<node_delegate> node_delegate_to_add);\n\n   uint32_t get_connection_count() const override { return 8; }\nprivate:\n   struct node_info;\n   void message_sender(std::shared_ptr<node_info> destination_node) const;\n   std::list<std::shared_ptr<node_info>> network_nodes;\n};\n\nusing simulated_network_ptr = std::shared_ptr<simulated_network>;\n\n} } // graphene::net\n"
  },
  {
    "path": "tests/common/utils.hpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#pragma once\n\n#ifdef _WIN32\n   #ifndef _WIN32_WINNT\n      #define _WIN32_WINNT 0x0501\n   #endif\n   #include <winsock2.h>\n   #include <ws2tcpip.h>\n#else\n   #include <sys/types.h>\n   #include <sys/socket.h>\n   #include <netinet/in.h>\n   #include <netinet/ip.h>\n#endif\n\nnamespace fc {\n\n   /** Waits for F() to return true before max_duration has passed.\n    */\n   template<typename Functor>\n   static void wait_for( const fc::microseconds max_duration, const Functor&& f )\n   {\n      const auto start = fc::time_point::now();\n      while( !f() && fc::time_point::now() < start + max_duration )\n         fc::usleep(fc::milliseconds(100));\n      BOOST_REQUIRE( f() );\n   }\n\nnamespace network {\n   //////\n   /// @brief attempt to find an available port on localhost\n   /// @returns an available port number, or -1 on error\n   /////\n   int get_available_port()\n   {\n      struct sockaddr_in sin;\n      int socket_fd = socket(AF_INET, SOCK_STREAM, 0);\n      if (socket_fd == -1)\n         return -1;\n      sin.sin_family = AF_INET;\n      sin.sin_port = 0;\n      sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n      if (::bind(socket_fd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1)\n         return -1;\n      socklen_t len = sizeof(sin);\n      if (getsockname(socket_fd, (struct sockaddr *)&sin, &len) == -1)\n         return -1;\n   #ifdef _WIN32\n      closesocket(socket_fd);\n   #else\n      close(socket_fd);\n   #endif\n      return ntohs(sin.sin_port);\n   }\n\n} // namespace fc::network\n\n} // namespace fc\n"
  },
  {
    "path": "tests/elasticsearch/main.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/app/api.hpp>\n#include <graphene/utilities/tempdir.hpp>\n#include <fc/crypto/digest.hpp>\n\n#include <graphene/utilities/elasticsearch.hpp>\n#include <graphene/elasticsearch/elasticsearch_plugin.hpp>\n\n#include \"../common/init_unit_test_suite.hpp\"\n#include \"../common/database_fixture.hpp\"\n#include \"../common/elasticsearch.hpp\"\n#include \"../common/utils.hpp\"\n\n#define ES_WAIT_TIME (fc::milliseconds(10000))\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::app;\n\nextern std::string GRAPHENE_TESTING_ES_URL;\n\nBOOST_FIXTURE_TEST_SUITE( elasticsearch_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE(elasticsearch_account_history) {\n   try {\n\n      CURL *curl; // curl handler\n      curl = curl_easy_init();\n      curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);\n\n      graphene::utilities::ES es;\n      es.curl = curl;\n      es.elasticsearch_url = GRAPHENE_TESTING_ES_URL;\n      es.index_prefix = es_index_prefix;\n\n      // delete all first\n      auto delete_account_history = graphene::utilities::deleteAll(es);\n\n      BOOST_REQUIRE(delete_account_history); // require successful deletion\n      if(delete_account_history) { // all records deleted\n\n         //account_id_type() do 3 ops\n         create_bitasset(\"USD\", account_id_type());\n         auto dan = create_account(\"dan\");\n         auto bob = create_account(\"bob\");\n\n         generate_block();\n\n         string query = \"{ \\\"query\\\" : { \\\"bool\\\" : { \\\"must\\\" : [{\\\"match_all\\\": {}}] } } }\";\n         es.endpoint = es.index_prefix + \"*/_count\";\n         es.query = query;\n\n         string res;\n         variant j;\n         string total;\n\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::simpleQuery(es);\n            j = fc::json::from_string(res);\n            total = j[\"count\"].as_string();\n            return (total == \"5\");\n         });\n\n         es.endpoint = es.index_prefix + \"*/_search\";\n         res = graphene::utilities::simpleQuery(es);\n         j = fc::json::from_string(res);\n         auto first_id = j[\"hits\"][\"hits\"][size_t(0)][\"_id\"].as_string();\n         BOOST_CHECK_EQUAL(first_id, \"2.9.0\");\n\n         generate_block();\n         auto willie = create_account(\"willie\");\n         generate_block();\n\n         es.endpoint = es.index_prefix + \"*/_count\";\n\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::simpleQuery(es);\n            j = fc::json::from_string(res);\n            total = j[\"count\"].as_string();\n            return (total == \"7\");\n         });\n\n         // do some transfers in 1 block\n         transfer(account_id_type()(db), bob, asset(100));\n         transfer(account_id_type()(db), bob, asset(200));\n         transfer(account_id_type()(db), bob, asset(300));\n\n         generate_block();\n\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::simpleQuery(es);\n            j = fc::json::from_string(res);\n            total = j[\"count\"].as_string();\n            return (total == \"13\");\n         });\n\n         // check the visitor data\n         auto block_date = db.head_block_time();\n         std::string index_name = es_index_prefix + block_date.to_iso_string().substr( 0, 7 ); // yyyy-MM\n\n         es.endpoint = index_name + \"/_doc/2.9.12\"; // we know last op is a transfer of amount 300\n         res = graphene::utilities::getEndPoint(es);\n         j = fc::json::from_string(res);\n         auto last_transfer_amount = j[\"_source\"][\"operation_history\"][\"op_object\"][\"amount_\"][\"amount\"].as_string();\n         BOOST_CHECK_EQUAL(last_transfer_amount, \"300\");\n         auto last_transfer_payer = j[\"_source\"][\"operation_history\"][\"fee_payer\"].as_string();\n         BOOST_CHECK_EQUAL(last_transfer_payer, \"1.2.0\");\n         auto is_virtual = j[\"_source\"][\"operation_history\"][\"is_virtual\"].as_bool();\n         BOOST_CHECK( !is_virtual );\n\n         // To test credit offers\n         generate_blocks( HARDFORK_CORE_2362_TIME );\n         set_expiration( db, trx );\n\n         ACTORS((sam)(ted)(por));\n\n         auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         fund( sam, asset(init_amount) );\n         fund( ted, asset(init_amount) );\n\n         const asset_object& core = asset_id_type()(db);\n         asset_id_type core_id;\n\n         const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n         asset_id_type usd_id = usd.get_id();\n         issue_uia( sam, usd.amount(init_amount) );\n         issue_uia( ted, usd.amount(init_amount) );\n\n         const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n         asset_id_type eur_id = eur.get_id();\n         issue_uia( sam, eur.amount(init_amount) );\n         issue_uia( ted, eur.amount(init_amount) );\n\n         // propose\n         {\n            flat_map<asset_id_type, price> collateral_map;\n            collateral_map[usd_id] = price( asset(1), asset(1, usd_id) );\n\n            credit_offer_create_operation cop = make_credit_offer_create_op( sam_id, core.get_id(), 10000, 100, 3600,\n                                                   0, false, db.head_block_time() + fc::days(1), collateral_map, {} );\n            propose( cop );\n         }\n\n         // create credit offers\n         // 1.\n         auto disable_time1 = db.head_block_time() - fc::minutes(1); // a time in the past\n\n         flat_map<asset_id_type, price> collateral_map1;\n         collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) );\n\n         const credit_offer_object& coo1 = create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, collateral_map1, {} );\n\n         BOOST_CHECK( coo1.owner_account == sam_id );\n         BOOST_CHECK( coo1.current_balance == 10000 );\n\n         // 2.\n         auto duration2 = GRAPHENE_MAX_CREDIT_DEAL_SECS;\n         auto disable_time2 = db.head_block_time() + fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS);\n\n         flat_map<asset_id_type, price> collateral_map2;\n         collateral_map2[core_id] = price( asset(2, usd_id), asset(3) );\n         collateral_map2[eur_id] = price( asset(3, usd_id), asset(4, eur_id) );\n\n         flat_map<account_id_type, share_type> borrower_map2;\n         borrower_map2[account_id_type()] = 0;\n         borrower_map2[sam_id] = 1;\n         borrower_map2[ted_id] = GRAPHENE_MAX_SHARE_SUPPLY;\n\n         const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, borrower_map2 );\n         BOOST_CHECK( coo2.owner_account == ted_id );\n         BOOST_CHECK( coo2.asset_type == usd_id );\n         BOOST_CHECK( coo2.total_balance == 1 );\n\n         generate_block();\n\n         es.endpoint = es.index_prefix + \"*/_count\";\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::simpleQuery(es);\n            j = fc::json::from_string(res);\n            total = j[\"count\"].as_string();\n            return (std::stoi(total) > 13);\n         });\n\n      }\n   }\n   catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(elasticsearch_objects) {\n   try {\n\n      CURL *curl; // curl handler\n      curl = curl_easy_init();\n      curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);\n\n      graphene::utilities::ES es;\n      es.curl = curl;\n      es.elasticsearch_url = GRAPHENE_TESTING_ES_URL;\n      es.index_prefix = es_obj_index_prefix;\n\n      // The head block number is 1\n      BOOST_CHECK_EQUAL( db.head_block_num(), 1u );\n\n      generate_blocks( HARDFORK_CORE_2535_TIME ); // For Order-Sends-Take-Profit-Order\n      generate_block();\n      set_expiration( db, trx );\n\n      // delete all first, this will delete genesis data and data inserted at block 1\n      auto delete_objects = graphene::utilities::deleteAll(es);\n      BOOST_REQUIRE(delete_objects); // require successful deletion\n\n      generate_block();\n\n      if(delete_objects) { // all records deleted\n\n         // asset and bitasset\n         asset_id_type usd_id = create_bitasset(\"USD\", account_id_type()).get_id();\n         generate_block();\n\n         string query = \"{ \\\"query\\\" : { \\\"bool\\\" : { \\\"must\\\" : [{\\\"match_all\\\": {}}] } } }\";\n         es.endpoint = es.index_prefix + \"*/_count\";\n         es.query = query;\n\n         string res;\n         variant j;\n         string total;\n\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::simpleQuery(es);\n            j = fc::json::from_string(res);\n            total = j[\"count\"].as_string();\n            return (total == \"2\");\n         });\n\n         es.endpoint = es.index_prefix + \"asset/_search\";\n         res = graphene::utilities::simpleQuery(es);\n         j = fc::json::from_string(res);\n         auto first_id = j[\"hits\"][\"hits\"][size_t(0)][\"_source\"][\"symbol\"].as_string();\n         BOOST_CHECK_EQUAL(first_id, \"USD\");\n\n         auto bitasset_data_id = j[\"hits\"][\"hits\"][size_t(0)][\"_source\"][\"bitasset_data_id\"].as_string();\n         es.endpoint = es.index_prefix + \"bitasset/_search\";\n         es.query = \"{ \\\"query\\\" : { \\\"bool\\\": { \\\"must\\\" : [{ \\\"term\\\": { \\\"object_id\\\": \\\"\"\n                  + bitasset_data_id + \"\\\"}}] } } }\";\n         res = graphene::utilities::simpleQuery(es);\n         j = fc::json::from_string(res);\n         auto bitasset_object_id = j[\"hits\"][\"hits\"][size_t(0)][\"_source\"][\"object_id\"].as_string();\n         BOOST_CHECK_EQUAL(bitasset_object_id, bitasset_data_id);\n\n         //                                           fee_asset, spread,  size,   expiration, repeat\n         create_take_profit_order_action tpa1 { asset_id_type(),    300,  9900,        86400, true };\n         vector<limit_order_auto_action> on_fill_1 { tpa1 };\n         // create a limit order that expires at the next maintenance time\n         create_sell_order( account_id_type(), asset(1), asset(1, usd_id),\n                            db.get_dynamic_global_properties().next_maintenance_time,\n                            price::unit_price(), on_fill_1 );\n         generate_block();\n\n         es.endpoint = es.index_prefix + \"limitorder/_count\";\n         es.query = \"\";\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::getEndPoint(es);\n            j = fc::json::from_string(res);\n            if( !j.is_object() )\n               return false;\n            const auto& obj = j.get_object();\n            if( obj.find(\"count\") == obj.end() )\n               return false;\n            total = obj[\"count\"].as_string();\n            return (total == \"1\");\n         });\n\n         // maintenance, for budget records\n         generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n         generate_block();\n\n         es.endpoint = es.index_prefix + \"budget/_count\";\n         es.query = \"\";\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::getEndPoint(es);\n            j = fc::json::from_string(res);\n            if( !j.is_object() )\n               return false;\n            const auto& obj = j.get_object();\n            if( obj.find(\"count\") == obj.end() )\n               return false;\n            total = obj[\"count\"].as_string();\n            return (total == \"1\"); // new record inserted at the first maintenance block\n         });\n\n         es.endpoint = es.index_prefix + \"limitorder/_count\";\n         es.query = \"\";\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            res = graphene::utilities::getEndPoint(es);\n            j = fc::json::from_string(res);\n            if( !j.is_object() )\n               return false;\n            const auto& obj = j.get_object();\n            if( obj.find(\"count\") == obj.end() )\n               return false;\n            total = obj[\"count\"].as_string();\n            return (total == \"0\"); // the limit order expired, so the object is removed\n         });\n\n      }\n   }\n   catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(elasticsearch_history_api) {\n   try {\n      CURL *curl; // curl handler\n      curl = curl_easy_init();\n      curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);\n\n      graphene::utilities::ES es;\n      es.curl = curl;\n      es.elasticsearch_url = GRAPHENE_TESTING_ES_URL;\n      es.index_prefix = es_index_prefix;\n\n      generate_blocks( HARDFORK_CORE_2535_TIME ); // For Order-Sends-Take-Profit-Order\n      generate_block();\n      set_expiration( db, trx );\n\n      auto delete_account_history = graphene::utilities::deleteAll(es);\n      BOOST_REQUIRE(delete_account_history); // require successful deletion\n\n      generate_block();\n\n      if(delete_account_history) {\n\n         create_bitasset(\"USD\", account_id_type()); // create op 0\n         const account_object& dan = create_account(\"dan\"); // create op 1\n         create_bitasset(\"CNY\", dan.get_id()); // create op 2\n         create_bitasset(\"BTC\", account_id_type()); // create op 3\n         create_bitasset(\"XMR\", dan.get_id()); // create op 4\n         create_bitasset(\"EUR\", account_id_type()); // create op 5\n         create_bitasset(\"OIL\", dan.get_id()); // create op 6\n\n         generate_block();\n\n         // Test history APIs\n         graphene::app::history_api hist_api(app);\n\n         // f(A, 0, 4, 9) = { 5, 3, 1, 0 }\n         auto histories = hist_api.get_account_history(\n               \"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(9));\n\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            histories = hist_api.get_account_history(\n                  \"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(9));\n            return (histories.size() == 4u);\n         });\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n         BOOST_CHECK( !histories[0].is_virtual );\n         BOOST_CHECK( histories[0].block_time == db.head_block_time() );\n\n         // f(A, 0, 4, 6) = { 5, 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(6));\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n         // f(A, 0, 4, 5) = { 5, 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(5));\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n         // f(A, 0, 4, 4) = { 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(4));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n         // f(A, 0, 4, 3) = { 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(3));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n         // f(A, 0, 4, 2) = { 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(2));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n         // f(A, 0, 4, 1) = { 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(1));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n         // f(A, 0, 4, 0) = { 5, 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type());\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n         // f(A, 1, 5, 9) = { 5, 3 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(9));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n         // f(A, 1, 5, 6) = { 5, 3 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(6));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n         // f(A, 1, 5, 5) = { 5, 3 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(5));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n         // f(A, 1, 5, 4) = { 3 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(4));\n         BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n\n         // f(A, 1, 5, 3) = { 3 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(3));\n         BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n\n         // f(A, 1, 5, 2) = { }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(2));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // f(A, 1, 5, 1) = { }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(1));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // f(A, 1, 5, 0) = { 5, 3 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5, operation_history_id_type(0));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n         // f(A, 0, 3, 9) = { 5, 3, 1 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(9));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n         // f(A, 0, 3, 6) = { 5, 3, 1 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(6));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n         // f(A, 0, 3, 5) = { 5, 3, 1 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(5));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n         // f(A, 0, 3, 4) = { 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(4));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n         // f(A, 0, 3, 3) = { 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(3));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n         // f(A, 0, 3, 2) = { 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(2));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n         // f(A, 0, 3, 1) = { 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(1));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n         // f(A, 0, 3, 0) = { 5, 3, 1 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type());\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n         // f(B, 0, 4, 9) = { 6, 4, 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(9));\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n         // f(B, 0, 4, 6) = { 6, 4, 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(6));\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n         // f(B, 0, 4, 5) = { 4, 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(5));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n         // f(B, 0, 4, 4) = { 4, 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(4));\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n         // f(B, 0, 4, 3) = { 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(3));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n\n         // f(B, 0, 4, 2) = { 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(2));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n\n         // f(B, 0, 4, 1) = { 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(1));\n         BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n\n         // f(B, 0, 4, 0) = { 6, 4, 2, 1 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type());\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n         // f(B, 2, 4, 9) = { 6, 4 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(9));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n         // f(B, 2, 4, 6) = { 6, 4 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(6));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n         // f(B, 2, 4, 5) = { 4 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(5));\n         BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n\n         // f(B, 2, 4, 4) = { 4 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(4));\n         BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n\n         // f(B, 2, 4, 3) = { }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(3));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // f(B, 2, 4, 2) = { }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(2));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // f(B, 2, 4, 1) = { }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(1));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // f(B, 2, 4, 0) = { 6, 4 }\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(0));\n         BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n         // 0 limits\n         histories = hist_api.get_account_history(\"dan\", operation_history_id_type(0), 0, operation_history_id_type(0));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(3), 0, operation_history_id_type(9));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // non existent account\n         histories = hist_api.get_account_history(\"1.2.18\", operation_history_id_type(0), 4, operation_history_id_type(0));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // create a new account C = alice { 7 }\n         auto alice = create_account(\"alice\");\n         account_id_type alice_id = alice.get_id();\n\n         generate_block();\n\n         // f(C, 0, 4, 10) = { 7 }\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            histories = hist_api.get_account_history(\n                  \"alice\", operation_history_id_type(0), 4, operation_history_id_type(10));\n            return (histories.size() == 1u);\n         });\n         BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 7u);\n\n         // f(C, 8, 4, 10) = { }\n         histories = hist_api.get_account_history(\"alice\", operation_history_id_type(8), 4, operation_history_id_type(10));\n         BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n         // f(A, 0, 10, 0) = { 7, 5, 3, 1, 0 }\n         histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10, operation_history_id_type(0));\n         BOOST_REQUIRE_EQUAL(histories.size(), 5u);\n         BOOST_CHECK_EQUAL(histories[0].id.instance(), 7u);\n         BOOST_CHECK_EQUAL(histories[1].id.instance(), 5u);\n         BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);\n         BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n         BOOST_CHECK_EQUAL(histories[4].id.instance(), 0u);\n\n         // Ugly test to cover elasticsearch_plugin::get_operation_by_id()\n         if( !app.elasticsearch_thread )\n            app.elasticsearch_thread = std::make_shared<fc::thread>(\"elasticsearch\");\n         auto es_plugin = app.get_plugin< graphene::elasticsearch::elasticsearch_plugin >(\"elasticsearch\");\n         auto his_obj7 = app.elasticsearch_thread->async([&es_plugin]() {\n            return es_plugin->get_operation_by_id( operation_history_id_type(7) );\n         }, \"thread invoke for method \" BOOST_PP_STRINGIZE(method_name)).wait();\n         BOOST_REQUIRE( his_obj7.op.is_type<account_create_operation>() );\n         BOOST_CHECK_EQUAL( his_obj7.op.get<account_create_operation>().name, \"alice\" );\n\n         // Test virtual operation\n\n         // Prepare funds\n         transfer( account_id_type()(db), alice_id(db), asset(100) );\n         //                                           fee_asset, spread,  size,   expiration, repeat\n         create_take_profit_order_action tpa1 { asset_id_type(),    100, 10000,        86400, false };\n         vector<limit_order_auto_action> on_fill_1 { tpa1 };\n         // Create a limit order that expires in 300 seconds\n         create_sell_order( alice_id, asset(1), asset(1, asset_id_type(1)), db.head_block_time() + 300,\n                            price::unit_price(), on_fill_1 );\n\n         generate_block();\n\n         // f(C, 0, 4, 0) = { 9, 8, 7 }\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            histories = hist_api.get_account_history(\n                  \"alice\", operation_history_id_type(0), 4, operation_history_id_type(0));\n            return (histories.size() == 3u);\n         });\n         BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n         BOOST_CHECK( histories[0].op.is_type<limit_order_create_operation>() );\n         BOOST_CHECK( !histories[0].is_virtual );\n         BOOST_CHECK( histories[0].block_time == db.head_block_time() );\n         BOOST_CHECK( histories[1].op.is_type<transfer_operation>() );\n         BOOST_CHECK( !histories[1].is_virtual );\n\n         // Let the limit order expire\n         generate_blocks( db.head_block_time() + 300 );\n         generate_block();\n\n         // f(C, 0, 4, 0) = { 10, 9, 8, 7 }\n         fc::wait_for( ES_WAIT_TIME,  [&]() {\n            histories = hist_api.get_account_history(\n                  \"alice\", operation_history_id_type(0), 4, operation_history_id_type(0));\n            return (histories.size() == 4u);\n         });\n         BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n         BOOST_CHECK( histories[0].op.is_type<limit_order_cancel_operation>() );\n         BOOST_CHECK( histories[0].is_virtual );\n         BOOST_CHECK( histories[1].op.is_type<limit_order_create_operation>() );\n         BOOST_CHECK( !histories[1].is_virtual );\n         BOOST_CHECK( histories[2].op.is_type<transfer_operation>() );\n         BOOST_CHECK( !histories[2].is_virtual );\n\n      }\n   }\n   catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/generate_empty_blocks/CMakeLists.txt",
    "content": "add_executable( generate_empty_blocks main.cpp )\nif( UNIX AND NOT APPLE )\n  set(rt_library rt )\nendif()\n\ntarget_link_libraries( generate_empty_blocks\n                       PRIVATE graphene_app graphene_chain graphene_egenesis_none fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )\n\ninstall( TARGETS\n   generate_empty_blocks\n\n   RUNTIME DESTINATION bin\n   LIBRARY DESTINATION lib\n   ARCHIVE DESTINATION lib\n)\n"
  },
  {
    "path": "tests/generate_empty_blocks/main.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n\n#include <fc/io/fstream.hpp>\n#include <fc/io/json.hpp>\n#include <fc/io/stdio.hpp>\n\n#include <graphene/app/api.hpp>\n#include <graphene/egenesis/egenesis.hpp>\n#include <graphene/utilities/key_conversion.hpp>\n\n#include <boost/filesystem.hpp>\n\n#ifndef WIN32\n#include <csignal>\n#endif\n\nusing namespace graphene::app;\nusing namespace graphene::chain;\nusing namespace graphene::utilities;\nusing namespace std;\nnamespace bpo = boost::program_options;\n\n// hack:  import create_example_genesis() even though it's a way, way\n// specific internal detail\nnamespace graphene { namespace app { namespace detail {\ngenesis_state_type create_example_genesis();\n} } } // graphene::app::detail\n\nint main( int argc, char** argv )\n{\n   try\n   {\n      bpo::options_description cli_options(\"BitShares empty blocks\");\n      cli_options.add_options()\n            (\"help,h\", \"Print this help message and exit.\")\n            (\"data-dir\", bpo::value<boost::filesystem::path>()->default_value(\"empty_blocks_data_dir\"), \"Directory containing generator database\")\n            (\"genesis-json,g\", bpo::value<boost::filesystem::path>(), \"File to read genesis state from\")\n            (\"genesis-time,t\", bpo::value<uint32_t>()->default_value(0), \"Timestamp for genesis state (0=use value from file/example)\")\n            (\"num-blocks,n\", bpo::value<uint32_t>()->default_value(1000000), \"Number of blocks to generate\")\n            (\"miss-rate,r\", bpo::value<uint32_t>()->default_value(3), \"Percentage of blocks to miss\")\n            (\"verbose,v\", \"Enter verbose mode\")\n            ;\n\n      bpo::variables_map options;\n      try\n      {\n         boost::program_options::store( boost::program_options::parse_command_line(argc, argv, cli_options), options );\n      }\n      catch (const boost::program_options::error& e)\n      {\n         std::cerr << \"empty_blocks:  error parsing command line: \" << e.what() << \"\\n\";\n         return 1;\n      }\n\n      if( options.count(\"help\") )\n      {\n         std::cout << cli_options << \"\\n\";\n         return 0;\n      }\n\n      fc::path data_dir;\n      if( options.count(\"data-dir\") )\n      {\n         data_dir = options[\"data-dir\"].as<boost::filesystem::path>();\n         if( data_dir.is_relative() )\n            data_dir = fc::current_path() / data_dir;\n      }\n\n      genesis_state_type genesis;\n      if( options.count(\"genesis-json\") )\n      {\n         fc::path genesis_json_filename = options[\"genesis-json\"].as<boost::filesystem::path>();\n         std::cerr << \"embed_genesis:  Reading genesis from file \" << genesis_json_filename.preferred_string() << \"\\n\";\n         std::string genesis_json;\n         read_file_contents( genesis_json_filename, genesis_json );\n         genesis = fc::json::from_string( genesis_json ).as< genesis_state_type >(20);\n      }\n      else\n         genesis = graphene::app::detail::create_example_genesis();\n      uint32_t timestamp = options[\"genesis-time\"].as<uint32_t>();\n      if( timestamp != 0 )\n      {\n         genesis.initial_timestamp = fc::time_point_sec( timestamp );\n         std::cerr << \"embed_genesis:  Genesis timestamp is \" << genesis.initial_timestamp.sec_since_epoch() << \" (from CLI)\\n\";\n      }\n      else\n         std::cerr << \"embed_genesis:  Genesis timestamp is \" << genesis.initial_timestamp.sec_since_epoch() << \" (from state)\\n\";\n      bool verbose = (options.count(\"verbose\") != 0);\n\n      uint32_t num_blocks = options[\"num-blocks\"].as<uint32_t>();\n      uint32_t miss_rate = options[\"miss-rate\"].as<uint32_t>();\n\n      fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"nathan\")));\n\n      database db;\n      fc::path db_path = data_dir / \"db\";\n      db.open(db_path, [&]() { return genesis; }, \"TEST\" );\n\n      uint32_t slot = 1;\n      uint32_t missed = 0;\n\n      for( uint32_t i = 1; i < num_blocks; ++i )\n      {\n         signed_block b = db.generate_block(db.get_slot_time(slot), db.get_scheduled_witness(slot), nathan_priv_key, database::skip_nothing);\n         FC_ASSERT( db.head_block_id() == b.id() );\n         fc::sha256 h = b.digest();\n         uint64_t rand = h._hash[0].value();\n         slot = 1;\n         while(true)\n         {\n            if( (rand % 100) < miss_rate )\n            {\n               slot++;\n               rand = (rand/100) ^ h._hash[slot&3].value();\n               missed++;\n            }\n            else\n               break;\n         }\n         \n         witness_id_type prev_witness = b.witness;\n         witness_id_type cur_witness = db.get_scheduled_witness(1);\n         if( verbose )\n         {\n            wdump( (prev_witness)(cur_witness) );\n         }\n         else if( (i%10000) == 0 )\n         {\n            std::cerr << \"\\rblock #\" << i << \"   missed \" << missed;\n         }\n         if( slot == 1 )  // can possibly get consecutive production if block missed\n         {\n            FC_ASSERT( cur_witness != prev_witness );\n         }\n      }\n      std::cerr << \"\\n\";\n      db.close();\n   }\n   catch ( const fc::exception& e )\n   {\n      std::cout << e.to_detail_string() << \"\\n\";\n      return 1;\n   }\n   return 0;\n}\n"
  },
  {
    "path": "tests/intense/api_stress.py",
    "content": "#!/usr/bin/env python3\n\nimport json\nimport os\nimport random\nimport signal\nimport struct\nimport sys\nimport time\n\ntry:\n    import asyncio\nexcept ImportError:\n    print(\"asyncio module not found (try pip install asyncio, or upgrade to Python 3.4 or later)\")\n    sys.exit(1)\n\ntry:\n    import websockets\nexcept ImportError:\n    print(\"websockets module not found (try pip install websockets)\")\n    sys.exit(1)\n\n@asyncio.coroutine\ndef mainloop():\n    ws = yield from websockets.connect('ws://localhost:8090/')\n    entropy = struct.unpack(\"<Q\", os.urandom(8))[0]\n    rand = random.Random(entropy)\n    my_account_id = rand.randrange(0, 90000)\n\n    gpo_get = {\"id\":1, \"method\":\"call\", \"params\":[0, \"get_objects\", [\"2.0.0\"]]}\n    dgpo_sub = {\"id\":2, \"method\":\"call\", \"params\":[0, \"subscribe_to_objects\", [111, [\"2.1.0\"]]]}\n    acct_sub = {\"id\":3, \"method\":\"call\", \"params\":[0, \"get_full_accounts\", [222, [\"1.2.\"+str(my_account_id)], True]]}\n    yield from ws.send(json.dumps(gpo_get))\n    yield from ws.send(json.dumps(dgpo_sub))\n    yield from ws.send(json.dumps(acct_sub))\n\n    next_call_id = 4\n\n    def peek_random_account():\n        nonlocal next_call_id\n        asyncio.get_event_loop().call_later(rand.uniform(0, 3), peek_random_account)\n        peek_account_id = rand.randrange(0, 90000)\n        acct_peek = {\"id\" : next_call_id, \"method\" : \"call\", \"params\":\n           [0, \"get_objects\", [[\"1.2.\"+str(peek_account_id)], True]]}\n        next_call_id += 1\n        yield from ws.send(json.dumps(acct_peek))\n        return\n\n    yield from peek_random_account()\n\n    while True:\n        result = yield from ws.recv()\n        #print(result)\n        if result is None:\n           break\n    yield from ws.close()\n\nchild_procs = []\n\n# stress test with 200 instances\nwhile len(child_procs) < 200:\n    pid = os.fork()\n    if pid == 0:\n        asyncio.get_event_loop().run_until_complete(mainloop())\n        break\n    else:\n        child_procs.append(pid)\n\ntime.sleep(60)\nfor pid in child_procs:\n    os.kill(pid, signal.SIGTERM)\n\ntime.sleep(2)\nfor pid in child_procs:\nos.kill(pid, signal.SIGKILL)\n"
  },
  {
    "path": "tests/performance/README.md",
    "content": "HOW TO\n======\n\nThis small test suite serves to demonstrate two key points about the performance\nof our current implementation. The subject was talked about in detail at\nBitFest Amsterdam, Sep 22, 2018.\n\nThe original description of the 100,000 transactions per second test can be\nfound at\nhttps://web.archive.org/web/20181111215859/https://bitshares.org/blog/2015/06/08/measuring-performance/ .\n\nPrepare\n-------\n\n1. Follow the build instructions in the top-level README file.\n2. Instead of running ``make`` you can run ``make performance_test`` to build\n   only the test suite.\n3. Run ``tests/performance_test -t performance_tests/<testcase>``\n\n\n100k TX/s\n---------\n\n``tests/performance_test -t performance_tests/one_hundred_k_benchmark``\n\nThis test will create 200,000 accounts, make two transfers from each account,\nthen create an asset and issue tokens to each account, for a total of one\nmillion operations.\n\nDifferent operation types have different execution times, but on fairly modern\noff-the-shelf hardware an average of 100,000 transactions per second should be\nachieved.\n\nSignature verification\n----------------------\n\n``tests/performance_test -t performance_tests/sigcheck_benchmark``\n\nThis suite pre-creates 100,000 signatures and then measures how long it takes\nto verify them. Results vary depending on CPU type and clockspeed, but should be\nsomewhere between 5,000 and 20,000 per second.\n"
  },
  {
    "path": "tests/performance/genesis_allocation.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/db_with.hpp>\n#include <graphene/chain/account_object.hpp>\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include <database_fixture.hpp>\n\nextern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP;\n\nusing namespace graphene::chain;\n\nBOOST_AUTO_TEST_CASE( operation_sanity_check )\n{\n   try {\n      operation op = account_create_operation();\n      op.get<account_create_operation>().active.add_authority(account_id_type(), 123);\n      operation tmp = std::move(op);\n      wdump((tmp.which()));\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( genesis_and_persistence_bench )\n{\n   try {\n      const auto witness_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      const auto witness_pub_key = witness_priv_key.get_public_key();\n\n      genesis_state_type genesis_state;\n      genesis_state.initial_timestamp = fc::time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP );\n      genesis_state.initial_parameters.get_mutable_fees().zero_all_fees();\n      genesis_state.initial_active_witnesses = 10;\n      genesis_state.initial_chain_id = fc::sha256::hash(string(\"dummy_id\"));\n      for( unsigned int i = 0; i < genesis_state.initial_active_witnesses; ++i )\n      {\n         auto name = \"init\"+fc::to_string(i);\n         genesis_state.initial_accounts.emplace_back(name, witness_pub_key, witness_pub_key, true);\n         genesis_state.initial_committee_candidates.push_back({name});\n         genesis_state.initial_witness_candidates.push_back({name, witness_pub_key});\n      }\n\n#ifdef NDEBUG\n      ilog(\"Running in release mode.\");\n      const int account_count = 2000000;\n      const int blocks_to_produce = 1000000;\n#else\n      ilog(\"Running in debug mode.\");\n      const int account_count = 30000;\n      const int blocks_to_produce = 1000;\n#endif\n\n      const auto account_pub_key = fc::ecc::private_key::regenerate(fc::digest(account_count)).get_public_key();\n      for( int i = 0; i < account_count; ++i )\n         genesis_state.initial_accounts.emplace_back(\"target\"+fc::to_string(i),\n                                                     public_key_type(account_pub_key));\n\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n\n      {\n         database db;\n         db.open(data_dir.path(), [&genesis_state]{return genesis_state;}, \"test\");\n\n         for( int i = 11; i < account_count + 11; ++i)\n            BOOST_CHECK(db.get_balance(account_id_type(i), asset_id_type()).amount == 0);\n\n         fc::time_point start_time = fc::time_point::now();\n         db.close();\n         ilog(\"Closed database in ${t} milliseconds.\", (\"t\", (fc::time_point::now() - start_time).count() / 1000));\n      }\n      {\n         database db;\n\n         fc::time_point start_time = fc::time_point::now();\n         db.open(data_dir.path(), [&genesis_state]{return genesis_state;}, \"test\");\n         ilog(\"Opened database in ${t} milliseconds.\", (\"t\", (fc::time_point::now() - start_time).count() / 1000));\n\n         for( int i = 11; i < account_count + 11; ++i)\n            BOOST_CHECK(db.get_balance(account_id_type(i), asset_id_type()).amount == 0);\n\n         int blocks_out = 0;\n         db.generate_block( db.get_slot_time( 1 ), db.get_scheduled_witness( 1 ), witness_priv_key, ~0 );\n\n         start_time = fc::time_point::now();\n         transfer_operation top;\n         top.amount = asset(1);\n         top.from = account_id_type();\n         for( int i = 0; i < blocks_to_produce; ++i )\n         {\n            signed_transaction trx;\n            test::set_expiration( db, trx );\n            top.to = account_id_type(i + 11);\n            trx.operations.push_back( top );\n            db.push_transaction(trx, ~graphene::chain::database::skip_transaction_dupe_check);\n            db.generate_block( db.get_slot_time( 1 ), db.get_scheduled_witness( 1 ), witness_priv_key,\n                               ~graphene::chain::database::skip_transaction_dupe_check );\n         }\n         ilog(\"Pushed ${c} blocks (1 op each, no validation) in ${t} milliseconds.\",\n              (\"c\", blocks_out)(\"t\", (fc::time_point::now() - start_time).count() / 1000));\n\n         for( int i = 0; i < blocks_to_produce; ++i )\n            BOOST_CHECK_EQUAL( 1, db.get_balance(account_id_type(i + 11), asset_id_type()).amount.value );\n\n         start_time = fc::time_point::now();\n         db.close();\n         ilog(\"Closed database in ${t} milliseconds.\", (\"t\", (fc::time_point::now() - start_time).count() / 1000));\n      }\n      {\n         database db;\n         const auto skip = graphene::chain::database::skip_witness_signature |\n                graphene::chain::database::skip_block_size_check |\n                graphene::chain::database::skip_merkle_check |\n                graphene::chain::database::skip_transaction_signatures |\n                graphene::chain::database::skip_transaction_dupe_check |\n                graphene::chain::database::skip_tapos_check |\n                graphene::chain::database::skip_witness_schedule_check;\n\n         auto start_time = fc::time_point::now();\n         wlog( \"about to start reindex...\" );\n         graphene::chain::detail::with_skip_flags( db, skip, [&data_dir,&db,&genesis_state] () {\n            db.open(data_dir.path(), [&genesis_state]{return genesis_state;}, \"force_wipe\");\n         });\n        \n         ilog(\"Replayed database in ${t} milliseconds.\", (\"t\", (fc::time_point::now() - start_time).count() / 1000));\n\n         for( int i = 0; i < blocks_to_produce; ++i )\n            BOOST_CHECK( db.get_balance(account_id_type(i + 11), asset_id_type()).amount == 1 );\n      }\n\n   } catch(fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n"
  },
  {
    "path": "tests/performance/market_fee_sharing_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Bitshares Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <boost/test/unit_test.hpp>\n\n#include <chrono>\n#include <graphene/chain/hardfork.hpp>\n#include <fc/time.hpp>\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\n\nBOOST_FIXTURE_TEST_CASE(mfs_performance_test, database_fixture)\n{\n   try\n   {\n      ACTORS((issuer));\n\n      const unsigned int accounts = 3000;\n      const unsigned int iterations = 20;\n\n      std::vector<account_object> registrators;\n      for (unsigned int i = 0; i < accounts; ++i)\n      {\n         auto account = create_account(\"registrar\" + std::to_string(i));\n         transfer(committee_account, account.get_id(), asset(1000000));\n         upgrade_to_lifetime_member(account);\n\n         registrators.push_back(std::move(account));\n      }\n\n      generate_block();\n\n      additional_asset_options_t options;\n      options.value.reward_percent = 2 * GRAPHENE_1_PERCENT;\n\n      const auto usd = create_user_issued_asset(\n                  \"USD\",\n                  issuer,\n                  charge_market_fee,\n                  price(asset(1, asset_id_type(1)), asset(1)),\n                  1,\n                  20 * GRAPHENE_1_PERCENT,\n                  options);\n\n      issue_uia(issuer, usd.amount(iterations * accounts * 2000));\n\n      std::vector<account_object> traders;\n      for (unsigned int i = 0; i < accounts; ++i)\n      {\n         std::string name = \"account\" + std::to_string(i);\n         auto account = create_account(name, registrators[i], registrators[i], GRAPHENE_1_PERCENT);\n         transfer(committee_account, account.get_id(), asset(1000000));\n         transfer(issuer, account, usd.amount(iterations * 2000));\n\n         traders.push_back(std::move(account));\n      }\n\n      using namespace std::chrono;\n\n      const auto start = high_resolution_clock::now();\n\n      for (unsigned int i = 0; i < iterations; ++i)\n      {\n         for (unsigned int j = 0; j < accounts; ++j)\n         {\n            create_sell_order(traders[j], usd.amount(2000), asset(1));\n            create_sell_order(traders[accounts - j - 1], asset(1), usd.amount(1000));\n         }\n      }\n\n      const auto end = high_resolution_clock::now();\n\n      const auto elapsed = duration_cast<milliseconds>(end - start);\n      wlog(\"Elapsed: ${c} ms\", (\"c\", elapsed.count()));\n\n      for (unsigned int i = 0; i < accounts; ++i)\n      {\n         const auto reward = get_market_fee_reward(registrators[i], usd);\n         BOOST_CHECK_GT(reward, 0);\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n"
  },
  {
    "path": "tests/performance/performance_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/init_unit_test_suite.hpp\"\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <graphene/db/simple_index.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n#include <cstdlib>\n#include <iostream>\n\nusing namespace graphene::chain;\n\nBOOST_FIXTURE_TEST_SUITE( performance_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( sigcheck_benchmark )\n{\n   fc::ecc::private_key nathan_key = fc::ecc::private_key::generate();\n   auto digest = fc::sha256::hash(\"hello\");\n   auto sig = nathan_key.sign_compact( digest );\n   auto start = fc::time_point::now();\n   const uint64_t cycles = 100000;\n   for( uint32_t i = 0; i < cycles; ++i )\n      fc::ecc::public_key( sig, digest );\n   auto end = fc::time_point::now();\n   auto elapsed = end-start;\n   wlog( \"Benchmark: verify ${sps} signatures/s\", (\"sps\",(cycles*1000000)/elapsed.count()) );\n}\n\n// See https://bitshares.org/blog/2015/06/08/measuring-performance/\n// (note this is not the original test mentioned in the above post, but was\n//  recreated later according to the description)\nBOOST_AUTO_TEST_CASE( one_hundred_k_benchmark )\n{ try {\n   ACTORS( (alice) );\n   fund( alice, asset(10000000) );\n   db._undo_db.disable(); // Blog post mentions replay, this implies no undo\n\n   const fc::ecc::private_key nathan_key = fc::ecc::private_key::generate();\n   const fc::ecc::public_key  nathan_pub = nathan_key.get_public_key();;\n   const auto& committee_account = account_id_type()(db);\n\n   const uint64_t cycles = 200000;\n   uint64_t total_time = 0;\n   uint64_t total_count = 0;\n   std::vector<account_id_type> accounts;\n   accounts.reserve( cycles+1 );\n   std::vector<asset_id_type> assets;\n   assets.reserve( cycles );\n\n   std::vector<signed_transaction> transactions;\n   transactions.reserve( cycles );\n\n   {\n      account_create_operation aco;\n      aco.name = \"a1\";\n      aco.registrar = committee_account.id;\n      aco.owner = authority( 1, public_key_type(nathan_pub), 1 );\n      aco.active = authority( 1, public_key_type(nathan_pub), 1 );\n      aco.options.memo_key = nathan_pub;\n      aco.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n      aco.options.num_committee = 0;\n      aco.options.num_witness = 0;\n      aco.fee = db.current_fee_schedule().calculate_fee( aco );\n      trx.clear();\n      test::set_expiration( db, trx );\n      for( uint32_t i = 0; i < cycles; ++i )\n      {\n         aco.name = \"a\" + fc::to_string(i);\n         trx.operations.push_back( aco );\n         transactions.push_back( trx );\n         trx.operations.clear();\n         ++total_count;\n      }\n\n      auto start = fc::time_point::now();\n      for( uint32_t i = 0; i < cycles; ++i )\n      {\n         auto result = db.apply_transaction( transactions[i], ~0 );\n         accounts[i] = result.operation_results[0].get<object_id_type>();\n      }\n      auto end = fc::time_point::now();\n      auto elapsed = end - start;\n      total_time += elapsed.count();\n      wlog( \"Create ${aps} accounts/s over ${total}ms\",\n            (\"aps\",(cycles*1000000)/elapsed.count())(\"total\",elapsed.count()/1000) );\n   }\n\n   {\n      accounts[cycles] = accounts[0];\n      transfer_operation to1;\n      to1.from = committee_account.id;\n      to1.amount = asset( 1000000 );\n      to1.fee = asset( 10 );\n      transfer_operation to2;\n      to2.amount = asset( 100 );\n      to2.fee = asset( 10 );\n      for( uint32_t i = 0; i < cycles; ++i )\n      {\n         to1.to = accounts[i];\n         to2.from = accounts[i];\n         to2.to = accounts[i+1];\n         trx.operations.push_back( to1 );\n         ++total_count;\n         trx.operations.push_back( to2 );\n         ++total_count;\n         transactions[i] = trx;\n         trx.operations.clear();\n      }\n\n      auto start = fc::time_point::now();\n      for( uint32_t i = 0; i < cycles; ++i )\n         db.apply_transaction( transactions[i], ~0 );\n      auto end = fc::time_point::now();\n      auto elapsed = end - start;\n      total_time += elapsed.count();\n      wlog( \"${aps} transfers/s over ${total}ms\",\n            (\"aps\",(2*cycles*1000000)/elapsed.count())(\"total\",elapsed.count()/1000) );\n      trx.clear();\n   }\n\n   {\n      asset_create_operation aco;\n      aco.fee = asset( 100000 );\n      aco.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type(1) );\n      for( uint32_t i = 0; i < cycles; ++i )\n      {\n         aco.issuer = accounts[i];\n         aco.symbol = \"ASSET\" + fc::to_string( i );\n         trx.operations.push_back( aco );\n         ++total_count;\n         transactions[i] = trx;\n         trx.operations.clear();\n      }\n\n      auto start = fc::time_point::now();\n      for( uint32_t i = 0; i < cycles; ++i )\n      {\n         auto result = db.apply_transaction( transactions[i], ~0 );\n         assets[i] = result.operation_results[0].get<object_id_type>();\n      }\n      auto end = fc::time_point::now();\n      auto elapsed = end - start;\n      total_time += elapsed.count();\n      wlog( \"${aps} asset create/s over ${total}ms\",\n            (\"aps\",(cycles*1000000)/elapsed.count())(\"total\",elapsed.count()/1000) );\n      trx.clear();\n   }\n\n   {\n      asset_issue_operation aio;\n      aio.fee = asset( 10 );\n      for( uint32_t i = 0; i < cycles; ++i )\n      {\n         aio.issuer = accounts[i];\n         aio.issue_to_account = accounts[i+1];\n         aio.asset_to_issue = asset( 10, assets[i] );\n         trx.operations.push_back( aio );\n         ++total_count;\n         transactions[i] = trx;\n         trx.operations.clear();\n      }\n\n      auto start = fc::time_point::now();\n      for( uint32_t i = 0; i < cycles; ++i )\n         db.apply_transaction( transactions[i], ~0 );\n      auto end = fc::time_point::now();\n      auto elapsed = end - start;\n      total_time += elapsed.count();\n      wlog( \"${aps} issuances/s over ${total}ms\",\n            (\"aps\",(cycles*1000000)/elapsed.count())(\"total\",elapsed.count()/1000) );\n      trx.clear();\n   }\n\n   wlog( \"${total} operations in ${total_time}ms => ${avg} ops/s on average\",\n         (\"total\",total_count)(\"total_time\",total_time/1000)\n         (\"avg\",(total_count*1000000)/total_time) );\n\n   db._undo_db.enable();\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/api_limit_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/database_api.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(api_limit_tests, database_fixture)\n\nBOOST_AUTO_TEST_CASE( api_limit_get_key_references ){\n   try{\n   const int num_keys = 210;\n   const int num_keys1 = 2;\n   vector< private_key_type > numbered_private_keys;\n   vector< public_key_type >  numbered_key_id;\n   numbered_private_keys.reserve( num_keys );\n\n   graphene::app::application_options opt1 = app.get_options();\n   opt1.has_api_helper_indexes_plugin = false;\n   graphene::app::database_api db_api1( db, &opt1 );\n   BOOST_CHECK_THROW( db_api1.get_key_references(numbered_key_id), fc::exception );\n\n   graphene::app::application_options opt = app.get_options();\n   opt.has_api_helper_indexes_plugin = true;\n   graphene::app::database_api db_api( db, &opt );\n\n   for( int i=0; i<num_keys1; i++ )\n   {\n      private_key_type privkey = generate_private_key(std::string(\"key_\") + std::to_string(i));\n      public_key_type pubkey = privkey.get_public_key();\n      numbered_private_keys.push_back( privkey );\n      numbered_key_id.push_back( pubkey );\n   }\n   vector< flat_set<account_id_type> > final_result=db_api.get_key_references(numbered_key_id);\n   BOOST_REQUIRE_EQUAL( final_result.size(), 2u );\n   numbered_private_keys.reserve( num_keys );\n   for( int i=num_keys1; i<num_keys; i++ )\n   {\n       private_key_type privkey = generate_private_key(std::string(\"key_\") + std::to_string(i));\n       public_key_type pubkey = privkey.get_public_key();\n       numbered_private_keys.push_back( privkey );\n       numbered_key_id.push_back( pubkey );\n   }\n   GRAPHENE_CHECK_THROW(db_api.get_key_references(numbered_key_id), fc::exception);\n   }catch (fc::exception& e) {\n   edump((e.to_detail_string()));\n   throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_get_full_accounts ) {\n\n   try {\n      ACTOR(alice);\n\n      graphene::app::application_options opt = app.get_options();\n      opt.has_api_helper_indexes_plugin = true;\n      graphene::app::database_api db_api( db, &opt );\n\n      vector<string> accounts;\n\n      for( size_t i = 0; i < 50; ++i )\n      {\n         string account_name = \"testaccount\" + fc::to_string(i);\n         create_account( account_name );\n         accounts.push_back( account_name );\n      }\n      accounts.push_back( \"alice\" );\n\n      transfer_operation op;\n      op.from = alice_id;\n      op.amount = asset(1);\n      for( size_t i = 0; i < 501; ++i )\n      {\n         propose( op, alice_id );\n      }\n\n      // Too many accounts\n      GRAPHENE_CHECK_THROW(db_api.get_full_accounts(accounts, false), fc::exception);\n\n      accounts.erase(accounts.begin());\n      auto full_accounts = db_api.get_full_accounts(accounts, false);\n      BOOST_CHECK(full_accounts.size() == 50);\n\n      // The default max list size is 500\n      BOOST_REQUIRE( full_accounts.find(\"alice\") != full_accounts.end() );\n      BOOST_CHECK_EQUAL( full_accounts[\"alice\"].proposals.size(), 500u );\n      BOOST_CHECK( full_accounts[\"alice\"].more_data_available.proposals );\n      BOOST_REQUIRE( full_accounts.find(\"testaccount9\") != full_accounts.end() );\n      BOOST_CHECK_EQUAL( full_accounts[\"testaccount9\"].proposals.size(), 0 );\n      BOOST_CHECK( !full_accounts[\"testaccount9\"].more_data_available.proposals );\n\n      // not an account\n      accounts.erase(accounts.begin());\n      accounts.push_back(\"nosuchaccount\");\n\n      // non existing accounts will be ignored in the results\n      full_accounts = db_api.get_full_accounts(accounts, false);\n      BOOST_CHECK(full_accounts.size() == 49);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_get_limit_orders ){\n   try{\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   //account_id_type() do 3 ops\n   create_bitasset(\"USD\", account_id_type());\n   create_account(\"dan\");\n   create_account(\"bob\");\n   asset_id_type bit_jmj_id = create_bitasset(\"JMJBIT\").get_id();\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n   GRAPHENE_CHECK_THROW(db_api.get_limit_orders(std::string(static_cast<object_id_type>(asset_id_type())),\n      std::string(static_cast<object_id_type>(bit_jmj_id)), 370), fc::exception);\n   vector<limit_order_object>  limit_orders =db_api.get_limit_orders(std::string(\n      static_cast<object_id_type>(asset_id_type())),\n      std::string(static_cast<object_id_type>(bit_jmj_id)), 340);\n   BOOST_REQUIRE_EQUAL( limit_orders.size(), 0u);\n\n   }catch (fc::exception& e) {\n   edump((e.to_detail_string()));\n   throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_get_limit_orders_by_account ){\n   try{\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   const auto& test = create_user_issued_asset(\"TESTASSET\");\n   create_sell_order( account_id_type(), asset(1,asset_id_type()), test.amount(1) );\n   GRAPHENE_CHECK_THROW(db_api.get_limit_orders_by_account(\n      std::string(static_cast<object_id_type>(account_id_type())), 160), fc::exception);\n   vector<limit_order_object>  limit_orders =db_api.get_limit_orders_by_account(\n      std::string(static_cast<object_id_type>(account_id_type())), 145);\n   BOOST_REQUIRE_EQUAL( limit_orders.size(), 1u);\n\n   }catch (fc::exception& e) {\n   edump((e.to_detail_string()));\n   throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_get_call_orders ){\n   try{\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   //account_id_type() do 3 ops\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   account_id_type nathan_id = create_account(\"nathan\", nathan_private_key.get_public_key()).get_id();\n   transfer(account_id_type(), nathan_id, asset(100));\n   asset_id_type bitusd_id = create_bitasset(\n         \"USDBIT\", nathan_id, 100, disable_force_settle).get_id();\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n   BOOST_CHECK( bitusd_id(db).is_market_issued() );\n   GRAPHENE_CHECK_THROW(db_api.get_call_orders(std::string(static_cast<object_id_type>(bitusd_id)),\n         370), fc::exception);\n   vector< call_order_object>  call_order =db_api.get_call_orders(std::string(\n         static_cast<object_id_type>(bitusd_id)), 340);\n   BOOST_REQUIRE_EQUAL( call_order.size(), 0u);\n   }catch (fc::exception& e) {\n   edump((e.to_detail_string()));\n   throw;\n   }\n}\nBOOST_AUTO_TEST_CASE( api_limit_get_settle_orders ){\n   try{\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   //account_id_type() do 3 ops\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   account_id_type nathan_id = create_account(\"nathan\", nathan_private_key.get_public_key()).get_id();\n   transfer(account_id_type(), nathan_id, asset(100));\n   asset_id_type bitusd_id = create_bitasset(\n         \"USDBIT\", nathan_id, 100, disable_force_settle).get_id();\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n   GRAPHENE_CHECK_THROW(db_api.get_settle_orders(\n         std::string(static_cast<object_id_type>(bitusd_id)), 370), fc::exception);\n   vector<force_settlement_object> result =db_api.get_settle_orders(\n         std::string(static_cast<object_id_type>(bitusd_id)), 340);\n   BOOST_REQUIRE_EQUAL( result.size(), 0u);\n   }catch (fc::exception& e) {\n   edump((e.to_detail_string()));\n   throw;\n   }\n}\nBOOST_AUTO_TEST_CASE( api_limit_get_order_book ){\n try {\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   auto dan_private_key = generate_private_key(\"dan\");\n   account_id_type nathan_id = create_account(\"nathan\", nathan_private_key.get_public_key()).get_id();\n   account_id_type dan_id = create_account(\"dan\", dan_private_key.get_public_key()).get_id();\n   transfer(account_id_type(), nathan_id, asset(100));\n   transfer(account_id_type(), dan_id, asset(100));\n   asset_id_type bitusd_id = create_user_issued_asset( \"USDBIT\", nathan_id(db), charge_market_fee).get_id();\n   asset_id_type bitdan_id = create_user_issued_asset( \"DANBIT\", dan_id(db), charge_market_fee).get_id();\n   issue_uia( nathan_id, asset(100, bitusd_id) );\n   issue_uia( dan_id, asset(100, bitdan_id) );\n   create_sell_order( nathan_id, asset(100, bitusd_id), asset(10000, bitdan_id) );\n   create_sell_order( dan_id, asset(100, bitdan_id), asset(10000, bitusd_id) );\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n   GRAPHENE_CHECK_THROW(db_api.get_order_book(std::string(static_cast<object_id_type>(bitusd_id)),\n         std::string(static_cast<object_id_type>(bitdan_id)),89), fc::exception);\n   graphene::app::order_book result =db_api.get_order_book(std::string(\n         static_cast<object_id_type>(bitusd_id)), std::string(static_cast<object_id_type>(bitdan_id)),78);\n   BOOST_REQUIRE_EQUAL( result.bids.size(), 1u );\n   BOOST_CHECK( result.bids.front().owner_name == \"nathan\" );\n   BOOST_REQUIRE_EQUAL( result.asks.size(), 1u );\n   BOOST_CHECK( result.asks.front().owner_name == \"dan\" );\n } catch (fc::exception& e) {\n   edump((e.to_detail_string()));\n   throw;\n }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_lookup_accounts ) {\n   try{\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      ACTOR(bob);\n      GRAPHENE_CHECK_THROW(db_api.lookup_accounts(\"bob\",220), fc::exception);\n      auto result =db_api.lookup_accounts(\"bob\",190);\n      BOOST_REQUIRE_EQUAL( result.size(), 17u);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_lookup_witness_accounts ) {\n   try{\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      ACTORS((bob)) ;\n      GRAPHENE_CHECK_THROW(db_api.lookup_witness_accounts(\"bob\",220), fc::exception);\n      auto result =db_api.lookup_witness_accounts(\"bob\",190);\n      BOOST_REQUIRE_EQUAL( result.size(), 10u);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE( api_limit_get_full_accounts2 ) {\n\n   try {\n      ACTOR(alice);\n\n      graphene::app::application_options opt = app.get_options();\n      opt.has_api_helper_indexes_plugin = true;\n      graphene::app::database_api db_api( db, &opt );\n\n      vector<string> accounts;\n      for (int i=0; i<200; i++) {\n         std::string acct_name = \"mytempacct\" + std::to_string(i);\n         const account_object& account_name=create_account(acct_name);\n         accounts.push_back(account_name.name);\n      }\n      accounts.push_back( \"alice\" );\n\n      transfer_operation op;\n      op.from = alice_id;\n      op.amount = asset(1);\n      for( size_t i = 0; i < 501; ++i )\n      {\n         propose( op, alice_id );\n      }\n\n      GRAPHENE_CHECK_THROW(db_api.get_full_accounts(accounts, false), fc::exception);\n      accounts.erase(accounts.begin());\n      auto full_accounts = db_api.get_full_accounts(accounts, false);\n      BOOST_REQUIRE_EQUAL(full_accounts.size(), 200u);\n\n      // The updated max list size is 120\n      BOOST_REQUIRE( full_accounts.find(\"alice\") != full_accounts.end() );\n      BOOST_CHECK_EQUAL( full_accounts[\"alice\"].proposals.size(), 120u );\n      BOOST_CHECK( full_accounts[\"alice\"].more_data_available.proposals );\n      BOOST_REQUIRE( full_accounts.find(\"mytempacct9\") != full_accounts.end() );\n      BOOST_CHECK_EQUAL( full_accounts[\"mytempacct9\"].proposals.size(), 0 );\n      BOOST_CHECK( !full_accounts[\"mytempacct9\"].more_data_available.proposals );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_withdraw_permissions_by_recipient){\n   try{\n      graphene::app::database_api db_api( db, &app.get_options());\n      ACTORS((bob)) ;\n      withdraw_permission_id_type withdraw_permission;\n      GRAPHENE_CHECK_THROW(db_api.get_withdraw_permissions_by_recipient(\n         \"bob\",withdraw_permission, 251), fc::exception);\n      vector<withdraw_permission_object> result =db_api.get_withdraw_permissions_by_recipient(\n         \"bob\",withdraw_permission,250);\n      BOOST_REQUIRE_EQUAL( result.size(), 0u);\n   }catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_withdraw_permissions_by_giver){\n   try{\n      graphene::app::database_api db_api( db, &app.get_options());\n      ACTORS((bob)) ;\n      withdraw_permission_id_type withdraw_permission;\n      GRAPHENE_CHECK_THROW(db_api.get_withdraw_permissions_by_giver(\n         \"bob\",withdraw_permission, 251), fc::exception);\n      vector<withdraw_permission_object> result =db_api.get_withdraw_permissions_by_giver(\n         \"bob\",withdraw_permission,250);\n      BOOST_REQUIRE_EQUAL( result.size(), 0u);\n   }catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_trade_history_by_sequence){\n   try{\n      app.enable_plugin(\"market_history\");\n      graphene::app::application_options opt=app.get_options();\n      opt.has_market_history_plugin = true;\n      graphene::app::database_api db_api( db, &opt);\n      const auto& bitusd = create_bitasset(\"USDBIT\");\n      asset_id_type asset_1, asset_2;\n      asset_1 = bitusd.id;\n      asset_2 = asset_id_type();\n      GRAPHENE_CHECK_THROW(db_api.get_trade_history_by_sequence(\n         std::string( static_cast<object_id_type>(asset_1)),\n         std::string( static_cast<object_id_type>(asset_2)),\n         0,fc::time_point_sec(), 251), fc::exception);\n      vector<graphene::app::market_trade> result =db_api.get_trade_history_by_sequence(\n         std::string( static_cast<object_id_type>(asset_1)),\n         std::string( static_cast<object_id_type>(asset_2)),\n         0,fc::time_point_sec(),250);\n      BOOST_REQUIRE_EQUAL( result.size(), 0u);\n   }catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(api_limit_get_trade_history){\n   try{\n      app.enable_plugin(\"market_history\");\n      graphene::app::application_options opt=app.get_options();\n      opt.has_market_history_plugin = true;\n      graphene::app::database_api db_api( db, &opt);\n      const auto& bitusd = create_bitasset(\"USDBIT\");\n      asset_id_type asset_1, asset_2;\n      asset_1 = bitusd.id;\n      asset_2 = asset_id_type();\n      GRAPHENE_CHECK_THROW(db_api.get_trade_history(\n                              std::string( static_cast<object_id_type>(asset_1)),\n                              std::string( static_cast<object_id_type>(asset_2)),\n                              fc::time_point_sec(),fc::time_point_sec(),\n                              251), fc::exception);\n      vector<graphene::app::market_trade> result =db_api.get_trade_history(\n         std::string( static_cast<object_id_type>(asset_1)),\n         std::string( static_cast<object_id_type>(asset_2)),\n         fc::time_point_sec(),fc::time_point_sec(),250);\n      BOOST_REQUIRE_EQUAL( result.size(), 0u);\n   }catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_top_markets){\n   try{\n      app.enable_plugin(\"market_history\");\n      graphene::app::application_options opt=app.get_options();\n      opt.has_market_history_plugin = true;\n      graphene::app::database_api db_api( db, &opt);\n      const auto& bitusd = create_bitasset(\"USDBIT\");\n      asset_id_type asset_1, asset_2;\n      asset_1 = bitusd.id;\n      asset_2 = asset_id_type();\n      GRAPHENE_CHECK_THROW(db_api.get_top_markets(251), fc::exception);\n      vector<graphene::app::market_ticker> result =db_api.get_top_markets(250);\n      BOOST_REQUIRE_EQUAL( result.size(), 0u);\n   }catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_collateral_bids) {\n   try {\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n\n      int64_t init_balance = 10000;\n      ///account_id_type borrower, borrower2, feedproducer;\n      asset_id_type swan, back;\n      ACTORS((borrower) (borrower2) (feedproducer)) ;\n      const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n      swan = bitusd.id;\n      back = asset_id_type();\n      update_feed_producers(swan(db), {feedproducer_id});\n      transfer(committee_account, borrower_id, asset(init_balance));\n      transfer(committee_account, borrower2_id, asset(init_balance));\n\n      generate_blocks( HARDFORK_CORE_216_TIME );\n      generate_block();\n\n      price_feed feed;\n      feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      feed.settlement_price = swan(db).amount(1) / back(db).amount(1);\n      publish_feed(swan(db), feedproducer_id(db), feed);\n      // start out with 2:1 collateral\n      borrow(borrower_id(db), swan(db).amount(10), back(db).amount(2*10));\n      borrow(borrower2_id(db), swan(db).amount(10), back(db).amount(4*10));\n      //feed 1: 2\n      feed.settlement_price = swan(db).amount(1) / back(db).amount(2);\n      publish_feed(swan(db), feedproducer_id(db), feed);\n\n      // this sell order is designed to trigger a black swan\n\n      create_sell_order( borrower2_id(db), swan(db).amount(1), back(db).amount(3) );\n      BOOST_CHECK( swan(db).bitasset_data(db).is_globally_settled() );\n      //making 3 collateral bids\n      for (int i=0; i<3; i++) {\n\n         std::string acct_name = \"mytempacct\" + std::to_string(i);\n         account_id_type account_id=create_account(acct_name).get_id();\n         transfer(committee_account, account_id, asset(init_balance));\n         bid_collateral(account_id(db), back(db).amount(10), swan(db).amount(1));\n      }\n      auto swan_symbol = swan(db).symbol;\n\n\n      //validating normal case; total_bids =3 ; result_bids=3\n      vector<collateral_bid_object> result_bids = db_api.get_collateral_bids(swan_symbol, 250, 0);\n      BOOST_CHECK_EQUAL( 3u, result_bids.size() );\n\n      //verify skip /// inefficient code test\n      //skip < total_bids; skip=1; total_bids =3 ; result_bids=2\n      result_bids = db_api.get_collateral_bids(swan_symbol, 250, 1);\n      BOOST_CHECK_EQUAL( 2u, result_bids.size() );\n      //skip= total_bids; skip=3; total_bids =3 ; result_bids=0\n      result_bids = db_api.get_collateral_bids(swan_symbol, 250, 3);\n      BOOST_CHECK_EQUAL( 0u, result_bids.size() );\n      //skip> total_bids; skip=4; total_bids =3 ; result_bids=0\n      result_bids = db_api.get_collateral_bids(swan_symbol, 250, 4);\n      BOOST_CHECK_EQUAL( 0u, result_bids.size() );\n\n      //verify limit // inefficient code test\n      //limit= api_limit\n      for (int i=3; i<255; i++) {\n         std::string acct_name = \"mytempacct\" + std::to_string(i);\n         account_id_type account_id=create_account(acct_name).get_id();\n         transfer(committee_account, account_id, asset(init_balance));\n         bid_collateral(account_id(db), back(db).amount(10), swan(db).amount(1));\n      }\n      result_bids=db_api.get_collateral_bids(swan_symbol, 250, 0);\n      BOOST_CHECK_EQUAL( 250u, result_bids.size() );\n      //limit> api_limit throw error\n      GRAPHENE_CHECK_THROW(db_api.get_collateral_bids(swan_symbol, 253, 3), fc::exception);\n\n   }\n   catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_account_limit_orders) {\n   try {\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      ACTORS((seller));\n      const auto &bitcny = create_bitasset(\"CNY\");\n      const auto &core = asset_id_type()(db);\n\n      int64_t init_balance(10000000);\n      transfer(committee_account, seller_id, asset(init_balance));\n\n      /// Create  versatile orders\n      for (size_t i = 0; i < 250; ++i) {\n         BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250+i)));\n      }\n\n\n      std::vector<limit_order_object> results=db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\",250);\n      BOOST_REQUIRE_EQUAL( results.size(), 250u);\n      GRAPHENE_CHECK_THROW( db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\",251), fc::exception);\n\n   }\n   catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE( api_limit_lookup_vote_ids ) {\n   try{\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      ACTORS( (connie)(whitney)(wolverine) );\n      fund(connie);\n      upgrade_to_lifetime_member(connie);\n      fund(whitney);\n      upgrade_to_lifetime_member(whitney);\n      fund(wolverine);\n      upgrade_to_lifetime_member(wolverine);\n      const auto& committee = create_committee_member( connie );\n      const auto& witness = create_witness( whitney );\n      const auto& worker = create_worker( wolverine_id );\n      std::vector<vote_id_type> votes;\n      votes.push_back( committee.vote_id );\n      votes.push_back( witness.vote_id );\n      const auto results = db_api.lookup_vote_ids( votes );\n      BOOST_REQUIRE_EQUAL( results.size(), 2u);\n      votes.push_back( worker.vote_for );\n      GRAPHENE_CHECK_THROW(db_api.lookup_vote_ids(votes), fc::exception);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( api_limit_lookup_committee_member_accounts ) {\n   try{\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      ACTORS((bob));\n      GRAPHENE_CHECK_THROW(db_api.lookup_committee_member_accounts(\"bob\",220), fc::exception);\n      auto result =db_api.lookup_committee_member_accounts(\"bob\",190);\n      BOOST_REQUIRE_EQUAL( result.size(), 10u);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/app_util_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/app/util.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::app;\n\nusing fc::uint128_t;\n\nBOOST_AUTO_TEST_SUITE(app_util_tests)\n\nBOOST_AUTO_TEST_CASE(uint128_amount_to_string_test) {\n\n   fc::uint128_t max_u64( std::numeric_limits<uint64_t>::max() );\n   fc::uint128_t min_gt_u64 = max_u64 + 1;\n   fc::uint128_t one_u128 = max_u64 * 10;\n   fc::uint128_t max_u128 = std::numeric_limits<fc::uint128_t>::max();\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          0), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          0), \"1\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        0), \"100\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    0), \"1024000\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 0), \"1234567890\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    0), \"18446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 0), \"18446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   0), \"184467440737095516150\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   0), \"340282366920938463463374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          1), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          1), \"0.1\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        1), \"10\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    1), \"102400\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 1), \"123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    1), \"1844674407370955161.5\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 1), \"1844674407370955161.6\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   1), \"18446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   1), \"34028236692093846346337460743176821145.5\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          2), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          2), \"0.01\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        2), \"1\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    2), \"10240\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 2), \"12345678.9\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    2), \"184467440737095516.15\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 2), \"184467440737095516.16\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   2), \"1844674407370955161.5\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   2), \"3402823669209384634633746074317682114.55\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          3), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          3), \"0.001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        3), \"0.1\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    3), \"1024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 3), \"1234567.89\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    3), \"18446744073709551.615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 3), \"18446744073709551.616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   3), \"184467440737095516.15\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   3), \"340282366920938463463374607431768211.455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          4), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          4), \"0.0001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        4), \"0.01\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    4), \"102.4\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 4), \"123456.789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    4), \"1844674407370955.1615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 4), \"1844674407370955.1616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   4), \"18446744073709551.615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   4), \"34028236692093846346337460743176821.1455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          9), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          9), \"0.000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        9), \"0.0000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    9), \"0.001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 9), \"1.23456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    9), \"18446744073.709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 9), \"18446744073.709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   9), \"184467440737.09551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   9), \"340282366920938463463374607431.768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          10), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          10), \"0.0000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        10), \"0.00000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    10), \"0.0001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 10), \"0.123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    10), \"1844674407.3709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 10), \"1844674407.3709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   10), \"18446744073.709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   10), \"34028236692093846346337460743.1768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          19), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          19), \"0.0000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        19), \"0.00000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    19), \"0.0000000000001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 19), \"0.000000000123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    19), \"1.8446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 19), \"1.8446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   19), \"18.446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   19), \"34028236692093846346.3374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          20), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          20), \"0.00000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        20), \"0.000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    20), \"0.00000000000001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 20), \"0.0000000000123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    20), \"0.18446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 20), \"0.18446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   20), \"1.8446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   20), \"3402823669209384634.63374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          21), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          21), \"0.000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        21), \"0.0000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    21), \"0.000000000000001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 21), \"0.00000000000123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    21), \"0.018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 21), \"0.018446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   21), \"0.18446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   21), \"340282366920938463.463374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          38), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          38), \"0.00000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        38), \"0.000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    38), \"0.00000000000000000000000000000001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 38), \"0.0000000000000000000000000000123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    38), \"0.00000000000000000018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 38), \"0.00000000000000000018446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   38), \"0.0000000000000000018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   38), \"3.40282366920938463463374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          39), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          39), \"0.000000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        39), \"0.0000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    39), \"0.000000000000000000000000000000001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 39), \"0.00000000000000000000000000000123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    39), \"0.000000000000000000018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 39), \"0.000000000000000000018446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   39), \"0.00000000000000000018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   39), \"0.340282366920938463463374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          40), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          40), \"0.0000000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        40), \"0.00000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1024000,    40), \"0.0000000000000000000000000000000001024\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1234567890, 40), \"0.000000000000000000000000000000123456789\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u64,    40), \"0.0000000000000000000018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( min_gt_u64, 40), \"0.0000000000000000000018446744073709551616\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( one_u128,   40), \"0.000000000000000000018446744073709551615\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   40), \"0.0340282366920938463463374607431768211455\" );\n\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 0,          127), \"0\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 1,          127), \"0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( 100,        127), \"0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\" );\n   BOOST_CHECK_EQUAL( uint128_amount_to_string( max_u128,   127), \"0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000340282366920938463463374607431768211455\" );\n\n}\n\nBOOST_AUTO_TEST_CASE(price_to_string_throws) {\n\n   int64_t m = std::numeric_limits<int64_t>::max();\n   int64_t n = -1;\n   int64_t x = m / 10000;\n   int64_t y = m / 2;\n   int64_t z = m - 1;\n\n   int64_t a[11] = {n,0,1,2,3,10,200,x,y,z,m};\n   price p[11][11];\n   for( int i = 0; i < 11; ++i )\n      for( int j = 0; j < 11; ++j )\n         p[i][j] = price( asset( a[i] ), asset( a[j] ) );\n\n   for( int i = 0; i < 11; ++i )\n   {\n      for( int j = 0; j < 11; ++j )\n      {\n         price pr = p[i][j];\n         if( i == 0 )\n         {\n            GRAPHENE_REQUIRE_THROW( price_to_string( p[i][j], 0, 0 ), fc::exception );\n         }\n         else if( i == 1 )\n         {\n            BOOST_CHECK_EQUAL( price_to_string( p[i][j],  0,  0 ), \"0\" );\n            BOOST_CHECK_EQUAL( price_to_string( p[i][j],  0, 19 ), \"0\" );\n            BOOST_CHECK_EQUAL( price_to_string( p[i][j], 19,  0 ), \"0\" );\n            BOOST_CHECK_EQUAL( price_to_string( p[i][j], 19, 19 ), \"0\" );\n            BOOST_CHECK_EQUAL( price_to_string( p[i][j], 20, 20 ), \"0\" );\n         }\n         else\n         {\n            GRAPHENE_REQUIRE_THROW( price_to_string( p[i][j], 20, 0 ), fc::exception );\n            GRAPHENE_REQUIRE_THROW( price_to_string( p[i][j], 0, 20 ), fc::exception );\n         }\n         try {\n            if ( pr.base.amount == 0 || (pr.base.amount > 0 && pr.quote.amount >= 0 ) )\n            {\n               // idump( (i) (j) (pr) ); // for debugging\n               // These should not throw\n               // TODO: Verify results\n               BOOST_CHECK( !price_to_string( pr ,0,0).empty() );\n               BOOST_CHECK( !price_to_string( pr ,0,1).empty() );\n               BOOST_CHECK( !price_to_string( pr ,0,2).empty() );\n               BOOST_CHECK( !price_to_string( pr ,0,8).empty() );\n               BOOST_CHECK( !price_to_string( pr ,0,19).empty() );\n               BOOST_CHECK( !price_to_string( pr ,1,0).empty() );\n               BOOST_CHECK( !price_to_string( pr ,1,15).empty() );\n               BOOST_CHECK( !price_to_string( pr ,2,6).empty() );\n               BOOST_CHECK( !price_to_string( pr ,2,10).empty() );\n               BOOST_CHECK( !price_to_string( pr ,5,0).empty() );\n               BOOST_CHECK( !price_to_string( pr ,9,1).empty() );\n               BOOST_CHECK( !price_to_string( pr ,9,9).empty() );\n               BOOST_CHECK( !price_to_string( pr ,9,19).empty() );\n               BOOST_CHECK( !price_to_string( pr ,18,10).empty() );\n               BOOST_CHECK( !price_to_string( pr ,18,13).empty() );\n               BOOST_CHECK( !price_to_string( pr ,18,19).empty() );\n               BOOST_CHECK( !price_to_string( pr ,19,0).empty() );\n               BOOST_CHECK( !price_to_string( pr ,19,7).empty() );\n               BOOST_CHECK( !price_to_string( pr ,19,19).empty() );\n               price new_price = p[j][i];\n               if (pr.quote.amount >= 0)\n                  BOOST_CHECK( !price_diff_percent_string( pr, new_price ).empty() );\n               else\n                  GRAPHENE_REQUIRE_THROW( price_diff_percent_string( pr, new_price ), fc::exception );\n            } else {\n               GRAPHENE_REQUIRE_THROW( price_to_string( pr, 0, 0 ), fc::exception );\n            }\n         } catch(fc::exception& fcx) {\n            BOOST_FAIL( \"FC Exception logging price_to_string: \" + fcx.to_detail_string() );\n         } catch(...) {\n            BOOST_FAIL( \"Uncaught exception in price_to_string. i=\" + std::to_string(i) + \" j=\" + std::to_string(j));\n         }\n      }\n   }\n}\n\n/**\n * Verify that price_to_string comes back with the correct results. Put edge cases here.\n */\nBOOST_AUTO_TEST_CASE(price_to_string_verify)\n{\n   try\n   {\n      BOOST_CHECK_EQUAL( price_to_string( price{ asset(1), asset(1) }, 0, 0 ), \"1\" );\n      BOOST_CHECK_EQUAL( price_to_string( price{ asset(10), asset(10) }, 0, 0), \"1\" );\n      int64_t mx = std::numeric_limits<int64_t>::max();\n      BOOST_CHECK_EQUAL( price_to_string( price{ asset(mx), asset(mx) }, 0, 0), \"1\" );\n      BOOST_CHECK_EQUAL( price_to_string( price{ asset(1), asset(mx) }, 0, 0), \"0.0000000000000000001\" );\n      BOOST_CHECK_EQUAL( price_to_string( price{ asset(mx), asset(1) }, 0, 0), \"9223372036854775807\" );\n   } \n   catch (fc::exception& fx)\n   {\n      BOOST_FAIL( \"FC Exception: \" + fx.to_detail_string() );\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/asset_api_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/app/api.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::app;\n\nBOOST_FIXTURE_TEST_SUITE(asset_api_tests, database_fixture)\n\nBOOST_AUTO_TEST_CASE( asset_holders )\n{\n   graphene::app::asset_api asset_api(app);\n\n   // create an asset and some accounts\n   create_bitasset(\"USD\", account_id_type());\n   auto dan = create_account(\"dan\");\n   auto bob = create_account(\"bob\");\n   auto alice = create_account(\"alice\");\n\n   // send them some bts\n   transfer(account_id_type()(db), dan, asset(100));\n   transfer(account_id_type()(db), alice, asset(200));\n   transfer(account_id_type()(db), bob, asset(300));\n\n   // make call\n   auto holders = asset_api.get_asset_holders( std::string( asset_id_type() ), 0, 100);\n   BOOST_CHECK_EQUAL(holders.size(), 4u);\n\n   // by now we can guarantee the order\n   BOOST_CHECK(holders[0].name == \"committee-account\");\n   BOOST_CHECK(holders[1].name == \"bob\");\n   BOOST_CHECK(holders[2].name == \"alice\");\n   BOOST_CHECK(holders[3].name == \"dan\");\n}\nBOOST_AUTO_TEST_CASE( api_limit_get_asset_holders )\n{\n   graphene::app::asset_api asset_api(app);\n\n   // create an asset and some accounts\n   create_bitasset(\"USD\", account_id_type());\n   auto dan = create_account(\"dan\");\n   auto bob = create_account(\"bob\");\n   auto alice = create_account(\"alice\");\n\n   // send them some bts\n   transfer(account_id_type()(db), dan, asset(100));\n   transfer(account_id_type()(db), alice, asset(200));\n   transfer(account_id_type()(db), bob, asset(300));\n\n   // make call\n   GRAPHENE_CHECK_THROW(asset_api.get_asset_holders(std::string( asset_id_type() ), 0, 260), fc::exception);\n   auto holders = asset_api.get_asset_holders(std::string( asset_id_type() ), 0, 210);\n   BOOST_REQUIRE_EQUAL( holders.size(), 4u );\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/asset_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Bitshares Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n#include <string>\n#include <cmath>\n#include <graphene/chain/asset_object.hpp>\n\nBOOST_AUTO_TEST_SUITE(asset_tests)\n\nBOOST_AUTO_TEST_CASE( asset_to_from_string )\n{\n   std::string positive_results[19];\n   positive_results[0]  = \"12345\";\n   positive_results[1]  = \"1234.5\";\n   positive_results[2]  = \"123.45\";\n   positive_results[3]  = \"12.345\";\n   positive_results[4]  = \"1.2345\";\n   positive_results[5]  = \"0.12345\";\n   positive_results[6]  = \"0.012345\";\n   positive_results[7]  = \"0.0012345\";\n   positive_results[8]  = \"0.00012345\";\n   positive_results[9]  = \"0.000012345\";\n   positive_results[10] = \"0.0000012345\";\n   positive_results[11] = \"0.00000012345\";\n   positive_results[12] = \"0.000000012345\";\n   positive_results[13] = \"0.0000000012345\";\n   positive_results[14] = \"0.00000000012345\";\n   positive_results[15] = \"0.000000000012345\";\n   positive_results[16] = \"0.0000000000012345\";\n   positive_results[17] = \"0.00000000000012345\";\n   positive_results[18] = \"0.000000000000012345\";\n   std::string negative_results[19];\n   for(int i = 0; i < 19; ++i)\n   {\n      negative_results[i] = \"-\" + positive_results[i];\n   }\n   graphene::chain::asset_object test_obj;\n   graphene::chain::share_type amt12345 = 12345;\n   BOOST_TEST_MESSAGE( \"Testing positive numbers\" );\n   for (int i = 0; i < 19; i++)\n   {\n      test_obj.precision = i;\n      BOOST_CHECK_EQUAL(positive_results[i], test_obj.amount_to_string(amt12345));\n   }\n   BOOST_TEST_MESSAGE( \"Testing negative numbers\" );\n   for (int i = 0; i < 19; i++)\n   {\n      test_obj.precision = i;\n      BOOST_CHECK_EQUAL(negative_results[i], test_obj.amount_to_string(amt12345 * -1));\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/authority_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/restriction_predicate.hpp>\n\n#include <graphene/db/simple_index.hpp>\n\n#include <fc/crypto/digest.hpp>\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( authority_tests, database_fixture )\n\nauto make_get_custom(const database& db) {\n   return [&db](account_id_type id, const operation& op, rejected_predicate_map* rejects) {\n      return db.get_viable_custom_authorities(id, op, rejects); };\n}\n\nBOOST_AUTO_TEST_CASE( simple_single_signature )\n{ try {\n   try {\n      fc::ecc::private_key nathan_key = fc::ecc::private_key::generate();\n      const account_object& nathan = create_account(\"nathan\", nathan_key.get_public_key());\n      const asset_object& core = asset_id_type()(db);\n      auto old_balance = fund(nathan);\n\n      transfer_operation op;\n      op.from = nathan.id;\n      op.to = account_id_type();\n      op.amount = core.amount(500);\n      trx.operations.push_back(op);\n      sign(trx, nathan_key);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast<int64_t>(old_balance - 500));\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( any_two_of_three )\n{\n   try {\n      fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      fc::ecc::private_key nathan_key3 = fc::ecc::private_key::regenerate(fc::digest(\"key3\"));\n      const account_object& nathan = create_account(\"nathan\", nathan_key1.get_public_key() );\n      const asset_object& core = asset_id_type()(db);\n      auto old_balance = fund(nathan);\n\n      try {\n         account_update_operation op;\n         op.account = nathan.id;\n         op.active = authority(2, public_key_type(nathan_key1.get_public_key()), 1, public_key_type(nathan_key2.get_public_key()), 1, public_key_type(nathan_key3.get_public_key()), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         sign(trx, nathan_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n      } FC_CAPTURE_AND_RETHROW ((nathan.active))\n\n      transfer_operation op;\n      op.from = nathan.id;\n      op.to = account_id_type();\n      op.amount = core.amount(500);\n      trx.operations.push_back(op);\n      sign(trx, nathan_key1);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      sign(trx, nathan_key2);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast<int64_t>(old_balance - 500));\n\n      trx.clear_signatures();\n      sign(trx, nathan_key2);\n      sign(trx, nathan_key3);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast<int64_t>(old_balance - 1000));\n\n      trx.clear_signatures();\n      sign(trx, nathan_key1);\n      sign(trx, nathan_key3);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast<int64_t>(old_balance - 1500));\n\n      trx.clear_signatures();\n      //sign(trx, fc::ecc::private_key::generate());\n      sign(trx,nathan_key3);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), static_cast<int64_t>(old_balance - 1500));\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( recursive_accounts )\n{\n   try {\n      fc::ecc::private_key parent1_key = fc::ecc::private_key::generate();\n      fc::ecc::private_key parent2_key = fc::ecc::private_key::generate();\n      const auto& core = asset_id_type()(db);\n\n      BOOST_TEST_MESSAGE( \"Creating parent1 and parent2 accounts\" );\n      const account_object& parent1 = create_account(\"parent1\", parent1_key.get_public_key());\n      const account_object& parent2 = create_account(\"parent2\", parent2_key.get_public_key());\n\n      BOOST_TEST_MESSAGE( \"Creating child account that requires both parent1 and parent2 to approve\" );\n      {\n         auto make_child_op = make_account(\"child\");\n         make_child_op.owner = authority(2, account_id_type(parent1.id), 1, account_id_type(parent2.id), 1);\n         make_child_op.active = authority(2, account_id_type(parent1.id), 1, account_id_type(parent2.id), 1);\n         trx.operations.push_back(make_child_op);\n         PUSH_TX( db, trx, ~0 );\n         trx.operations.clear();\n      }\n\n      const account_object& child = get_account(\"child\");\n      auto old_balance = fund(child);\n\n      BOOST_TEST_MESSAGE( \"Attempting to transfer with no signatures, should fail\" );\n      transfer_operation op; \n      op.from = child.id;\n      op.amount = core.amount(500);\n      trx.operations.push_back(op);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n\n      BOOST_TEST_MESSAGE( \"Attempting to transfer with parent1 signature, should fail\" );\n      sign(trx,parent1_key);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      trx.clear_signatures();\n\n      BOOST_TEST_MESSAGE( \"Attempting to transfer with parent2 signature, should fail\" );\n      sign(trx,parent2_key);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n\n      BOOST_TEST_MESSAGE( \"Attempting to transfer with parent1 and parent2 signature, should succeed\" );\n      sign(trx,parent1_key);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(child, core), static_cast<int64_t>(old_balance - 500));\n      trx.clear();\n\n      BOOST_TEST_MESSAGE( \"Adding a key for the child that can override parents\" );\n      fc::ecc::private_key child_key = fc::ecc::private_key::generate();\n      {\n         account_update_operation op;\n         op.account = child.id;\n         op.active = authority(2, account_id_type(parent1.id), 1, account_id_type(parent2.id), 1,\n                               public_key_type(child_key.get_public_key()), 2);\n         trx.operations.push_back(op);\n         sign(trx,parent1_key);\n         sign(trx,parent2_key);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         BOOST_REQUIRE_EQUAL(child.active.num_auths(), 3u);\n         trx.clear();\n      }\n\n      op.from = child.id;\n      op.to = account_id_type();\n      op.amount = core.amount(500);\n      trx.operations.push_back(op);\n\n      BOOST_TEST_MESSAGE( \"Attempting transfer with no signatures, should fail\" );\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      BOOST_TEST_MESSAGE( \"Attempting transfer just parent1, should fail\" );\n      sign(trx, parent1_key);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      trx.clear_signatures();\n      BOOST_TEST_MESSAGE( \"Attempting transfer just parent2, should fail\" );\n      sign(trx, parent2_key);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n\n      BOOST_TEST_MESSAGE( \"Attempting transfer both parents, should succeed\" );\n      sign(trx,  parent1_key);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(child, core), static_cast<int64_t>(old_balance - 1000));\n      trx.clear_signatures();\n\n      BOOST_TEST_MESSAGE( \"Attempting transfer with just child key, should succeed\" );\n      sign(trx, child_key);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(child, core), static_cast<int64_t>(old_balance - 1500));\n      trx.clear();\n\n      BOOST_TEST_MESSAGE( \"Creating grandparent account, parent1 now requires authority of grandparent\" );\n      auto grandparent = create_account(\"grandparent\");\n      fc::ecc::private_key grandparent_key = fc::ecc::private_key::generate();\n      {\n         account_update_operation op;\n         op.account = parent1.id;\n         op.active = authority(1, account_id_type(grandparent.id), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         op.account = grandparent.id;\n         op.active = authority(1, public_key_type(grandparent_key.get_public_key()), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         PUSH_TX( db, trx, ~0 );\n         trx.clear();\n      }\n\n      BOOST_TEST_MESSAGE( \"Attempt to transfer using old parent keys, should fail\" );\n      trx.operations.push_back(op);\n      sign(trx, parent1_key);\n      sign(trx, parent2_key);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      trx.clear_signatures();\n      sign( trx,  parent2_key  );\n      sign( trx,  grandparent_key  );\n\n      BOOST_TEST_MESSAGE( \"Attempt to transfer using parent2_key and grandparent_key\" );\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(child, core), static_cast<int64_t>(old_balance - 2000));\n      trx.clear();\n\n      BOOST_TEST_MESSAGE( \"Update grandparent account authority to be committee account\" );\n      {\n         account_update_operation op;\n         op.account = grandparent.id;\n         op.active = authority(1, account_id_type(), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         PUSH_TX( db, trx, ~0 );\n         trx.clear();\n      }\n\n      BOOST_TEST_MESSAGE( \"Create recursion depth failure\" );\n      trx.operations.push_back(op);\n      sign(trx, parent2_key);\n      sign(trx, grandparent_key);\n      sign(trx, init_account_priv_key);\n      //Fails due to recursion depth.\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n      BOOST_TEST_MESSAGE( \"verify child key can override recursion checks\" );\n      trx.clear_signatures();\n      sign(trx,  child_key);\n      PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n      BOOST_CHECK_EQUAL(get_balance(child, core), static_cast<int64_t>(old_balance - 2500));\n      trx.clear();\n\n      BOOST_TEST_MESSAGE( \"Verify a cycle fails\" );\n      {\n         account_update_operation op;\n         op.account = parent1.id;\n         op.active = authority(1, account_id_type(child.id), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         PUSH_TX( db, trx, ~0 );\n         trx.clear();\n      }\n\n      trx.operations.push_back(op);\n      sign(trx, parent2_key);\n      //Fails due to recursion depth.\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, database::skip_transaction_dupe_check ), fc::exception);\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( proposed_single_account )\n{\n   using namespace graphene::chain;\n   try {\n      INVOKE(any_two_of_three);\n\n      fc::ecc::private_key committee_key = init_account_priv_key;\n      fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      fc::ecc::private_key nathan_key3 = fc::ecc::private_key::regenerate(fc::digest(\"key3\"));\n\n      const account_object& moneyman = create_account(\"moneyman\", init_account_pub_key);\n      const account_object& nathan = get_account(\"nathan\");\n      const asset_object& core = asset_id_type()(db);\n\n      transfer(account_id_type()(db), moneyman, core.amount(1000000));\n\n      //Following any_two_of_three, nathan's active authority is satisfied by any two of {key1,key2,key3}\n      BOOST_TEST_MESSAGE( \"moneyman is creating proposal for nathan to transfer 100 CORE to moneyman\" );\n\n      transfer_operation transfer_op;\n      transfer_op.from = nathan.id;\n      transfer_op.to  = moneyman.get_id();\n      transfer_op.amount = core.amount(100); \n\n      proposal_create_operation op;\n      op.fee_paying_account = moneyman.id;\n      op.proposed_ops.emplace_back( transfer_op );\n      op.expiration_time =  db.head_block_time() + fc::days(1);\n                                     \n      asset nathan_start_balance = db.get_balance(nathan.get_id(), core.get_id());\n      {\n         vector<authority> other;\n         flat_set<account_id_type> active_set, owner_set;\n         operation_get_required_authorities(op, active_set, owner_set, other, false);\n         BOOST_CHECK_EQUAL(active_set.size(), 1lu);\n         BOOST_CHECK_EQUAL(owner_set.size(), 0lu);\n         BOOST_CHECK_EQUAL(other.size(), 0lu);\n         BOOST_CHECK(*active_set.begin() == moneyman.get_id());\n\n         active_set.clear();\n         other.clear();\n         operation_get_required_authorities(op.proposed_ops.front().op, active_set, owner_set, other, false);\n         BOOST_CHECK_EQUAL(active_set.size(), 1lu);\n         BOOST_CHECK_EQUAL(owner_set.size(), 0lu);\n         BOOST_CHECK_EQUAL(other.size(), 0lu);\n         BOOST_CHECK(*active_set.begin() == nathan.id);\n      }\n\n      trx.operations.push_back(op);\n      set_expiration( db, trx );\n\n      sign( trx,  init_account_priv_key  );\n      const proposal_object& proposal = db.get<proposal_object>(PUSH_TX( db, trx ).operation_results.front().get<object_id_type>());\n\n      BOOST_CHECK_EQUAL(proposal.required_active_approvals.size(), 1lu);\n      BOOST_CHECK_EQUAL(proposal.available_active_approvals.size(), 0lu);\n      BOOST_CHECK_EQUAL(proposal.required_owner_approvals.size(), 0lu);\n      BOOST_CHECK_EQUAL(proposal.available_owner_approvals.size(), 0lu);\n      BOOST_CHECK(*proposal.required_active_approvals.begin() == nathan.id);\n\n      proposal_update_operation pup;\n      pup.proposal = proposal.id;\n      pup.fee_paying_account = nathan.id;\n      BOOST_TEST_MESSAGE( \"Updating the proposal to have nathan's authority\" );\n      pup.active_approvals_to_add.insert(nathan.get_id());\n\n      trx.operations = {pup};\n      sign( trx,   committee_key  );\n      //committee may not add nathan's approval.\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception);\n      pup.active_approvals_to_add.clear();\n      pup.active_approvals_to_add.insert(account_id_type());\n      trx.operations = {pup};\n      sign( trx,   committee_key  );\n      //committee has no stake in the transaction.\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception);\n\n      trx.clear_signatures();\n      pup.active_approvals_to_add.clear();\n      pup.active_approvals_to_add.insert(nathan.get_id());\n      \n      trx.operations = {pup};\n      sign( trx,   nathan_key3  );\n      sign( trx,   nathan_key2  );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), nathan_start_balance.amount.value);\n      PUSH_TX( db, trx );\n      BOOST_CHECK_EQUAL(get_balance(nathan, core), nathan_start_balance.amount.value - 100);\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( proposal_failure )\n{\n   try\n   {\n      ACTORS( (bob) (alice) );\n\n      fund( bob,   asset(1000000) );\n      fund( alice, asset(1000000) );\n\n      // create proposal that will eventually fail due to lack of funds\n      transfer_operation top;\n      top.to = alice_id;\n      top.from = bob_id;\n      top.amount = asset(2000000);\n      proposal_create_operation pop;\n      pop.proposed_ops.push_back( { op_wrapper(top) } );\n      pop.expiration_time = db.head_block_time() + fc::days(1);\n      pop.fee_paying_account = bob_id;\n      trx.operations.push_back( pop );\n      trx.clear_signatures();\n      sign( trx, bob_private_key );\n      processed_transaction processed = PUSH_TX( db, trx );\n      proposal_object prop = db.get<proposal_object>(processed.operation_results.front().get<object_id_type>());\n      trx.clear();\n      generate_block();\n      // add signature\n      proposal_update_operation up_op;\n      up_op.proposal = prop.id;\n      up_op.fee_paying_account = bob_id;\n      up_op.active_approvals_to_add.emplace( bob_id );\n      trx.operations.push_back( up_op );\n      sign( trx, bob_private_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n\n      // check fail reason\n      const proposal_object& result = db.get<proposal_object>(prop.id);\n      BOOST_CHECK(!result.fail_reason.empty());\n      BOOST_CHECK_EQUAL( result.fail_reason.substr(0, 16), \"Assert Exception\");\n   }\n   FC_LOG_AND_RETHROW()\n}\n\n/// Verify that committee authority cannot be invoked in a normal transaction\nBOOST_AUTO_TEST_CASE( committee_authority )\n{ try {\n   fc::ecc::private_key nathan_key = fc::ecc::private_key::generate();\n   fc::ecc::private_key committee_key = init_account_priv_key;\n   const account_object nathan = create_account(\"nathan\", nathan_key.get_public_key());\n   const auto& global_params = db.get_global_properties().parameters;\n\n   generate_block();\n\n   // Signatures are for suckers.\n   db.modify(db.get_global_properties(), [](global_property_object& p) {\n      // Turn the review period WAY down, so it doesn't take long to produce blocks to that point in simulated time.\n      p.parameters.committee_proposal_review_period = fc::days(1).to_seconds();\n   });\n\n   BOOST_TEST_MESSAGE( \"transfering 100000 CORE to nathan, signing with committee key should fail because this requires it to be part of a proposal\" );\n   transfer_operation top;\n   top.to = nathan.id;\n   top.amount = asset(100000);\n   trx.operations.push_back(top);\n   sign(trx, committee_key);\n   GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), graphene::chain::invalid_committee_approval );\n\n   auto _sign = [&] { trx.clear_signatures(); sign( trx, nathan_key ); };\n\n   proposal_create_operation pop;\n   pop.proposed_ops.push_back({op_wrapper(trx.operations.front())});\n   pop.expiration_time = db.head_block_time() + global_params.committee_proposal_review_period*2;\n   pop.fee_paying_account = nathan.id;\n   trx.operations = {pop};\n   _sign();\n\n   // The review period isn't set yet. Make sure it throws.\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), proposal_create_review_period_required );\n   pop.review_period_seconds = global_params.committee_proposal_review_period / 2;\n   trx.operations.back() = pop;\n   _sign();\n   // The review period is too short. Make sure it throws.\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), proposal_create_review_period_insufficient );\n   pop.review_period_seconds = global_params.committee_proposal_review_period;\n   trx.operations.back() = pop;\n   _sign();\n   proposal_object prop = db.get<proposal_object>(PUSH_TX( db, trx ).operation_results.front().get<object_id_type>());\n   BOOST_REQUIRE(db.find_object(prop.id));\n\n   BOOST_CHECK(prop.expiration_time == pop.expiration_time);\n   BOOST_CHECK(prop.review_period_time && *prop.review_period_time == pop.expiration_time - *pop.review_period_seconds);\n   BOOST_CHECK(prop.proposed_transaction.operations.size() == 1);\n   BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 0);\n   BOOST_CHECK(!db.get<proposal_object>(prop.id).is_authorized_to_execute(db));\n\n   generate_block();\n   BOOST_REQUIRE(db.find_object(prop.id));\n   BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 0);\n\n   BOOST_TEST_MESSAGE( \"Checking that the proposal is not authorized to execute\" );\n   BOOST_REQUIRE(!db.get<proposal_object>(prop.id).is_authorized_to_execute(db));\n   trx.clear();\n   proposal_update_operation uop;\n   uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n   uop.proposal = prop.id;\n\n   uop.key_approvals_to_add.emplace(committee_key.get_public_key());\n   /*\n   uop.key_approvals_to_add.emplace(1);\n   uop.key_approvals_to_add.emplace(2);\n   uop.key_approvals_to_add.emplace(3);\n   uop.key_approvals_to_add.emplace(4);\n   uop.key_approvals_to_add.emplace(5);\n   uop.key_approvals_to_add.emplace(6);\n   */\n   trx.operations.push_back(uop);\n   sign( trx, committee_key );\n   PUSH_TX(db, trx);\n   BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 0);\n   BOOST_CHECK(db.get<proposal_object>(prop.id).is_authorized_to_execute(db));\n\n   trx.clear_signatures();\n   generate_blocks(*prop.review_period_time);\n   uop.key_approvals_to_add.clear();\n   uop.key_approvals_to_add.insert(committee_key.get_public_key()); // was 7\n   trx.operations.back() = uop;\n   sign( trx,  committee_key );\n   // Should throw because the transaction is now in review.\n   GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception);\n\n   generate_blocks(prop.expiration_time);\n   BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 100000);\n   // proposal deleted\n   BOOST_CHECK_THROW( db.get<proposal_object>(prop.id), fc::exception );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( fired_committee_members, database_fixture )\n{ try {\n   generate_block();\n   fc::ecc::private_key committee_key = init_account_priv_key;\n   fc::ecc::private_key committee_member_key = fc::ecc::private_key::generate();\n\n   //Meet nathan. He has a little money.\n   const account_object* nathan = &create_account(\"nathan\");\n   transfer(account_id_type()(db), *nathan, asset(5000));\n   generate_block();\n   nathan = &get_account(\"nathan\");\n   flat_set<vote_id_type> committee_members;\n\n   /*\n   db.modify(db.get_global_properties(), [](global_property_object& p) {\n      // Turn the review period WAY down, so it doesn't take long to produce blocks to that point in simulated time.\n      p.parameters.committee_proposal_review_period = fc::days(1).to_seconds();\n   });\n   */\n\n   for( int i = 0; i < 15; ++i )\n   {\n      const auto& account = create_account(\"committee-member\" + fc::to_string(i+1), committee_member_key.get_public_key());\n      upgrade_to_lifetime_member(account);\n      committee_members.insert(create_committee_member(account).vote_id);\n   }\n   BOOST_REQUIRE_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n\n   //A proposal is created to give nathan lots more money.\n   proposal_create_operation pop = proposal_create_operation::committee_proposal(db.get_global_properties().parameters, db.head_block_time());\n   pop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n   pop.expiration_time = db.head_block_time() + *pop.review_period_seconds + fc::days(1).to_seconds();\n   ilog( \"Creating proposal to give nathan money that expires: ${e}\", (\"e\", pop.expiration_time ) );\n   ilog( \"The proposal has a review period of: ${r} sec\", (\"r\",*pop.review_period_seconds) );\n\n   transfer_operation top;\n   top.to = nathan->id;\n   top.amount = asset(100000);\n   pop.proposed_ops.emplace_back(top);\n   trx.operations.push_back(pop);\n   const proposal_object& prop = db.get<proposal_object>(PUSH_TX( db, trx ).operation_results.front().get<object_id_type>());\n   proposal_id_type pid = prop.get_id();\n   BOOST_CHECK(!pid(db).is_authorized_to_execute(db));\n\n   ilog( \"commitee member approves proposal\" );\n   //committee key approves of the proposal.\n   proposal_update_operation uop;\n   uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n   uop.proposal = pid;\n   uop.key_approvals_to_add.emplace(init_account_pub_key);\n   trx.operations.back() = uop;\n   sign( trx, committee_key );\n   PUSH_TX( db, trx );\n   BOOST_CHECK(pid(db).is_authorized_to_execute(db));\n\n   ilog( \"Generating blocks for 2 days\" );\n   generate_block();\n   BOOST_REQUIRE_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n   generate_block();\n   BOOST_REQUIRE_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n   //Time passes... the proposal is now in its review period.\n   //generate_blocks(*pid(db).review_period_time);\n   generate_blocks(db.head_block_time() + fc::days(2) );\n   ilog( \"head block time: ${t}\", (\"t\",db.head_block_time()));\n\n   fc::time_point_sec maintenance_time = db.get_dynamic_global_properties().next_maintenance_time;\n   BOOST_CHECK_LT(maintenance_time.sec_since_epoch(), pid(db).expiration_time.sec_since_epoch());\n   //Yay! The proposal to give nathan more money is authorized.\n   BOOST_REQUIRE(pid(db).is_authorized_to_execute(db));\n\n   nathan = &get_account(\"nathan\");\n   // no money yet\n   BOOST_REQUIRE_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n\n   {\n      //Oh noes! Nathan votes for a whole new slate of committee_members!\n      account_update_operation op;\n      op.account = nathan->id;\n      op.new_options = nathan->options;\n      op.new_options->votes = committee_members;\n      trx.operations.push_back(op);\n      set_expiration( db, trx );\n      PUSH_TX( db, trx, ~0 );\n      trx.operations.clear();\n   }\n   idump((get_balance(*nathan, asset_id_type()(db))));\n   // still no money\n   BOOST_CHECK_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n\n   //Time passes... the set of active committee_members gets updated.\n   generate_blocks(maintenance_time);\n   //The proposal is no longer authorized, because the active committee_members got changed.\n   BOOST_CHECK(!pid(db).is_authorized_to_execute(db));\n   // still no money\n   BOOST_CHECK_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n\n   //Time passes... the proposal has now expired.\n   generate_blocks(pid(db).expiration_time);\n   BOOST_CHECK(db.find(pid) == nullptr);\n\n   //Nathan never got any more money because the proposal was rejected.\n   BOOST_CHECK_EQUAL(get_balance(*nathan, asset_id_type()(db)), 5000);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( proposal_two_accounts, database_fixture )\n{ try {\n   generate_block();\n\n   auto nathan_key = generate_private_key(\"nathan\");\n   auto dan_key = generate_private_key(\"dan\");\n   const account_object& nathan = create_account(\"nathan\", nathan_key.get_public_key() );\n   const account_object& dan = create_account(\"dan\", dan_key.get_public_key() );\n\n   transfer(account_id_type()(db), nathan, asset(100000));\n   transfer(account_id_type()(db), dan, asset(100000));\n\n   {\n      transfer_operation top;\n      top.from = dan.get_id();\n      top.to = nathan.get_id();\n      top.amount = asset(500);\n\n      proposal_create_operation pop;\n      pop.proposed_ops.emplace_back(top);\n      std::swap(top.from, top.to);\n      pop.proposed_ops.emplace_back(top);\n\n      pop.fee_paying_account = nathan.get_id();\n      pop.expiration_time = db.head_block_time() + fc::days(1);\n      trx.operations.push_back(pop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   const proposal_object& prop = *db.get_index_type<proposal_index>().indices().begin();\n   BOOST_CHECK(prop.required_active_approvals.size() == 2);\n   BOOST_CHECK(prop.required_owner_approvals.size() == 0);\n   BOOST_CHECK(!prop.is_authorized_to_execute(db));\n\n   {\n      proposal_id_type pid = prop.get_id();\n      proposal_update_operation uop;\n      uop.proposal = prop.id;\n      uop.active_approvals_to_add.insert(nathan.get_id());\n      uop.fee_paying_account = nathan.get_id();\n      trx.operations.push_back(uop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n\n      BOOST_CHECK(db.find(pid) != nullptr);\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n\n      uop.active_approvals_to_add = {dan.get_id()};\n      trx.operations.push_back(uop);\n      sign( trx, nathan_key );\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception);\n      sign( trx, dan_key );\n      PUSH_TX( db, trx );\n\n      BOOST_CHECK(db.find(pid) == nullptr);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( proposal_delete, database_fixture )\n{ try {\n   generate_block();\n\n   auto nathan_key = generate_private_key(\"nathan\");\n   auto dan_key = generate_private_key(\"dan\");\n   const account_object& nathan = create_account(\"nathan\", nathan_key.get_public_key() );\n   const account_object& dan = create_account(\"dan\", dan_key.get_public_key() );\n\n   transfer(account_id_type()(db), nathan, asset(100000));\n   transfer(account_id_type()(db), dan, asset(100000));\n\n   {\n      transfer_operation top;\n      top.from = dan.get_id();\n      top.to = nathan.get_id();\n      top.amount = asset(500);\n\n      proposal_create_operation pop;\n      pop.proposed_ops.emplace_back(top);\n      std::swap(top.from, top.to);\n      top.amount = asset(6000);\n      pop.proposed_ops.emplace_back(top);\n\n      pop.fee_paying_account = nathan.get_id();\n      pop.expiration_time = db.head_block_time() + fc::days(1);\n      trx.operations.push_back(pop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   const proposal_object& prop = *db.get_index_type<proposal_index>().indices().begin();\n   BOOST_CHECK(prop.required_active_approvals.size() == 2);\n   BOOST_CHECK(prop.required_owner_approvals.size() == 0);\n   BOOST_CHECK(!prop.is_authorized_to_execute(db));\n\n   {\n      proposal_update_operation uop;\n      uop.fee_paying_account = nathan.get_id();\n      uop.proposal = prop.id;\n      uop.active_approvals_to_add.insert(nathan.get_id());\n      trx.operations.push_back(uop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_active_approvals.size(), 1lu);\n\n      std::swap(uop.active_approvals_to_add, uop.active_approvals_to_remove);\n      trx.operations.push_back(uop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_active_approvals.size(), 0lu);\n   }\n\n   {\n      proposal_id_type pid = prop.get_id();\n      proposal_delete_operation dop;\n      dop.fee_paying_account = nathan.get_id();\n      dop.proposal = pid;\n      trx.operations.push_back(dop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      BOOST_CHECK(db.find(pid) == nullptr);\n      BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 100000);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( proposal_owner_authority_delete, database_fixture )\n{ try {\n   generate_block();\n\n   auto nathan_key = generate_private_key(\"nathan\");\n   auto dan_key = generate_private_key(\"dan\");\n   const account_object& nathan = create_account(\"nathan\", nathan_key.get_public_key() );\n   const account_object& dan = create_account(\"dan\", dan_key.get_public_key() );\n\n   transfer(account_id_type()(db), nathan, asset(100000));\n   transfer(account_id_type()(db), dan, asset(100000));\n\n   {\n      transfer_operation top;\n      top.from = dan.get_id();\n      top.to = nathan.get_id();\n      top.amount = asset(500);\n\n      account_update_operation uop;\n      uop.account = nathan.get_id();\n      uop.owner = authority(1, public_key_type(generate_private_key(\"nathan2\").get_public_key()), 1);\n\n      proposal_create_operation pop;\n      pop.proposed_ops.emplace_back(top);\n      pop.proposed_ops.emplace_back(uop);\n      std::swap(top.from, top.to);\n      top.amount = asset(6000);\n      pop.proposed_ops.emplace_back(top);\n\n      pop.fee_paying_account = nathan.get_id();\n      pop.expiration_time = db.head_block_time() + fc::days(1);\n      trx.operations.push_back(pop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   const proposal_object& prop = *db.get_index_type<proposal_index>().indices().begin();\n   BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1lu);\n   BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 1lu);\n   BOOST_CHECK(!prop.is_authorized_to_execute(db));\n\n   {\n      proposal_update_operation uop;\n      uop.fee_paying_account = nathan.get_id();\n      uop.proposal = prop.id;\n      uop.owner_approvals_to_add.insert(nathan.get_id());\n      trx.operations.push_back(uop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_owner_approvals.size(), 1lu);\n\n      std::swap(uop.owner_approvals_to_add, uop.owner_approvals_to_remove);\n      trx.operations.push_back(uop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_owner_approvals.size(), 0lu);\n   }\n\n   {\n      proposal_id_type pid = prop.get_id();\n      proposal_delete_operation dop;\n      dop.fee_paying_account = nathan.get_id();\n      dop.proposal = pid;\n      dop.using_owner_authority = true;\n      trx.operations.push_back(dop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      BOOST_CHECK(db.find(pid) == nullptr);\n      BOOST_CHECK_EQUAL(get_balance(nathan, asset_id_type()(db)), 100000);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( proposal_owner_authority_complete, database_fixture )\n{ try {\n   generate_block();\n\n   auto nathan_key = generate_private_key(\"nathan\");\n   auto dan_key = generate_private_key(\"dan\");\n   const account_object& nathan = create_account(\"nathan\", nathan_key.get_public_key() );\n   const account_object& dan = create_account(\"dan\", dan_key.get_public_key() );\n\n   transfer(account_id_type()(db), nathan, asset(100000));\n   transfer(account_id_type()(db), dan, asset(100000));\n\n   {\n      transfer_operation top;\n      top.from = dan.get_id();\n      top.to = nathan.get_id();\n      top.amount = asset(500);\n\n      account_update_operation uop;\n      uop.account = nathan.get_id();\n      uop.owner = authority(1, public_key_type(generate_private_key(\"nathan2\").get_public_key()), 1);\n\n      proposal_create_operation pop;\n      pop.proposed_ops.emplace_back(top);\n      pop.proposed_ops.emplace_back(uop);\n      std::swap(top.from, top.to);\n      top.amount = asset(6000);\n      pop.proposed_ops.emplace_back(top);\n\n      pop.fee_paying_account = nathan.get_id();\n      pop.expiration_time = db.head_block_time() + fc::days(1);\n      trx.operations.push_back(pop);\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   const proposal_object& prop = *db.get_index_type<proposal_index>().indices().begin();\n   BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1lu);\n   BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 1lu);\n   BOOST_CHECK(!prop.is_authorized_to_execute(db));\n\n   {\n      proposal_id_type pid = prop.get_id();\n      proposal_update_operation uop;\n      uop.fee_paying_account = nathan.get_id();\n      uop.proposal = prop.id;\n      uop.key_approvals_to_add.insert(dan.active.key_auths.begin()->first);\n      trx.operations.push_back(uop);\n      set_expiration( db, trx );\n      sign( trx, nathan_key );\n      sign( trx, dan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 1lu);\n\n      std::swap(uop.key_approvals_to_add, uop.key_approvals_to_remove);\n      trx.operations.push_back(uop);\n      trx.expiration += fc::seconds(1);  // Survive trx dupe check\n      sign( trx, nathan_key );\n      sign( trx, dan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 0lu);\n\n      std::swap(uop.key_approvals_to_add, uop.key_approvals_to_remove);\n      trx.operations.push_back(uop);\n      trx.expiration += fc::seconds(1);  // Survive trx dupe check\n      sign( trx, nathan_key );\n      sign( trx, dan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(!prop.is_authorized_to_execute(db));\n      BOOST_CHECK_EQUAL(prop.available_key_approvals.size(), 1lu);\n\n      uop.key_approvals_to_add.clear();\n      uop.owner_approvals_to_add.insert(nathan.get_id());\n      trx.operations.push_back(uop);\n      trx.expiration += fc::seconds(1);  // Survive trx dupe check\n      sign( trx, nathan_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n      BOOST_CHECK(db.find(pid) == nullptr);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( max_authority_membership, database_fixture )\n{\n   try\n   {\n      //Get a sane head block time\n      generate_block();\n\n      db.modify(db.get_global_properties(), [](global_property_object& p) {\n         p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();\n      });\n\n      transaction tx;\n\n      private_key_type committee_key = init_account_priv_key;\n      // Sam is the creator of accounts\n      private_key_type sam_key = generate_private_key(\"sam\");\n\n      account_object sam_account_object = create_account( \"sam\", sam_key );\n      upgrade_to_lifetime_member(sam_account_object);\n      account_object committee_account_object = committee_account(db);\n\n      const asset_object& core = asset_id_type()(db);\n\n      transfer(committee_account_object, sam_account_object, core.amount(100000));\n\n      // have Sam create some keys\n\n      int keys_to_create = 2*GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP;\n      vector<private_key_type> private_keys;\n\n      private_keys.reserve( keys_to_create );\n      for( int i=0; i<keys_to_create; i++ )\n      {\n         string seed = \"this_is_a_key_\" + std::to_string(i);\n         private_key_type privkey = generate_private_key( seed );\n         private_keys.push_back( privkey );\n      }\n      set_expiration( db, tx );\n\n      vector<public_key_type> key_ids;\n\n      key_ids.reserve( keys_to_create );\n      for( int i=0; i<keys_to_create; i++ )\n          key_ids.push_back( private_keys[i].get_public_key() );\n\n      // now try registering accounts with n keys, 0 < n < 20\n\n      // TODO:  Make sure it throws / accepts properly when\n      //   max_account_authority is changed in global parameteres\n\n      for( int num_keys=1; num_keys<=keys_to_create; num_keys++ )\n      {\n         // try registering account with n keys\n\n         authority test_authority;\n         test_authority.weight_threshold = num_keys;\n\n         for( int i=0; i<num_keys; i++ )\n            test_authority.key_auths[ key_ids[i] ] = 1;\n\n         auto check_tx = [&]( const authority& owner_auth,\n                              const authority& active_auth )\n         {\n             const uint16_t max_authority_membership = GRAPHENE_DEFAULT_MAX_AUTHORITY_MEMBERSHIP;\n             account_create_operation anon_create_op;\n             transaction tx;\n\n             anon_create_op.owner = owner_auth;\n             anon_create_op.active = active_auth;\n             anon_create_op.registrar = sam_account_object.id;\n             anon_create_op.options.memo_key = sam_account_object.options.memo_key;\n             anon_create_op.name = generate_anon_acct_name();\n\n             tx.operations.push_back( anon_create_op );\n             set_expiration( db, tx );\n\n             if( num_keys > max_authority_membership )\n             {\n                GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, ~0 ), account_create_max_auth_exceeded );\n             }\n             else\n             {\n                PUSH_TX( db, tx, ~0 );\n             }\n             return;\n         };\n\n         check_tx( sam_account_object.owner, test_authority  );\n         check_tx( test_authority, sam_account_object.active );\n         check_tx( test_authority, test_authority );\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_FIXTURE_TEST_CASE( bogus_signature, database_fixture )\n{\n   try\n   {\n      private_key_type committee_key = init_account_priv_key;\n      // Sam is the creator of accounts\n      private_key_type alice_key = generate_private_key(\"alice\");\n      private_key_type bob_key = generate_private_key(\"bob\");\n      private_key_type charlie_key = generate_private_key(\"charlie\");\n\n      account_object committee_account_object = committee_account(db);\n      account_object alice_account_object = create_account( \"alice\", alice_key );\n      account_object bob_account_object = create_account( \"bob\", bob_key );\n      account_object charlie_account_object = create_account( \"charlie\", charlie_key );\n\n      // unneeded, comment it out to silence compiler warning\n      //key_id_type bob_key_id = bob_account_object.memo_key;\n\n      uint32_t skip = database::skip_transaction_dupe_check;\n\n      // send from Sam -> Alice, signed by Sam\n\n      const asset_object& core = asset_id_type()(db);\n      transfer(committee_account_object, alice_account_object, core.amount(100000));\n\n      transfer_operation xfer_op;\n      xfer_op.from = alice_account_object.id;\n      xfer_op.to = bob_account_object.id;\n      xfer_op.amount = core.amount(5000);\n      xfer_op.fee = db.current_fee_schedule().calculate_fee( xfer_op );\n\n      trx.clear();\n      trx.operations.push_back( xfer_op );\n\n      BOOST_TEST_MESSAGE( \"Transfer signed by alice\" );\n      sign( trx, alice_key  );\n\n      flat_set<account_id_type> active_set, owner_set;\n      vector<authority> others;\n      trx.get_required_authorities(active_set, owner_set, others, false);\n\n      PUSH_TX( db,  trx, skip  );\n\n      trx.operations.push_back( xfer_op );\n      BOOST_TEST_MESSAGE( \"Invalidating Alices Signature\" );\n      // Alice's signature is now invalid\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db,  trx, skip  ), fc::exception );\n      // Re-sign, now OK (sig is replaced)\n      BOOST_TEST_MESSAGE( \"Resign with Alice's Signature\" );\n      trx.clear_signatures();\n      sign( trx,  alice_key  );\n      PUSH_TX( db,  trx, skip  );\n\n      trx.clear_signatures();\n      trx.operations.pop_back();\n      sign( trx,  alice_key  );\n      sign( trx,  charlie_key  );\n      // Signed by third-party Charlie (irrelevant key, not in authority)\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db,  trx, skip  ), tx_irrelevant_sig );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_FIXTURE_TEST_CASE( voting_account, database_fixture )\n{ try {\n   ACTORS((nathan)(vikram));\n   upgrade_to_lifetime_member(nathan_id);\n   upgrade_to_lifetime_member(vikram_id);\n   committee_member_id_type nathan_committee_member = create_committee_member(nathan_id(db)).get_id();\n   committee_member_id_type vikram_committee_member = create_committee_member(vikram_id(db)).get_id();\n\n   //wdump((db.get_balance(account_id_type(), asset_id_type())));\n   generate_block();\n\n   //wdump((db.get_balance(account_id_type(), asset_id_type())));\n   transfer(account_id_type(), nathan_id, asset(1000000));\n   transfer(account_id_type(), vikram_id, asset(100));\n\n   {\n      account_update_operation op;\n      op.account = nathan_id;\n      op.new_options = nathan_id(db).options;\n      op.new_options->voting_account = vikram_id;\n      op.new_options->votes = flat_set<vote_id_type>{nathan_committee_member(db).vote_id};\n      op.new_options->num_committee = 1;\n      trx.operations.push_back(op);\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n   {\n      account_update_operation op;\n      op.account = vikram_id;\n      op.new_options = vikram_id(db).options;\n      op.new_options->votes.insert(vikram_committee_member(db).vote_id);\n      op.new_options->num_committee = 11;\n      trx.operations.push_back(op);\n      sign( trx, vikram_private_key );\n      // Fails because num_committee is larger than the cardinality of committee members being voted for\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception);\n      op.new_options->num_committee = 3;\n      trx.operations = {op};\n      trx.clear_signatures();\n      sign( trx, vikram_private_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time + GRAPHENE_DEFAULT_BLOCK_INTERVAL);\n   BOOST_CHECK(std::find(db.get_global_properties().active_committee_members.begin(),\n                         db.get_global_properties().active_committee_members.end(),\n                         nathan_committee_member) == db.get_global_properties().active_committee_members.end());\n   BOOST_CHECK(std::find(db.get_global_properties().active_committee_members.begin(),\n                         db.get_global_properties().active_committee_members.end(),\n                         vikram_committee_member) != db.get_global_properties().active_committee_members.end());\n} FC_LOG_AND_RETHROW() }\n\n/*\n * Simple corporate accounts:\n *\n * Well Corp.       Alice 50, Bob 50             T=60\n * Xylo Company     Alice 30, Cindy 50           T=40\n * Yaya Inc.        Bob 10, Dan 10, Edy 10       T=20\n * Zyzz Co.         Dan 50                       T=40\n *\n * Complex corporate accounts:\n *\n * Mega Corp.       Well 30, Yes 30              T=40\n * Nova Ltd.        Alice 10, Well 10            T=20\n * Odle Intl.       Dan 10, Yes 10, Zyzz 10      T=20\n * Poxx LLC         Well 10, Xylo 10, Yes 20, Zyzz 20   T=40\n */\n\nBOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture )\n{\n   try\n   {\n      ACTORS(\n              (alice)(bob)(cindy)(dan)(edy)\n              (mega)(nova)(odle)(poxx)\n              (well)(xylo)(yaya)(zyzz)\n            );\n\n      auto set_auth = [&](\n         account_id_type aid,\n         const authority& auth\n         )\n      {\n         signed_transaction tx;\n         account_update_operation op;\n         op.account = aid;\n         op.active = auth;\n         op.owner = auth;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_transaction_signatures );\n      } ;\n\n      auto get_active = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).active);\n      } ;\n\n      auto get_owner = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).owner);\n      } ;\n\n      auto chk = [&](\n         const signed_transaction& tx,\n         flat_set<public_key_type> available_keys,\n         set<public_key_type> ref_set\n         ) -> bool\n      {\n         //wdump( (tx)(available_keys) );\n         set<public_key_type> result_set = tx.get_required_signatures(db.get_chain_id(), available_keys,\n                                                                      get_active, get_owner, false, false);\n         set<public_key_type> result_set2 = tx.get_required_signatures(db.get_chain_id(), available_keys,\n                                                                       get_active, get_owner, true, false);\n         //wdump( (result_set)(result_set2)(ref_set) );\n         return result_set == ref_set && result_set2 == ref_set;\n      } ;\n\n      set_auth( well_id, authority( 60, alice_id, 50, bob_id, 50 ) );\n      set_auth( xylo_id, authority( 40, alice_id, 30, cindy_id, 50 ) );\n      set_auth( yaya_id, authority( 20, bob_id, 10, dan_id, 10, edy_id, 10 ) );\n      set_auth( zyzz_id, authority( 40, dan_id, 50 ) );\n\n      set_auth( mega_id, authority( 40, well_id, 30, yaya_id, 30 ) );\n      set_auth( nova_id, authority( 20, alice_id, 10, well_id, 10 ) );\n      set_auth( odle_id, authority( 20, dan_id, 10, yaya_id, 10, zyzz_id, 10 ) );\n      set_auth( poxx_id, authority( 40, well_id, 10, xylo_id, 10, yaya_id, 20, zyzz_id, 20 ) );\n\n      signed_transaction tx;\n      flat_set< public_key_type > all_keys\n         { alice_public_key, bob_public_key, cindy_public_key, dan_public_key, edy_public_key };\n\n      tx.operations.push_back( transfer_operation() );\n      transfer_operation& op = tx.operations.back().get<transfer_operation>();\n      op.to = edy_id;\n      op.amount = asset(1);\n\n      op.from = alice_id;\n      BOOST_CHECK( chk( tx, all_keys, { alice_public_key } ) );\n      op.from = bob_id;\n      BOOST_CHECK( chk( tx, all_keys, { bob_public_key } ) );\n      op.from = well_id;\n      BOOST_CHECK( chk( tx, all_keys, { alice_public_key, bob_public_key } ) );\n      op.from = xylo_id;\n      BOOST_CHECK( chk( tx, all_keys, { alice_public_key, cindy_public_key } ) );\n      op.from = yaya_id;\n      BOOST_CHECK( chk( tx, all_keys, { bob_public_key, dan_public_key } ) );\n      op.from = zyzz_id;\n      BOOST_CHECK( chk( tx, all_keys, { dan_public_key } ) );\n\n      op.from = mega_id;\n      BOOST_CHECK( chk( tx, all_keys, { alice_public_key, bob_public_key, dan_public_key } ) );\n      op.from = nova_id;\n      BOOST_CHECK( chk( tx, all_keys, { alice_public_key, bob_public_key } ) );\n      op.from = odle_id;\n      BOOST_CHECK( chk( tx, all_keys, { bob_public_key, dan_public_key } ) );\n      op.from = poxx_id;\n      BOOST_CHECK( chk( tx, all_keys, { alice_public_key, bob_public_key, cindy_public_key, dan_public_key } ) );\n\n      // TODO:  Add sigs to tx, then check\n      // TODO:  Check removing sigs      \n      // TODO:  Accounts with mix of keys and accounts in their authority\n      // TODO:  Tx with multiple ops requiring different sigs\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/*\n * Pathological case\n *\n *      Roco(T=2)\n *    1/         \\2\n *   Styx(T=2)   Thud(T=1)\n *  1/     \\1       |1\n * Alice  Bob     Alice\n */\n\nBOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture )\n{\n   try\n   {\n      ACTORS(\n         (alice)(bob)\n         (roco)\n         (styx)(thud)\n         );\n\n      auto set_auth = [&](\n         account_id_type aid,\n         const authority& auth\n         )\n      {\n         signed_transaction tx;\n         account_update_operation op;\n         op.account = aid;\n         op.active = auth;\n         op.owner = auth;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_transaction_signatures );\n      } ;\n\n      auto get_active = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).active);\n      } ;\n\n      auto get_owner = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).owner);\n      } ;\n\n      auto chk = [&](\n         const signed_transaction& tx,\n         flat_set<public_key_type> available_keys,\n         set<public_key_type> ref_set\n         ) -> bool\n      {\n         //wdump( (tx)(available_keys) );\n         set<public_key_type> result_set = tx.get_required_signatures(db.get_chain_id(), available_keys,\n                                                                      get_active, get_owner, false, false);\n         set<public_key_type> result_set2 = tx.get_required_signatures(db.get_chain_id(), available_keys,\n                                                                       get_active, get_owner, true, false);\n         //wdump( (result_set)(result_set2)(ref_set) );\n         return result_set == ref_set && result_set2 == ref_set;\n      } ;\n\n      auto chk_min = [&](\n         const signed_transaction& tx,\n         flat_set<public_key_type> available_keys,\n         set<public_key_type> ref_set\n         ) -> bool\n      {\n         //wdump( (tx)(available_keys) );\n          auto get_custom = make_get_custom(db);\n         set<public_key_type> result_set = tx.minimize_required_signatures(db.get_chain_id(), available_keys,\n                                                                           get_active, get_owner, get_custom,\n                                                                           false, false);\n         set<public_key_type> result_set2 = tx.minimize_required_signatures(db.get_chain_id(), available_keys,\n                                                                            get_active, get_owner, get_custom,\n                                                                            true, false);\n         //wdump( (result_set)(result_set2)(ref_set) );\n         return result_set == ref_set && result_set2 == ref_set;\n      } ;\n\n      set_auth( roco_id, authority( 2, styx_id, 1, thud_id, 2 ) );\n      set_auth( styx_id, authority( 2, alice_id, 1, bob_id, 1 ) );\n      set_auth( thud_id, authority( 1, alice_id, 1 ) );\n\n      signed_transaction tx;\n      transfer_operation op;\n      op.from = roco_id;\n      op.to = bob_id;\n      op.amount = asset(1);\n      tx.operations.push_back( op );\n\n      BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) );\n      BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) );\n\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   true, false ), fc::exception );\n      sign( tx, alice_private_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/*\n * Active vs Owner https://github.com/bitshares/bitshares-core/issues/584\n *\n * All weights and all thresholds are 1, so every single key should be able to sign if within max_depth\n *\n * Bob --+--(a)--+-- Alice --+--(a)--+-- Daisy --(a/o)-- Daisy_active_key / Daisy_owner_key\n *       |       |           |       |\n *       |       |           |       +-- Alice_active_key\n *       |       |           |\n *       |       |           +--(o)--+-- Cindy --(a/o)-- Cindy_active_key / Cindy_owner_key\n *       |       |                   |\n *       |       |                   +-- Alice_owner_key\n *       |       |\n *       |       +-- Bob_active_key\n *       |\n *       +--(o)--+-- Edwin --+--(a)--+-- Gavin --(a/o)-- Gavin_active_key / Gavin_owner_key\n *               |           |       |\n *               |           |       +-- Edwin_active_key\n *               |           |\n *               |           +--(o)--+-- Frank --(a/o)-- Frank_active_key / Frank_owner_key\n *               |                   |\n *               |                   +-- Edwin_owner_key\n *               |\n *               +-- Bob_owner_key\n */\nBOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture )\n{\n   try\n   {\n      ACTORS(\n         (alice)(bob)(cindy)(daisy)(edwin)(frank)(gavin)\n         );\n\n      transfer( account_id_type(), bob_id, asset(100000) );\n\n      auto set_auth = [&](\n         account_id_type aid,\n         const authority& active,\n         const authority& owner\n         )\n      {\n         signed_transaction tx;\n         account_update_operation op;\n         op.account = aid;\n         op.active = active;\n         op.owner = owner;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_transaction_signatures );\n      } ;\n\n      auto get_active = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).active);\n      } ;\n\n      auto get_owner = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).owner);\n      } ;\n\n      auto chk = [&](\n         const signed_transaction& tx,\n         bool after_hf_584,\n         flat_set<public_key_type> available_keys,\n         set<public_key_type> ref_set\n         ) -> bool\n      {\n         //wdump( (tx)(available_keys) );\n         set<public_key_type> result_set = tx.get_required_signatures(db.get_chain_id(), available_keys,\n                                                                      get_active, get_owner, after_hf_584, false);\n         //wdump( (result_set)(ref_set) );\n         return result_set == ref_set;\n      } ;\n\n      fc::ecc::private_key alice_active_key = fc::ecc::private_key::regenerate(fc::digest(\"alice_active\"));\n      fc::ecc::private_key alice_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"alice_owner\"));\n      fc::ecc::private_key bob_active_key = fc::ecc::private_key::regenerate(fc::digest(\"bob_active\"));\n      fc::ecc::private_key bob_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"bob_owner\"));\n      fc::ecc::private_key cindy_active_key = fc::ecc::private_key::regenerate(fc::digest(\"cindy_active\"));\n      fc::ecc::private_key cindy_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"cindy_owner\"));\n      fc::ecc::private_key daisy_active_key = fc::ecc::private_key::regenerate(fc::digest(\"daisy_active\"));\n      fc::ecc::private_key daisy_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"daisy_owner\"));\n      fc::ecc::private_key edwin_active_key = fc::ecc::private_key::regenerate(fc::digest(\"edwin_active\"));\n      fc::ecc::private_key edwin_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"edwin_owner\"));\n      fc::ecc::private_key frank_active_key = fc::ecc::private_key::regenerate(fc::digest(\"frank_active\"));\n      fc::ecc::private_key frank_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"frank_owner\"));\n      fc::ecc::private_key gavin_active_key = fc::ecc::private_key::regenerate(fc::digest(\"gavin_active\"));\n      fc::ecc::private_key gavin_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"gavin_owner\"));\n\n      public_key_type alice_active_pub( alice_active_key.get_public_key() );\n      public_key_type alice_owner_pub( alice_owner_key.get_public_key() );\n      public_key_type bob_active_pub( bob_active_key.get_public_key() );\n      public_key_type bob_owner_pub( bob_owner_key.get_public_key() );\n      public_key_type cindy_active_pub( cindy_active_key.get_public_key() );\n      public_key_type cindy_owner_pub( cindy_owner_key.get_public_key() );\n      public_key_type daisy_active_pub( daisy_active_key.get_public_key() );\n      public_key_type daisy_owner_pub( daisy_owner_key.get_public_key() );\n      public_key_type edwin_active_pub( edwin_active_key.get_public_key() );\n      public_key_type edwin_owner_pub( edwin_owner_key.get_public_key() );\n      public_key_type frank_active_pub( frank_active_key.get_public_key() );\n      public_key_type frank_owner_pub( frank_owner_key.get_public_key() );\n      public_key_type gavin_active_pub( gavin_active_key.get_public_key() );\n      public_key_type gavin_owner_pub( gavin_owner_key.get_public_key() );\n\n      set_auth( alice_id, authority( 1, alice_active_pub, 1, daisy_id, 1 ), authority( 1, alice_owner_pub, 1, cindy_id, 1 ) );\n      set_auth(   bob_id, authority( 1,   bob_active_pub, 1, alice_id, 1 ), authority( 1,   bob_owner_pub, 1, edwin_id, 1 ) );\n\n      set_auth( cindy_id, authority( 1, cindy_active_pub, 1 ), authority( 1, cindy_owner_pub, 1 ) );\n      set_auth( daisy_id, authority( 1, daisy_active_pub, 1 ), authority( 1, daisy_owner_pub, 1 ) );\n\n      set_auth( edwin_id, authority( 1, edwin_active_pub, 1, gavin_id, 1 ), authority( 1, edwin_owner_pub, 1, frank_id, 1 ) );\n\n      set_auth( frank_id, authority( 1, frank_active_pub, 1 ), authority( 1, frank_owner_pub, 1 ) );\n      set_auth( gavin_id, authority( 1, gavin_active_pub, 1 ), authority( 1, gavin_owner_pub, 1 ) );\n\n      generate_block();\n\n      signed_transaction tx;\n      transfer_operation op;\n      op.from = bob_id;\n      op.to = alice_id;\n      op.amount = asset(1);\n      tx.operations.push_back( op );\n      set_expiration( db, tx );\n\n      // https://github.com/bitshares/bitshares-core/issues/584\n      // If not allow non-immediate owner to authorize\n      BOOST_CHECK( chk( tx, false, { alice_owner_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { alice_active_pub }, { alice_active_pub } ) );\n      BOOST_CHECK( chk( tx, false, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, false, { bob_owner_pub }, { bob_owner_pub } ) );\n      BOOST_CHECK( chk( tx, false, { bob_active_pub }, { bob_active_pub } ) );\n      BOOST_CHECK( chk( tx, false, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, false, { cindy_owner_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { cindy_active_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { cindy_active_pub, cindy_owner_pub }, { } ) );\n\n      BOOST_CHECK( chk( tx, false, { daisy_owner_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { daisy_active_pub }, { daisy_active_pub } ) );\n      BOOST_CHECK( chk( tx, false, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, false, { edwin_owner_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { edwin_active_pub }, { edwin_active_pub } ) );\n      BOOST_CHECK( chk( tx, false, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, false, { frank_owner_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { frank_active_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { frank_active_pub, frank_owner_pub }, { } ) );\n\n      BOOST_CHECK( chk( tx, false, { gavin_owner_pub }, { } ) );\n      BOOST_CHECK( chk( tx, false, { gavin_active_pub }, { gavin_active_pub } ) );\n      BOOST_CHECK( chk( tx, false, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) );\n\n      // If allow non-immediate owner to authorize\n      BOOST_CHECK( chk( tx, true, { alice_owner_pub }, { alice_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { alice_active_pub }, { alice_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, true, { bob_owner_pub }, { bob_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { bob_active_pub }, { bob_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, true, { cindy_owner_pub }, { cindy_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { cindy_active_pub }, { cindy_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { cindy_active_pub, cindy_owner_pub }, { cindy_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, true, { daisy_owner_pub }, { daisy_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { daisy_active_pub }, { daisy_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, true, { edwin_owner_pub }, { edwin_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { edwin_active_pub }, { edwin_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, true, { frank_owner_pub }, { frank_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { frank_active_pub }, { frank_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { frank_active_pub, frank_owner_pub }, { frank_active_pub } ) );\n\n      BOOST_CHECK( chk( tx, true, { gavin_owner_pub }, { gavin_owner_pub } ) );\n      BOOST_CHECK( chk( tx, true, { gavin_active_pub }, { gavin_active_pub } ) );\n      BOOST_CHECK( chk( tx, true, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) );\n\n      sign( tx, alice_owner_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                           true, false );\n      tx.clear_signatures();\n\n      sign( tx, alice_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, bob_owner_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, bob_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, cindy_owner_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, cindy_active_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, daisy_owner_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, daisy_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, edwin_owner_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, edwin_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, frank_owner_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, frank_active_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, gavin_owner_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      sign( tx, gavin_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n      tx.clear_signatures();\n\n      // proposal tests\n      auto new_proposal = [&]() -> proposal_id_type {\n         signed_transaction ptx;\n\n         proposal_create_operation pop;\n         pop.proposed_ops.emplace_back(op);\n         pop.fee_paying_account = bob_id;\n         pop.expiration_time = db.head_block_time() + fc::days(1);\n         ptx.operations.push_back(pop);\n         set_expiration( db, ptx );\n         sign( ptx, bob_active_key );\n\n         return proposal_id_type { PUSH_TX( db, ptx, database::skip_transaction_dupe_check ).operation_results[0]\n                                      .get<object_id_type>() };\n      };\n\n      auto approve_proposal = [&](\n            proposal_id_type proposal,\n            account_id_type account,\n            bool approve_with_owner,\n            fc::ecc::private_key key\n            )\n      {\n         signed_transaction ptx;\n\n         proposal_update_operation pup;\n         pup.fee_paying_account = account;\n         pup.proposal = proposal;\n         if( approve_with_owner )\n            pup.owner_approvals_to_add.insert( account );\n         else\n            pup.active_approvals_to_add.insert( account );\n         ptx.operations.push_back(pup);\n         set_expiration( db, ptx );\n         sign( ptx, key );\n         PUSH_TX( db, ptx, database::skip_transaction_dupe_check );\n      };\n\n      proposal_id_type pid;\n\n      pid = new_proposal();\n      approve_proposal( pid, alice_id, true, alice_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, alice_id, false, alice_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, bob_id, true, bob_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, bob_id, false, bob_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      // Cindy's approval doesn't work\n      pid = new_proposal();\n      approve_proposal( pid, cindy_id, true, cindy_owner_key );\n      BOOST_CHECK( db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, cindy_id, false, cindy_active_key );\n      BOOST_CHECK( db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, daisy_id, true, daisy_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, daisy_id, false, daisy_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, edwin_id, true, edwin_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, edwin_id, false, edwin_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      // Frank's approval doesn't work\n      pid = new_proposal();\n      approve_proposal( pid, frank_id, true, frank_owner_key );\n      BOOST_CHECK( db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, frank_id, false, frank_active_key );\n      BOOST_CHECK( db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, gavin_id, true, gavin_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, gavin_id, false, gavin_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      generate_block( database::skip_transaction_dupe_check );\n\n      // pass the hard fork time\n      generate_blocks( HARDFORK_CORE_584_TIME, true, database::skip_transaction_dupe_check );\n      set_expiration( db, tx );\n\n      // signing tests\n      sign( tx, alice_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, alice_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, bob_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, bob_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, cindy_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, cindy_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, daisy_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, daisy_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, edwin_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, edwin_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, frank_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, frank_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, gavin_owner_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      sign( tx, gavin_active_key );\n      PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      tx.clear_signatures();\n\n      // proposal tests\n      pid = new_proposal();\n      approve_proposal( pid, alice_id, true, alice_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, alice_id, false, alice_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, bob_id, true, bob_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, bob_id, false, bob_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, cindy_id, true, cindy_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, cindy_id, false, cindy_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, daisy_id, true, daisy_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, daisy_id, false, daisy_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, edwin_id, true, edwin_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, edwin_id, false, edwin_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, frank_id, true, frank_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, frank_id, false, frank_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, gavin_id, true, gavin_owner_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      pid = new_proposal();\n      approve_proposal( pid, gavin_id, false, gavin_active_key );\n      BOOST_CHECK( !db.find( pid ) );\n\n      generate_block( database::skip_transaction_dupe_check );\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( custom_operation_required_auths_before_fork ) {\n   try {\n      ACTORS((alice)(bob));\n      fund(alice, asset(10000000));\n      enable_fees();\n\n      // Unable to test custom_operation required auths before fork if hardfork already passed\n      BOOST_REQUIRE(db.head_block_time() < HARDFORK_CORE_210_TIME);\n\n      signed_transaction trx;\n      custom_operation op;\n      op.payer = alice_id;\n      op.required_auths.insert(bob_id);\n      op.fee = op.calculate_fee(db.current_fee_schedule().get<custom_operation>());\n      trx.operations.emplace_back(op);\n      trx.set_expiration(db.head_block_time() + 30);\n      sign(trx, alice_private_key);\n      // Op requires bob's authorization, but only alice signed. We're before the fork, so this should work anyways.\n      db.push_transaction(trx);\n\n      // Now try the same thing, but with a proposal\n      proposal_create_operation pcop;\n      pcop.fee_paying_account = alice_id;\n      pcop.proposed_ops = {op_wrapper(op)};\n      pcop.expiration_time = db.head_block_time() + 10;\n      pcop.fee = pcop.calculate_fee(db.current_fee_schedule().get<proposal_create_operation>());\n      trx.operations = {pcop};\n      trx.signatures.clear();\n      sign(trx, alice_private_key);\n      proposal_id_type pid { db.push_transaction(trx).operation_results[0].get<object_id_type>() };\n\n      // Check bob is not listed as a required approver\n      BOOST_REQUIRE_EQUAL(pid(db).required_active_approvals.count(bob_id), 0);\n\n      // Add alice's approval\n      proposal_update_operation puop;\n      puop.fee_paying_account = alice_id;\n      puop.proposal = pid;\n      puop.active_approvals_to_add = {alice_id};\n      puop.fee = puop.calculate_fee(db.current_fee_schedule().get<proposal_update_operation>());\n      trx.operations = {puop};\n      trx.signatures.clear();\n      sign(trx, alice_private_key);\n      db.push_transaction(trx);\n\n      // The proposal should have processed. Check it's not still in the database\n      BOOST_REQUIRE(db.find(pid) == nullptr);\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( custom_operation_required_auths_after_fork ) {\n   try {\n      ACTORS((alice)(bob));\n      fund(alice, asset(10000000));\n\n      if (db.head_block_time() < HARDFORK_CORE_210_TIME)\n         generate_blocks(HARDFORK_CORE_210_TIME + 10);\n\n      enable_fees();\n\n      signed_transaction trx;\n      custom_operation op;\n      op.payer = alice_id;\n      op.required_auths.insert(bob_id);\n      op.fee = op.calculate_fee(db.current_fee_schedule().get<custom_operation>());\n      trx.operations.emplace_back(op);\n      trx.set_expiration(db.head_block_time() + 30);\n      sign(trx, alice_private_key);\n      // Op require's bob's authorization, but only alice signed. This should throw.\n      GRAPHENE_REQUIRE_THROW(db.push_transaction(trx), tx_missing_active_auth);\n      sign(trx, bob_private_key);\n      // Now that bob has signed, it should work.\n      PUSH_TX(db, trx);\n\n      // Now try the same thing, but with a proposal\n      proposal_create_operation pcop;\n      pcop.fee_paying_account = alice_id;\n      pcop.proposed_ops = {op_wrapper(op)};\n      pcop.expiration_time = db.head_block_time() + 10;\n      pcop.fee = pcop.calculate_fee(db.current_fee_schedule().get<proposal_create_operation>());\n      trx.operations = {pcop};\n      trx.signatures.clear();\n      sign(trx, alice_private_key);\n      proposal_id_type pid { db.push_transaction(trx).operation_results[0].get<object_id_type>() };\n\n      // Check bob is listed as a required approver\n      BOOST_REQUIRE_EQUAL(pid(db).required_active_approvals.count(bob_id), 1);\n\n      // Add alice's approval\n      proposal_update_operation puop;\n      puop.fee_paying_account = alice_id;\n      puop.proposal = pid;\n      puop.active_approvals_to_add = {alice_id};\n      puop.fee = puop.calculate_fee(db.current_fee_schedule().get<proposal_update_operation>());\n      trx.operations = {puop};\n      trx.signatures.clear();\n      sign(trx, alice_private_key);\n      db.push_transaction(trx);\n\n      // The proposal should not have processed without bob's approval.\n      // Check it's still in the database\n      BOOST_REQUIRE_EQUAL(pid(db).required_active_approvals.count(bob_id), 1);\n\n      // Now add bob's approval\n      puop.active_approvals_to_add = {bob_id};\n      trx.operations = {puop};\n      trx.signatures.clear();\n      sign(trx, alice_private_key); // Alice still pays fee\n      sign(trx, bob_private_key);\n      db.push_transaction(trx);\n\n      // Now the proposal should have processed and been removed from the database\n      BOOST_REQUIRE(db.find(pid) == nullptr);\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( owner_delegation_test, database_fixture )\n{ try {\n   ACTORS( (alice)(bob) );\n\n   fc::ecc::private_key bob_active_key = fc::ecc::private_key::regenerate(fc::digest(\"bob_active\"));\n   fc::ecc::private_key bob_owner_key  = fc::ecc::private_key::regenerate(fc::digest(\"bob_owner\"));\n\n   trx.clear();\n\n   // Make sure Bob has different keys\n   account_update_operation auo;\n   auo.account = bob_id;\n   auo.active = authority( 1, public_key_type(bob_active_key.get_public_key()), 1 );\n   auo.owner  = authority( 1, public_key_type(bob_owner_key.get_public_key()), 1 );\n   trx.operations.push_back( auo );\n   sign( trx, bob_private_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n\n   // Delegate Alice's owner auth to herself and active auth to Bob\n   auo.account = alice_id;\n   auo.active = authority( 1, bob_id, 1 );\n   auo.owner  = authority( 1, alice_id, 1 );\n   trx.operations.push_back( auo );\n   sign( trx, alice_private_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n\n   // Now Bob has full control over Alice's account\n   auo.account = alice_id;\n   auo.active.reset();\n   auo.owner  = authority( 1, bob_id, 1 );\n   trx.operations.push_back( auo );\n   sign( trx, bob_active_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n} FC_LOG_AND_RETHROW() }\n\n/// This test case reproduces https://github.com/bitshares/bitshares-core/issues/944\n///                       and https://github.com/bitshares/bitshares-core/issues/580\nBOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture )\n{\n   try\n   {\n      ACTORS(\n         (alice)\n         );\n\n      auto set_auth = [&](\n         account_id_type aid,\n         const authority& active,\n         const authority& owner\n         )\n      {\n         signed_transaction tx;\n         account_update_operation op;\n         op.account = aid;\n         op.active = active;\n         op.owner = owner;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_transaction_signatures );\n      } ;\n\n      auto get_active = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).active);\n      } ;\n\n      auto get_owner = [&](\n         account_id_type aid\n         ) -> const authority*\n      {\n         return &(aid(db).owner);\n      } ;\n\n      fc::ecc::private_key alice_active_key = fc::ecc::private_key::regenerate(fc::digest(\"alice_active\"));\n      fc::ecc::private_key alice_owner_key = fc::ecc::private_key::regenerate(fc::digest(\"alice_owner\"));\n      public_key_type alice_active_pub( alice_active_key.get_public_key() );\n      public_key_type alice_owner_pub( alice_owner_key.get_public_key() );\n      set_auth( alice_id, authority( 1, alice_active_pub, 1 ), authority( 1, alice_owner_pub, 1 ) );\n\n      // creating a transaction that needs owner permission\n      signed_transaction tx;\n      account_update_operation op;\n      op.account = alice_id;\n      op.owner = authority( 1, alice_active_pub, 1 );\n      tx.operations.push_back( op );\n\n      // not signed, should throw tx_missing_owner_auth\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ),\n                              graphene::chain::tx_missing_owner_auth );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   true, false ),\n                              graphene::chain::tx_missing_owner_auth );\n\n      // signed with alice's active key, should throw tx_missing_owner_auth\n      sign( tx, alice_active_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ),\n                              graphene::chain::tx_missing_owner_auth );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   true, false ),\n                              graphene::chain::tx_missing_owner_auth );\n\n      // signed with alice's owner key, should not throw\n      tx.clear_signatures();\n      sign( tx, alice_owner_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n\n      // signed with both alice's owner key and active key,\n      // it does not throw due to https://github.com/bitshares/bitshares-core/issues/580\n      sign( tx, alice_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n\n      // creating a transaction that needs active permission\n      tx.clear();\n      op.owner.reset();\n      op.active = authority( 1, alice_owner_pub, 1 );\n      tx.operations.push_back( op );\n\n      // not signed, should throw tx_missing_active_auth\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ),\n                              graphene::chain::tx_missing_active_auth );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   true, false ),\n                              graphene::chain::tx_missing_active_auth );\n\n      // signed with alice's active key, should not throw\n      sign( tx, alice_active_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n\n      // signed with alice's owner key, should not throw\n      tx.clear_signatures();\n      sign( tx, alice_owner_key );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false );\n      tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false );\n\n      // signed with both alice's owner key and active key, should throw tx_irrelevant_sig\n      sign( tx, alice_active_key );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   false, false ),\n                              graphene::chain::tx_irrelevant_sig );\n      GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db),\n                                                   true, false ),\n                              graphene::chain::tx_irrelevant_sig );\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( nested_execution )\n{ try {\n   ACTORS( (alice)(bob) );\n   fund( alice );\n\n   generate_blocks( HARDFORK_CORE_214_TIME + fc::hours(1) );\n   set_expiration( db, trx );\n\n   const auto& gpo = db.get_global_properties();\n\n   proposal_create_operation pco;\n   pco.expiration_time = db.head_block_time() + fc::minutes(1);\n   pco.fee_paying_account = alice_id;\n   proposal_id_type inner;\n   {\n      transfer_operation top;\n      top.from = alice_id;\n      top.to = bob_id;\n      top.amount = asset( 10 );\n      pco.proposed_ops.emplace_back( top );\n      trx.operations.push_back( pco );\n      inner = PUSH_TX( db, trx, ~0 ).operation_results.front().get<object_id_type>();\n      trx.clear();\n      pco.proposed_ops.clear();\n   }\n\n   std::vector<proposal_id_type> nested;\n   nested.push_back( inner );\n   for( size_t i = 0; i < gpo.active_witnesses.size() * 2; i++ )\n   {\n      proposal_update_operation pup;\n      pup.fee_paying_account = alice_id;\n      pup.proposal = nested.back();\n      pup.active_approvals_to_add.insert( alice_id );\n      pco.proposed_ops.emplace_back( pup );\n      trx.operations.push_back( pco );\n      nested.push_back( proposal_id_type { PUSH_TX( db, trx, ~0 ).operation_results.front().get<object_id_type>() } );\n      trx.clear();\n      pco.proposed_ops.clear();\n   }\n\n   proposal_update_operation pup;\n   pup.fee_paying_account = alice_id;\n   pup.proposal = nested.back();\n   pup.active_approvals_to_add.insert( alice_id );\n   trx.operations.push_back( pup );\n   PUSH_TX( db, trx, ~0 );\n\n   for( size_t i = 1; i < nested.size(); i++ )\n      BOOST_CHECK_THROW( db.get( nested[i] ), fc::assert_exception ); // executed successfully -> object removed\n   db.get( inner ); // wasn't executed -> object exists, doesn't throw\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( issue_214 )\n{ try {\n   ACTORS( (alice)(bob) );\n   fund( alice );\n\n   generate_blocks( HARDFORK_CORE_214_TIME - fc::hours(1) );\n   set_expiration( db, trx );\n\n   // Bob proposes that Alice transfer 500 CORE to himself\n   transfer_operation top;\n   top.from = alice_id;\n   top.to = bob_id;\n   top.amount = asset( 500 );\n   proposal_create_operation pop;\n   pop.proposed_ops.emplace_back(top);\n   pop.fee_paying_account = bob_id;\n   pop.expiration_time = db.head_block_time() + fc::days(1);\n   trx.operations.push_back(pop);\n   sign( trx, bob_private_key );\n   const proposal_id_type pid1 { PUSH_TX( db, trx ).operation_results[0].get<object_id_type>() };\n   trx.clear();\n\n   // Bob wants to propose that Alice confirm the first proposal\n   proposal_update_operation pup;\n   pup.fee_paying_account = alice_id;\n   pup.proposal = pid1;\n   pup.active_approvals_to_add.insert( alice_id );\n   pop.proposed_ops.clear();\n   pop.proposed_ops.emplace_back( pup );\n   trx.operations.push_back(pop);\n   sign( trx, bob_private_key );\n   // before HF_CORE_214, Bob can't do that\n   BOOST_REQUIRE_THROW( PUSH_TX( db, trx ), fc::assert_exception );\n   trx.clear_signatures();\n\n   { // Bob can create a proposal nesting the one containing the proposal_update\n      proposal_create_operation npop;\n      npop.proposed_ops.emplace_back(pop);\n      npop.fee_paying_account = bob_id;\n      npop.expiration_time = db.head_block_time() + fc::days(2);\n      signed_transaction ntx;\n      set_expiration( db, ntx );\n      ntx.operations.push_back(npop);\n      sign( ntx, bob_private_key );\n      const proposal_id_type pid1a { PUSH_TX( db, ntx ).operation_results[0].get<object_id_type>() };\n      ntx.clear();\n\n      // But execution after confirming it fails\n      proposal_update_operation npup;\n      npup.fee_paying_account = bob_id;\n      npup.proposal = pid1a;\n      npup.active_approvals_to_add.insert( bob_id );\n      ntx.operations.push_back(npup);\n      sign( ntx, bob_private_key );\n      PUSH_TX( db, ntx );\n      ntx.clear();\n\n      db.get( pid1a ); // still exists\n   }\n\n   generate_blocks( HARDFORK_CORE_214_TIME + fc::hours(1) );\n   set_expiration( db, trx );\n   sign( trx, bob_private_key );\n   // after the HF the previously failed tx works too\n   const proposal_id_type pid2 { PUSH_TX( db, trx ).operation_results[0].get<object_id_type>() };\n   trx.clear();\n\n   // For completeness, Alice confirms Bob's second proposal\n   pup.proposal = pid2;\n   trx.operations.push_back(pup);\n   sign( trx, alice_private_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n\n   // Execution of the second proposal should have confirmed the first,\n   // which should have been executed by now.\n   BOOST_CHECK_THROW( db.get(pid1), fc::assert_exception );\n   BOOST_CHECK_THROW( db.get(pid2), fc::assert_exception );\n   BOOST_CHECK_EQUAL( top.amount.amount.value, get_balance( bob_id, top.amount.asset_id ) );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( irrelevant_signatures )\n{ try {\n   ACTORS( (alice)(bob) );\n   fund( alice );\n\n   // PK: BTS4vsFgTXJcGQMKCFayF2hrNRfYcKjNZ6Mzk8aw9M4zuWfscPhzE, A: BTSGfxPKKLj6tdTUB7i3mHsd2m7QvPLPy2YA\n   const fc::ecc::private_key test2 = fc::ecc::private_key::regenerate( fc::sha256::hash( std::string( \"test-2\" ) ) );\n   const public_key_type test2_pub( test2.get_public_key() );\n\n   // PK: BTS7FXC7S9UH7HEH8QiuJ8Xv1NRJJZd1GomALLm9ffjtH95Tb2ZQB, A: BTSBajRqmdrXqmDpZhJ8sgkGagdeXneHFVeM\n   const fc::ecc::private_key test3 = fc::ecc::private_key::regenerate( fc::sha256::hash( std::string( \"test-3\" ) ) );\n   const public_key_type test3_pub( test3.get_public_key() );\n\n   BOOST_REQUIRE( test2_pub.key_data < test3_pub.key_data );\n   BOOST_REQUIRE( address( test3_pub ) < address( test2_pub ) );\n\n   account_update_operation auo;\n   auo.account = alice_id;\n   auo.active = authority( 2, test2_pub, 2, test3_pub, 1 );\n\n   trx.clear();\n   set_expiration( db, trx );\n   trx.operations.push_back( auo );\n   sign( trx, alice_private_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n\n   transfer_operation to;\n   to.amount = asset( 1 );\n   to.from = alice_id;\n   to.to = bob_id;\n   trx.operations.push_back( to );\n   sign( trx, test2 );\n   sign( trx, test3 );\n   PUSH_TX( db, trx );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( self_approving_proposal )\n{ try {\n   ACTORS( (alice) );\n   fund( alice );\n\n   generate_blocks( HARDFORK_CORE_1479_TIME );\n   trx.clear();\n   set_expiration( db, trx );\n\n   proposal_update_operation pup;\n   pup.fee_paying_account = alice_id;\n   pup.proposal = proposal_id_type(0);\n   pup.active_approvals_to_add.insert( alice_id );\n\n   proposal_create_operation pop;\n   pop.proposed_ops.emplace_back(pup);\n   pop.fee_paying_account = alice_id;\n   pop.expiration_time = db.head_block_time() + fc::days(1);\n   trx.operations.push_back(pop);\n   const proposal_id_type pid1 { PUSH_TX( db, trx, ~0 ).operation_results[0].get<object_id_type>() };\n   trx.clear();\n   BOOST_REQUIRE_EQUAL( 0u, pid1.instance.value );\n   db.get(pid1);\n\n   trx.operations.push_back(pup);\n   PUSH_TX( db, trx, ~0 );\n\n   // Proposal failed and still exists\n   db.get(pid1);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( self_deleting_proposal )\n{ try {\n   ACTORS( (alice) );\n   fund( alice );\n\n   generate_blocks( HARDFORK_CORE_1479_TIME );\n   trx.clear();\n   set_expiration( db, trx );\n\n   proposal_delete_operation pdo;\n   pdo.fee_paying_account = alice_id;\n   pdo.proposal = proposal_id_type(0);\n   pdo.using_owner_authority = false;\n\n   proposal_create_operation pop;\n   pop.proposed_ops.emplace_back( pdo );\n   pop.fee_paying_account = alice_id;\n   pop.expiration_time = db.head_block_time() + fc::days(1);\n   trx.operations.push_back( pop );\n   const proposal_id_type pid1 { PUSH_TX( db, trx, ~0 ).operation_results[0].get<object_id_type>() };\n   trx.clear();\n   BOOST_REQUIRE_EQUAL( 0u, pid1.instance.value );\n   db.get(pid1);\n\n   proposal_update_operation pup;\n   pup.fee_paying_account = alice_id;\n   pup.proposal = proposal_id_type(0);\n   pup.active_approvals_to_add.insert( alice_id );\n   trx.operations.push_back(pup);\n   PUSH_TX( db, trx, ~0 );\n\n   // Proposal failed and still exists\n   db.get(pid1);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/basic_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/exceptions.hpp>\n\n#include <graphene/db/simple_index.hpp>\n\n#include <fc/crypto/digest.hpp>\n#include <fc/crypto/hex.hpp>\n#include \"../common/database_fixture.hpp\"\n\n#include <algorithm>\n#include <random>\n\nusing namespace graphene::chain;\nusing namespace graphene::db;\n\nBOOST_FIXTURE_TEST_SUITE( basic_tests, database_fixture )\n\n/**\n * Verify that names are RFC-1035 compliant https://tools.ietf.org/html/rfc1035\n * https://github.com/cryptonomex/graphene/issues/15\n */\nBOOST_AUTO_TEST_CASE( valid_name_test )\n{\n   BOOST_CHECK( is_valid_name( \"a\" ) );\n   BOOST_CHECK( !is_valid_name( \"A\" ) );\n   BOOST_CHECK( !is_valid_name( \"0\" ) );\n   BOOST_CHECK( !is_valid_name( \".\" ) );\n   BOOST_CHECK( !is_valid_name( \"-\" ) );\n\n   BOOST_CHECK( is_valid_name( \"aa\" ) );\n   BOOST_CHECK( !is_valid_name( \"aA\" ) );\n   BOOST_CHECK( is_valid_name( \"a0\" ) );\n   BOOST_CHECK( !is_valid_name( \"a.\" ) );\n   BOOST_CHECK( !is_valid_name( \"a-\" ) );\n\n   BOOST_CHECK( is_valid_name( \"aaa\" ) );\n   BOOST_CHECK( !is_valid_name( \"aAa\" ) );\n   BOOST_CHECK( is_valid_name( \"a0a\" ) );\n   BOOST_CHECK( is_valid_name( \"a.a\" ) );\n   BOOST_CHECK( is_valid_name( \"a-a\" ) );\n\n   BOOST_CHECK( is_valid_name( \"aa0\" ) );\n   BOOST_CHECK( !is_valid_name( \"aA0\" ) );\n   BOOST_CHECK( is_valid_name( \"a00\" ) );\n   BOOST_CHECK( !is_valid_name( \"a.0\" ) );\n   BOOST_CHECK( is_valid_name( \"a-0\" ) );\n\n   BOOST_CHECK(  is_valid_name( \"aaa-bbb-ccc\" ) );\n   BOOST_CHECK(  is_valid_name( \"aaa-bbb.ccc\" ) );\n\n   BOOST_CHECK( !is_valid_name( \"aaa,bbb-ccc\" ) );\n   BOOST_CHECK( !is_valid_name( \"aaa_bbb-ccc\" ) );\n   BOOST_CHECK( !is_valid_name( \"aaa-BBB-ccc\" ) );\n\n   BOOST_CHECK( !is_valid_name( \"1aaa-bbb\" ) );\n   BOOST_CHECK( !is_valid_name( \"-aaa-bbb-ccc\" ) );\n   BOOST_CHECK( !is_valid_name( \".aaa-bbb-ccc\" ) );\n   BOOST_CHECK( !is_valid_name( \"/aaa-bbb-ccc\" ) );\n\n   BOOST_CHECK( !is_valid_name( \"aaa-bbb-ccc-\" ) );\n   BOOST_CHECK( !is_valid_name( \"aaa-bbb-ccc.\" ) );\n   BOOST_CHECK( !is_valid_name( \"aaa-bbb-ccc..\" ) );\n   BOOST_CHECK( !is_valid_name( \"aaa-bbb-ccc/\" ) );\n\n   BOOST_CHECK( !is_valid_name( \"aaa..bbb-ccc\" ) );\n   BOOST_CHECK( is_valid_name( \"aaa.bbb-ccc\" ) );\n   BOOST_CHECK( is_valid_name( \"aaa.bbb.ccc\" ) );\n\n   BOOST_CHECK(  is_valid_name( \"aaa--bbb--ccc\" ) );\n   BOOST_CHECK(  is_valid_name( \"xn--sandmnnchen-p8a.de\" ) );\n   BOOST_CHECK(  is_valid_name( \"xn--sandmnnchen-p8a.dex\" ) );\n   BOOST_CHECK(  is_valid_name( \"xn-sandmnnchen-p8a.de\" ) );\n   BOOST_CHECK(  is_valid_name( \"xn-sandmnnchen-p8a.dex\" ) );\n\n   BOOST_CHECK(  is_valid_name( \"this-label-has-less-than-64-char.acters-63-to-be-really-precise\" ) );\n   BOOST_CHECK( !is_valid_name( \"this-label-has-more-than-63-char.act.ers-64-to-be-really-precise\" ) );\n   BOOST_CHECK( !is_valid_name( \"none.of.these.labels.has.more.than-63.chars--but.still.not.valid\" ) );\n}\n\nBOOST_AUTO_TEST_CASE( valid_symbol_test )\n{\n   BOOST_CHECK( !is_valid_symbol( \"A\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"a\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"0\" ) );\n   BOOST_CHECK( !is_valid_symbol( \".\" ) );\n\n   BOOST_CHECK( !is_valid_symbol( \"AA\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"Aa\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"A0\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"A.\" ) );\n\n   BOOST_CHECK( is_valid_symbol( \"AAA\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"AaA\" ) );\n   BOOST_CHECK( is_valid_symbol( \"A0A\" ) );\n   BOOST_CHECK( is_valid_symbol( \"A.A\" ) );\n\n   BOOST_CHECK( !is_valid_symbol( \"A..A\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"A.A.\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"A.A.A\" ) );\n\n   BOOST_CHECK( is_valid_symbol( \"AAAAAAAAAAAAAAAA\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"AAAAAAAAAAAAAAAAA\" ) );\n   BOOST_CHECK( is_valid_symbol( \"A.AAAAAAAAAAAAAA\" ) );\n   BOOST_CHECK( !is_valid_symbol( \"A.AAAAAAAAAAAA.A\" ) );\n\n   BOOST_CHECK( is_valid_symbol( \"AAA000AAA\" ) );\n}\n\nBOOST_AUTO_TEST_CASE( object_id_test )\n{\n\n   uint64_t u56 = ((uint64_t)1)<<56;\n   BOOST_CHECK_THROW( object_id_type( 1, 0, u56 ), fc::exception );\n\n   object_id_type o102( 1, 0, 2 );\n   BOOST_CHECK_THROW( (object_id<1,1>( o102 )), fc::exception );\n   BOOST_CHECK_THROW( (object_id<2,1>( o102 )), fc::exception );\n   BOOST_CHECK_THROW( (object_id<2,0>( o102 )), fc::exception );\n\n   BOOST_CHECK_THROW( (object_id<1,0>( u56 )), fc::exception );\n\n   object_id_type o1;\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\".1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1..1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1.\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"256.1.1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.256.1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"256.256.1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1.\") + std::to_string(u56) ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1.a\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.a.1\") ), o1 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"a.1.1\") ), o1 ), fc::exception );\n\n   from_variant( fc::variant( std::string(\"1.1.1234567\") ), o1 );\n   BOOST_CHECK( o1 == object_id_type( 1, 1, 1234567 ) );\n\n   object_id<1,1> o2;\n   BOOST_CHECK( o2 != o1 );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\".1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1..1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1.\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"2.1.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.2.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"2.2.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"256.1.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.256.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"256.256.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1.\") + std::to_string(u56) ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.1.a\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"1.a.1\") ), o2 ), fc::exception );\n   BOOST_CHECK_THROW( from_variant( fc::variant( std::string(\"a.1.1\") ), o2 ), fc::exception );\n\n   from_variant( fc::variant( std::string(\"1.1.1234567\") ), o2 );\n   BOOST_CHECK( o2 == o1 );\n\n}\n\nBOOST_AUTO_TEST_CASE( price_test )\n{\n    auto price_max = []( uint32_t a, uint32_t b )\n    {   return price::max( asset_id_type(a), asset_id_type(b) );   };\n    auto price_min = []( uint32_t a, uint32_t b )\n    {   return price::min( asset_id_type(a), asset_id_type(b) );   };\n\n    BOOST_CHECK( price_max(0,1) > price_min(0,1) );\n    BOOST_CHECK( price_max(1,0) > price_min(1,0) );\n    BOOST_CHECK( price_max(0,1) >= price_min(0,1) );\n    BOOST_CHECK( price_max(1,0) >= price_min(1,0) );\n    BOOST_CHECK( price_max(0,1) >= price_max(0,1) );\n    BOOST_CHECK( price_max(1,0) >= price_max(1,0) );\n    BOOST_CHECK( price_min(0,1) < price_max(0,1) );\n    BOOST_CHECK( price_min(1,0) < price_max(1,0) );\n    BOOST_CHECK( price_min(0,1) <= price_max(0,1) );\n    BOOST_CHECK( price_min(1,0) <= price_max(1,0) );\n    BOOST_CHECK( price_min(0,1) <= price_min(0,1) );\n    BOOST_CHECK( price_min(1,0) <= price_min(1,0) );\n    BOOST_CHECK( price_min(1,0) != price_max(1,0) );\n    BOOST_CHECK( ~price_max(0,1) != price_min(0,1) );\n    BOOST_CHECK( ~price_min(0,1) != price_max(0,1) );\n    BOOST_CHECK( ~price_max(0,1) == price_min(1,0) );\n    BOOST_CHECK( ~price_min(0,1) == price_max(1,0) );\n    BOOST_CHECK( ~price_max(0,1) < ~price_min(0,1) );\n    BOOST_CHECK( ~price_max(0,1) <= ~price_min(0,1) );\n    price a(asset(1), asset(2,asset_id_type(1)));\n    price b(asset(2), asset(2,asset_id_type(1)));\n    price c(asset(1), asset(2,asset_id_type(1)));\n    BOOST_CHECK(a < b);\n    BOOST_CHECK(b > a);\n    BOOST_CHECK(a == c);\n    BOOST_CHECK(!(b == c));\n\n    BOOST_CHECK_THROW( price( asset(-1), asset(1, asset_id_type(1)) ).validate(), fc::exception );\n    BOOST_CHECK_THROW( price( asset(0), asset(1, asset_id_type(1)) ).validate(), fc::exception );\n    BOOST_CHECK_THROW( price( asset(1), asset(0, asset_id_type(1)) ).validate(), fc::exception );\n    BOOST_CHECK_THROW( price( asset(1), asset(-1, asset_id_type(1)) ).validate(), fc::exception );\n    BOOST_CHECK_THROW( price( asset(1), asset(1) ).validate(), fc::exception );\n    BOOST_CHECK_THROW( price( asset(1, asset_id_type(1)), asset(1, asset_id_type(1)) ).validate(), fc::exception );\n\n    constexpr int64_t max_amount = GRAPHENE_MAX_SHARE_SUPPLY;\n    constexpr int64_t too_big_amount = GRAPHENE_MAX_SHARE_SUPPLY + 1;\n\n    price( asset(1), asset(max_amount, asset_id_type(1)) ).validate();\n    price( asset(max_amount), asset(1, asset_id_type(1)) ).validate();\n    price( asset(max_amount), asset(max_amount, asset_id_type(1)) ).validate();\n    price( asset(1), asset(max_amount, asset_id_type(1)) ).validate(true);\n    price( asset(max_amount), asset(1, asset_id_type(1)) ).validate(true);\n    price( asset(max_amount), asset(max_amount, asset_id_type(1)) ).validate(true);\n\n    price( asset(1), asset(too_big_amount, asset_id_type(1)) ).validate();\n    price( asset(too_big_amount), asset(1, asset_id_type(1)) ).validate();\n    price( asset(too_big_amount), asset(too_big_amount, asset_id_type(1)) ).validate();\n    BOOST_CHECK_THROW( price( asset(1), asset(too_big_amount, asset_id_type(1)) ).validate(true),\n                       fc::exception );\n    BOOST_CHECK_THROW( price( asset(too_big_amount), asset(1, asset_id_type(1)) ).validate(true),\n                       fc::exception );\n    BOOST_CHECK_THROW( price( asset(too_big_amount), asset(too_big_amount, asset_id_type(1)) ).validate(true),\n                       fc::exception );\n\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1)) * ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(0),  asset(1, asset_id_type(1))) * ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(-1), asset(1, asset_id_type(1))) * ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(0, asset_id_type(1))) * ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(-1, asset_id_type(1))) * ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) * ratio_type(0,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) * ratio_type(-1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) * ratio_type(1,0), std::domain_error ); // zero denominator\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) * ratio_type(1,-1), fc::exception );\n\n    GRAPHENE_REQUIRE_THROW( price(asset(0),  asset(1, asset_id_type(1))) / ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(-1), asset(1, asset_id_type(1))) / ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(0, asset_id_type(1))) / ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(-1, asset_id_type(1))) / ratio_type(1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) / ratio_type(0,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) / ratio_type(-1,1), fc::exception );\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) / ratio_type(1,0), std::domain_error ); // zero denominator\n    GRAPHENE_REQUIRE_THROW( price(asset(1),  asset(1, asset_id_type(1))) / ratio_type(1,-1), fc::exception );\n\n    BOOST_CHECK( price(asset(1), asset(1, asset_id_type(1))) * ratio_type(1,1) == price(asset(1), asset(1, asset_id_type(1))) );\n    BOOST_CHECK( price(asset(3), asset(2, asset_id_type(1))) * ratio_type(80,100) == price(asset(12), asset(10, asset_id_type(1))) );\n    BOOST_CHECK( price(asset(3), asset(2, asset_id_type(1))) * ratio_type(120,100) == price(asset(9), asset(5, asset_id_type(1))) );\n\n    BOOST_CHECK( price(asset(1), asset(1, asset_id_type(1))) / ratio_type(1,1) == price(asset(1), asset(1, asset_id_type(1))) );\n    BOOST_CHECK( price(asset(3), asset(2, asset_id_type(1))) / ratio_type(80,100) == price(asset(15), asset(8, asset_id_type(1))) );\n    BOOST_CHECK( price(asset(3), asset(2, asset_id_type(1))) / ratio_type(120,100) == price(asset(30), asset(24, asset_id_type(1))) );\n\n    BOOST_CHECK( price_max(0,1) * ratio_type(2,1) == price_max(0,1) );\n    BOOST_CHECK( price_max(0,1) * ratio_type(125317293,125317292) == price_max(0,1) );\n    BOOST_CHECK( price_max(0,1) * ratio_type(125317293,105317292) == price_max(0,1) );\n    BOOST_CHECK( price_max(0,1) * ratio_type(125317293,25317292) == price_max(0,1) );\n    BOOST_CHECK( price_min(0,1) * ratio_type(1,2) == price_min(0,1) );\n    BOOST_CHECK( price_min(0,1) * ratio_type(98752395,98752396) == price_min(0,1) );\n    BOOST_CHECK( price_min(0,1) * ratio_type(70000000,99999999) == price_min(0,1) );\n    BOOST_CHECK( price_min(0,1) * ratio_type(30000000,99999999) == price_min(0,1) );\n\n    price more_than_max = price_max(0,1);\n    more_than_max.base.amount *= 5;\n    more_than_max.quote.amount *= 3;\n    BOOST_CHECK( more_than_max * ratio_type(125317293,125317292) == more_than_max );\n    BOOST_CHECK( more_than_max * ratio_type(125317293,125317293) == more_than_max );\n    BOOST_CHECK( more_than_max * ratio_type(125317293,125317294) == price_max(0,1) );\n\n    price less_than_min = price_min(0,1);\n    less_than_min.base.amount *= 19;\n    less_than_min.quote.amount *= 47;\n    BOOST_CHECK( less_than_min * ratio_type(125317293,125317292) == price_min(0,1) );\n    BOOST_CHECK( less_than_min * ratio_type(125317293,125317293) == less_than_min );\n    BOOST_CHECK( less_than_min * ratio_type(125317293,125317294) == less_than_min );\n\n    price less_than_max = price_max(0,1);\n    less_than_max.quote.amount = 11;\n    BOOST_CHECK( less_than_max * ratio_type(7,1) == price(asset(less_than_max.base.amount*7/11),asset(1,asset_id_type(1))) );\n    less_than_max.quote.amount = 92131419;\n    BOOST_CHECK( less_than_max * ratio_type(7,1) == price(asset(less_than_max.base.amount*7/92131419),asset(1,asset_id_type(1))) );\n    less_than_max.quote.amount = 192131419;\n    BOOST_CHECK( less_than_max * ratio_type(7,1) == price(asset(less_than_max.base.amount.value*7>>3),asset(192131419>>3,asset_id_type(1))) );\n\n    price more_than_min = price_min(0,1);\n    more_than_min.base.amount = 11;\n    BOOST_CHECK( more_than_min * ratio_type(1,7) == price(asset(1),asset(more_than_min.quote.amount*7/11,asset_id_type(1))) );\n    more_than_min.base.amount = 64823;\n    BOOST_CHECK( more_than_min * ratio_type(31672,102472047) == price(asset(1),asset(static_cast<uint64_t>(fc::uint128_t(more_than_min.quote.amount.value)*102472047/(64823*31672)),asset_id_type(1))) );\n    more_than_min.base.amount = 13;\n    BOOST_CHECK( more_than_min * ratio_type(202472059,3) == price(asset((int64_t(13)*202472059)>>1),asset((more_than_min.quote.amount.value*3)>>1,asset_id_type(1))) ); // after >>1, quote = max*1.5, but gcd = 3, so quote/=3 = max/2, less than max\n\n    price less_than_max2 = price_max(0,1);\n    less_than_max2.base.amount *= 2;\n    less_than_max2.quote.amount *= 7;\n    BOOST_CHECK( less_than_max2 * ratio_type(1,1) == less_than_max2 );\n    BOOST_CHECK( less_than_max2 * ratio_type(5,2) == price(asset(less_than_max2.base.amount*5/2/7),asset(1,asset_id_type(1))) );\n\n    BOOST_CHECK( ( asset(1) * price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1, asset_id_type(1)) );\n    BOOST_CHECK( ( asset(1) * price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1, asset_id_type(1)) );\n    BOOST_CHECK( ( asset(1, asset_id_type(1)) * price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1) );\n    BOOST_CHECK( ( asset(1, asset_id_type(1)) * price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1) );\n\n    BOOST_CHECK( ( asset(3) * price( asset(3), asset(5, asset_id_type(1)) ) ) == asset(5, asset_id_type(1)) ); // round_down(3*5/3)\n    BOOST_CHECK( ( asset(5) * price( asset(2, asset_id_type(1)), asset(7) ) ) == asset(1, asset_id_type(1)) ); // round_down(5*2/7)\n    BOOST_CHECK( ( asset(7, asset_id_type(1)) * price( asset(2), asset(3, asset_id_type(1)) ) ) == asset(4) ); // round_down(7*2/3)\n    BOOST_CHECK( ( asset(9, asset_id_type(1)) * price( asset(8, asset_id_type(1)), asset(7) ) ) == asset(7) ); // round_down(9*7/8)\n\n    // asset and price doesn't match\n    BOOST_CHECK_THROW( asset(1) * price( asset(1, asset_id_type(2)), asset(1, asset_id_type(1)) ), fc::assert_exception );\n    // divide by zero\n    BOOST_CHECK_THROW( asset(1) * price( asset(0), asset(1, asset_id_type(1)) ), fc::assert_exception );\n    BOOST_CHECK_THROW( asset(1) * price( asset(1, asset_id_type(1)), asset(0) ), fc::assert_exception );\n    // overflow\n    BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1) * price( asset(1), asset(2, asset_id_type(1)) ), fc::assert_exception );\n    BOOST_CHECK_THROW( asset(2) * price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ), fc::assert_exception );\n\n    BOOST_CHECK( asset(1).multiply_and_round_up( price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1, asset_id_type(1)) );\n    BOOST_CHECK( asset(1).multiply_and_round_up( price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1, asset_id_type(1)) );\n    BOOST_CHECK( asset(1, asset_id_type(1)).multiply_and_round_up( price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1) );\n    BOOST_CHECK( asset(1, asset_id_type(1)).multiply_and_round_up( price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1) );\n\n    // round_up(3*5/3)\n    BOOST_CHECK( asset(3).multiply_and_round_up( price( asset(3), asset(5, asset_id_type(1)) ) ) == asset(5, asset_id_type(1)) );\n    // round_up(5*2/7)\n    BOOST_CHECK( asset(5).multiply_and_round_up( price( asset(2, asset_id_type(1)), asset(7) ) ) == asset(2, asset_id_type(1)) );\n    // round_up(7*2/3)\n    BOOST_CHECK( asset(7, asset_id_type(1)).multiply_and_round_up( price( asset(2), asset(3, asset_id_type(1)) ) ) == asset(5) );\n    // round_up(9*7/8)\n    BOOST_CHECK( asset(9, asset_id_type(1)).multiply_and_round_up( price( asset(8, asset_id_type(1)), asset(7) ) ) == asset(8) );\n\n    // asset and price doesn't match\n    BOOST_CHECK_THROW( asset(1, asset_id_type(3)).multiply_and_round_up( price( asset(1, asset_id_type(2)), asset(1) ) ),\n                       fc::assert_exception );\n    // divide by zero\n    BOOST_CHECK_THROW( asset(1).multiply_and_round_up( price( asset(0), asset(1, asset_id_type(1)) ) ), fc::assert_exception );\n    BOOST_CHECK_THROW( asset(1).multiply_and_round_up( price( asset(1, asset_id_type(1)), asset(0) ) ), fc::assert_exception );\n    // overflow\n    BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1).multiply_and_round_up( price( asset(1), asset(2, asset_id_type(1)) ) ),\n                       fc::assert_exception );\n    BOOST_CHECK_THROW( asset(2).multiply_and_round_up( price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ) ),\n                       fc::assert_exception );\n\n    price_feed dummy;\n    dummy.maintenance_collateral_ratio = 1002;\n    dummy.maximum_short_squeeze_ratio = 1234;\n    dummy.settlement_price = price(asset(1000), asset(2000, asset_id_type(1)));\n    price_feed dummy2 = dummy;\n    price_feed dummy3 = dummy;\n    dummy3.core_exchange_rate = price( asset(11), asset(13, asset_id_type(1)) );\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy ) );\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy2 ) );\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy3 ) );\n    dummy.maximum_short_squeeze_ratio = 1235;\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy ) );\n    BOOST_CHECK( !dummy.margin_call_params_equal( dummy2 ) );\n    BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) );\n    dummy2.maximum_short_squeeze_ratio = 1235;\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy ) );\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy2 ) );\n    BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) );\n    dummy2.maintenance_collateral_ratio = 1003;\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy ) );\n    BOOST_CHECK( !dummy.margin_call_params_equal( dummy2 ) );\n    BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) );\n    dummy3.maximum_short_squeeze_ratio = 1235;\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy3 ) );\n    dummy3.settlement_price = price( asset(1), asset(3, asset_id_type(1)) );\n    BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) );\n    dummy3.settlement_price = price( asset(1), asset(2, asset_id_type(1)) );\n    BOOST_CHECK( dummy.margin_call_params_equal( dummy3 ) );\n}\n\nBOOST_AUTO_TEST_CASE( price_multiplication_test )\n{ try {\n   // random test\n   std::mt19937_64 gen( time(NULL) );\n   std::uniform_int_distribution<int64_t> amt_uid(1, GRAPHENE_MAX_SHARE_SUPPLY);\n   std::uniform_int_distribution<int64_t> amt_uid2(1, 1000*1000*1000);\n   std::uniform_int_distribution<int64_t> amt_uid3(1, 1000*1000);\n   std::uniform_int_distribution<int64_t> amt_uid4(1, 1000);\n   asset a;\n   price p;\n   for( int i = 1*1000*1000; i > 0; --i )\n   {\n      if( i <= 30 )\n         a = asset( 0 );\n      else if( i % 4 == 0 )\n         a = asset( amt_uid(gen) );\n      else if( i % 4 == 1 )\n         a = asset( amt_uid2(gen) );\n      else if( i % 4 == 2 )\n         a = asset( amt_uid3(gen) );\n      else // if( i % 4 == 3 )\n         a = asset( amt_uid4(gen) );\n\n      if( i % 7 == 0 )\n         p = price( asset(amt_uid(gen)), asset(amt_uid(gen), asset_id_type(1)) );\n      else if( i % 7 == 1 )\n         p = price( asset(amt_uid2(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n      else if( i % 7 == 2 )\n         p = price( asset(amt_uid3(gen)), asset(amt_uid3(gen), asset_id_type(1)) );\n      else if( i % 7 == 3 )\n         p = price( asset(amt_uid4(gen)), asset(amt_uid4(gen), asset_id_type(1)) );\n      else if( i % 7 == 4 )\n         p = price( asset(amt_uid(gen)), asset(amt_uid(gen), asset_id_type(1)) );\n      else if( i % 7 == 5 )\n         p = price( asset(amt_uid4(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n      else // if( i % 7 == 6 )\n         p = price( asset(amt_uid2(gen)), asset(amt_uid4(gen), asset_id_type(1)) );\n\n      try\n      {\n         asset b = a * p;\n         asset a1 = b.multiply_and_round_up( p );\n         BOOST_CHECK( a1 <= a );\n         BOOST_CHECK( (a1 * p) == b );\n\n         b = a.multiply_and_round_up( p );\n         a1 = b * p;\n         BOOST_CHECK( a1 >= a );\n         BOOST_CHECK( a1.multiply_and_round_up( p ) == b );\n      }\n      catch( fc::assert_exception& e )\n      {\n         BOOST_CHECK( e.to_detail_string().find( \"result <= GRAPHENE_MAX_SHARE_SUPPLY\" ) != string::npos );\n      }\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( memo_test )\n{ try {\n   memo_data m;\n   auto sender = generate_private_key(\"1\");\n   auto receiver = generate_private_key(\"2\");\n   m.from = sender.get_public_key();\n   m.to = receiver.get_public_key();\n   m.set_message(sender, receiver.get_public_key(), \"Hello, world!\", 12345);\n\n   decltype(fc::digest(m)) hash(\"8de72a07d093a589f574460deb19023b4aff354b561eb34590d9f4629f51dbf3\");\n   if( fc::digest(m) != hash )\n   {\n      // If this happens, notify the web guys that the memo serialization format changed.\n      edump((m)(fc::digest(m)));\n      BOOST_FAIL(\"Memo format has changed. Notify the web guys and update this test.\");\n   }\n   BOOST_CHECK_EQUAL(m.get_message(receiver, sender.get_public_key()), \"Hello, world!\");\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( exceptions )\n{\n   GRAPHENE_CHECK_THROW(FC_THROW_EXCEPTION(balance_claim_invalid_claim_amount, \"Etc\"), balance_claim_invalid_claim_amount);\n}\n\nBOOST_AUTO_TEST_CASE( scaled_precision )\n{\n   const int64_t _k = 1000;\n   const int64_t _m = _k*_k;\n   const int64_t _g = _m*_k;\n   const int64_t _t = _g*_k;\n   const int64_t _p = _t*_k;\n   const int64_t _e = _p*_k;\n\n   BOOST_CHECK( asset::scaled_precision( 0) == share_type(   1   ) );\n   BOOST_CHECK( asset::scaled_precision( 1) == share_type(  10   ) );\n   BOOST_CHECK( asset::scaled_precision( 2) == share_type( 100   ) );\n   BOOST_CHECK( asset::scaled_precision( 3) == share_type(   1*_k) );\n   BOOST_CHECK( asset::scaled_precision( 4) == share_type(  10*_k) );\n   BOOST_CHECK( asset::scaled_precision( 5) == share_type( 100*_k) );\n   BOOST_CHECK( asset::scaled_precision( 6) == share_type(   1*_m) );\n   BOOST_CHECK( asset::scaled_precision( 7) == share_type(  10*_m) );\n   BOOST_CHECK( asset::scaled_precision( 8) == share_type( 100*_m) );\n   BOOST_CHECK( asset::scaled_precision( 9) == share_type(   1*_g) );\n   BOOST_CHECK( asset::scaled_precision(10) == share_type(  10*_g) );\n   BOOST_CHECK( asset::scaled_precision(11) == share_type( 100*_g) );\n   BOOST_CHECK( asset::scaled_precision(12) == share_type(   1*_t) );\n   BOOST_CHECK( asset::scaled_precision(13) == share_type(  10*_t) );\n   BOOST_CHECK( asset::scaled_precision(14) == share_type( 100*_t) );\n   BOOST_CHECK( asset::scaled_precision(15) == share_type(   1*_p) );\n   BOOST_CHECK( asset::scaled_precision(16) == share_type(  10*_p) );\n   BOOST_CHECK( asset::scaled_precision(17) == share_type( 100*_p) );\n   BOOST_CHECK( asset::scaled_precision(18) == share_type(   1*_e) );\n   GRAPHENE_CHECK_THROW( asset::scaled_precision(19), fc::exception );\n}\n\nBOOST_AUTO_TEST_CASE( merkle_root )\n{\n   clearable_block block;\n   vector<processed_transaction> tx;\n   vector<digest_type> t;\n   const uint32_t num_tx = 10;\n\n   for( uint32_t i=0; i<num_tx; i++ )\n   {\n      tx.emplace_back();\n      tx.back().ref_block_prefix = i;\n      t.push_back( tx.back().merkle_digest() );\n   }\n\n   auto c = []( const digest_type& digest ) -> checksum_type\n   {   return checksum_type::hash( digest );   };\n\n   auto d = []( const digest_type& left, const digest_type& right ) -> digest_type\n   {   return digest_type::hash( std::make_pair( left, right ) );   };\n\n   BOOST_CHECK( block.calculate_merkle_root() == checksum_type() );\n\n   block.transactions.push_back( tx[0] );\n   BOOST_CHECK( block.calculate_merkle_root() ==\n      c(t[0])\n      );\n\n   digest_type dA, dB, dC, dD, dE, dI, dJ, dK, dM, dN, dO;\n\n   /*\n      A=d(0,1)\n         / \\\n        0   1\n   */\n\n   dA = d(t[0], t[1]);\n\n   block.transactions.push_back( tx[1] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dA) );\n\n   /*\n            I=d(A,B)\n           /        \\\n      A=d(0,1)      B=2\n         / \\        /\n        0   1      2\n   */\n\n   dB = t[2];\n   dI = d(dA, dB);\n\n   block.transactions.push_back( tx[2] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dI) );\n\n   /*\n          I=d(A,B)\n           /    \\\n      A=d(0,1)   B=d(2,3)\n         / \\    /   \\\n        0   1  2     3\n   */\n\n   dB = d(t[2], t[3]);\n   dI = d(dA, dB);\n\n   block.transactions.push_back( tx[3] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dI) );\n\n   /*\n                     __M=d(I,J)__\n                    /            \\\n            I=d(A,B)              J=C\n           /        \\            /\n      A=d(0,1)   B=d(2,3)      C=4\n         / \\        / \\        /\n        0   1      2   3      4\n   */\n\n   dC = t[4];\n   dJ = dC;\n   dM = d(dI, dJ);\n\n   block.transactions.push_back( tx[4] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dM) );\n\n   /*\n                     __M=d(I,J)__\n                    /            \\\n            I=d(A,B)              J=C\n           /        \\            /\n      A=d(0,1)   B=d(2,3)   C=d(4,5)\n         / \\        / \\        / \\\n        0   1      2   3      4   5\n   */\n\n   dC = d(t[4], t[5]);\n   dJ = dC;\n   dM = d(dI, dJ);\n\n   block.transactions.push_back( tx[5] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dM) );\n\n   /*\n                     __M=d(I,J)__\n                    /            \\\n            I=d(A,B)              J=d(C,D)\n           /        \\            /        \\\n      A=d(0,1)   B=d(2,3)   C=d(4,5)      D=6\n         / \\        / \\        / \\        /\n        0   1      2   3      4   5      6\n   */\n\n   dD = t[6];\n   dJ = d(dC, dD);\n   dM = d(dI, dJ);\n\n   block.transactions.push_back( tx[6] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dM) );\n\n   /*\n                     __M=d(I,J)__\n                    /            \\\n            I=d(A,B)              J=d(C,D)\n           /        \\            /        \\\n      A=d(0,1)   B=d(2,3)   C=d(4,5)   D=d(6,7)\n         / \\        / \\        / \\        / \\\n        0   1      2   3      4   5      6   7\n   */\n\n   dD = d(t[6], t[7]);\n   dJ = d(dC, dD);\n   dM = d(dI, dJ);\n\n   block.transactions.push_back( tx[7] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dM) );\n\n   /*\n                                _____________O=d(M,N)______________\n                               /                                   \\\n                     __M=d(I,J)__                                  N=K\n                    /            \\                              /\n            I=d(A,B)              J=d(C,D)                 K=E\n           /        \\            /        \\            /\n      A=d(0,1)   B=d(2,3)   C=d(4,5)   D=d(6,7)      E=8\n         / \\        / \\        / \\        / \\        /\n        0   1      2   3      4   5      6   7      8\n   */\n\n   dE = t[8];\n   dK = dE;\n   dN = dK;\n   dO = d(dM, dN);\n\n   block.transactions.push_back( tx[8] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dO) );\n\n   /*\n                                _____________O=d(M,N)______________\n                               /                                   \\\n                     __M=d(I,J)__                                  N=K\n                    /            \\                              /\n            I=d(A,B)              J=d(C,D)                 K=E\n           /        \\            /        \\            /\n      A=d(0,1)   B=d(2,3)   C=d(4,5)   D=d(6,7)   E=d(8,9)\n         / \\        / \\        / \\        / \\        / \\\n        0   1      2   3      4   5      6   7      8   9\n   */\n\n   dE = d(t[8], t[9]);\n   dK = dE;\n   dN = dK;\n   dO = d(dM, dN);\n\n   block.transactions.push_back( tx[9] );\n   block.clear();\n   BOOST_CHECK( block.calculate_merkle_root() == c(dO) );\n}\n\n/**\n * Reproduces https://github.com/bitshares/bitshares-core/issues/888 and tests fix for it.\n */\nBOOST_AUTO_TEST_CASE( bitasset_feed_expiration_test )\n{\n   time_point_sec now = fc::time_point::now();\n\n   asset_bitasset_data_object o;\n\n   o.current_feed_publication_time = now - fc::hours(1);\n   o.options.feed_lifetime_sec = std::numeric_limits<uint32_t>::max() - 1;\n\n   BOOST_CHECK( !o.feed_is_expired( now ) );\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/bitasset_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Bitshares Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <vector>\n#include <boost/test/unit_test.hpp>\n\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/budget_record_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n#include <graphene/chain/asset_evaluator.hpp>\n\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/crypto/digest.hpp>\n#include <fc/log/log_message.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bitasset_tests, database_fixture )\n\n/*****\n * @brief helper method to change a backing asset to a new one\n * @param fixture the database_fixture\n * @param signing_key the signer\n * @param asset_id_to_update asset to update\n * @param new_backing_asset_id the new backing asset\n */\nvoid change_backing_asset(database_fixture& fixture, const fc::ecc::private_key& signing_key,\n      asset_id_type asset_id_to_update, asset_id_type new_backing_asset_id)\n{\n   try\n   {\n      asset_update_bitasset_operation ba_op;\n      const asset_object& asset_to_update = asset_id_to_update(fixture.db);\n      ba_op.asset_to_update = asset_id_to_update;\n      ba_op.issuer = asset_to_update.issuer;\n      ba_op.new_options.short_backing_asset = new_backing_asset_id;\n      fixture.trx.operations.push_back(ba_op);\n      fixture.sign(fixture.trx, signing_key);\n      PUSH_TX(fixture.db, fixture.trx, ~0);\n      fixture.generate_block();\n      fixture.trx.clear();\n   }\n   catch (fc::exception& ex)\n   {\n      BOOST_FAIL( \"Exception thrown in change_backing_asset. Exception was: \" +\n            ex.to_string(fc::log_level(fc::log_level::all)) );\n   }\n}\n\n/******\n * @brief helper method to turn witness_fed_asset on and off\n * @param fixture the database_fixture\n * @param new_issuer optionally change the issuer\n * @param signing_key signer\n * @param asset_id asset we want to change\n * @param witness_fed true if you want this to be a witness fed asset\n */\nvoid change_asset_options(database_fixture& fixture, const optional<account_id_type>& new_issuer,\n      const fc::ecc::private_key& signing_key,\n      asset_id_type asset_id, bool witness_fed)\n{\n   asset_update_operation op;\n   const asset_object& obj = asset_id(fixture.db);\n   op.asset_to_update = asset_id;\n   op.issuer = obj.issuer;\n   if (new_issuer)\n      op.new_issuer = new_issuer;\n   op.new_options = obj.options;\n   if (witness_fed)\n   {\n      op.new_options.flags |= witness_fed_asset;\n      op.new_options.flags &= ~committee_fed_asset;\n   }\n   else\n   {\n      op.new_options.flags &= ~witness_fed_asset; // we don't care about the committee flag here\n   }\n   fixture.trx.operations.push_back(op);\n   fixture.sign( fixture.trx, signing_key );\n   PUSH_TX( fixture.db, fixture.trx, ~0 );\n   fixture.generate_block();\n   fixture.trx.clear();\n\n}\n\n/*********\n * @brief helper method to create a coin backed by a bitasset\n * @param fixture the database_fixture\n * @param index added to name of the coin\n * @param backing the backing asset\n * @param signing_key the signing key\n */\nconst graphene::chain::asset_object& create_bitasset_backed(graphene::chain::database_fixture& fixture,\n      int index, graphene::chain::asset_id_type backing, const fc::ecc::private_key& signing_key)\n{\n   // create the coin\n   std::string name = \"COIN\" + std::to_string(index + 1) + \"TEST\";\n   const graphene::chain::asset_object& obj = fixture.create_bitasset(name);\n   asset_id_type asset_id = obj.get_id();\n   // adjust the backing asset\n   change_backing_asset(fixture, signing_key, asset_id, backing);\n   fixture.trx.set_expiration(fixture.db.get_dynamic_global_properties().next_maintenance_time);\n   return obj;\n}\n\n\n/*********\n * @brief make sure feeds still work after changing backing asset on a witness-fed asset\n */\nBOOST_AUTO_TEST_CASE( reset_backing_asset_on_witness_asset )\n{\n   ACTORS((nathan));\n\n   /*\n       // do a maintenance block\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      // generate blocks until close to hard fork\n      generate_blocks( HARDFORK_CORE_868_890_TIME - fc::hours(1) );\n    */\n\n   BOOST_TEST_MESSAGE(\"Advance to near hard fork\");\n   auto maint_interval = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks( HARDFORK_CORE_868_890_TIME - maint_interval);\n   trx.set_expiration(HARDFORK_CORE_868_890_TIME - fc::seconds(1));\n\n   BOOST_TEST_MESSAGE(\"Create USDBIT\");\n   asset_id_type bit_usd_id = create_bitasset(\"USDBIT\").get_id();\n   asset_id_type core_id = bit_usd_id(db).bitasset_data(db).options.short_backing_asset;\n\n   {\n      BOOST_TEST_MESSAGE(\"Update the USDBIT asset options\");\n      change_asset_options(*this, nathan_id, nathan_private_key, bit_usd_id, false );\n   }\n\n   BOOST_TEST_MESSAGE(\"Create JMJBIT based on USDBIT.\");\n   asset_id_type bit_jmj_id = create_bitasset(\"JMJBIT\").get_id();\n   {\n      BOOST_TEST_MESSAGE(\"Update the JMJBIT asset options\");\n      change_asset_options(*this, nathan_id, nathan_private_key, bit_jmj_id, true );\n   }\n\n   {\n      BOOST_TEST_MESSAGE(\"Update the JMJBIT bitasset options\");\n      asset_update_bitasset_operation ba_op;\n      const asset_object& obj = bit_jmj_id(db);\n      ba_op.asset_to_update = obj.get_id();\n      ba_op.issuer = obj.issuer;\n      ba_op.new_options.short_backing_asset = bit_usd_id;\n      ba_op.new_options.minimum_feeds = 1;\n      trx.operations.push_back(ba_op);\n      sign(trx, nathan_private_key);\n      PUSH_TX(db, trx, ~0);\n      generate_block();\n      trx.clear();\n   }\n\n   BOOST_TEST_MESSAGE(\"Grab active witnesses\");\n   auto& global_props = db.get_global_properties();\n   std::vector<account_id_type> active_witnesses;\n   for(const witness_id_type& wit_id : global_props.active_witnesses)\n      active_witnesses.push_back(wit_id(db).witness_account);\n   BOOST_REQUIRE_EQUAL(active_witnesses.size(), INITIAL_WITNESS_COUNT);\n\n   {\n      BOOST_TEST_MESSAGE(\"Adding price feed 1\");\n      publish_feed(active_witnesses[0], bit_usd_id, 1, bit_jmj_id, 300, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 300.0);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Adding price feed 2\");\n      publish_feed(active_witnesses[1], bit_usd_id, 1, bit_jmj_id, 100, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 300.0);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Adding price feed 3\");\n      publish_feed(active_witnesses[2], bit_usd_id, 1, bit_jmj_id, 1, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 100.0);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Change underlying asset of bit_jmj from bit_usd to core\");\n      change_backing_asset(*this, nathan_private_key, bit_jmj_id, core_id);\n\n      BOOST_TEST_MESSAGE(\"Verify feed producers have not been reset\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 3ul);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"With underlying bitasset changed from one to another, price feeds should still be publish-able\");\n      BOOST_TEST_MESSAGE(\"Re-Adding Witness 1 price feed\");\n      publish_feed(active_witnesses[0], core_id, 1, bit_jmj_id, 30, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 1);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      BOOST_CHECK(bitasset.current_feed.core_exchange_rate.base.asset_id != bitasset.current_feed.core_exchange_rate.quote.asset_id);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Re-Adding Witness 2 price feed\");\n      publish_feed(active_witnesses[1], core_id, 1, bit_jmj_id, 100, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 100);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Advance to after hard fork\");\n      generate_blocks( HARDFORK_CORE_868_890_TIME + fc::seconds(1));\n      trx.set_expiration(HARDFORK_CORE_868_890_TIME + fc::hours(2));\n\n      BOOST_TEST_MESSAGE(\"After hardfork, 1 feed should have been erased\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 2ul);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"After hardfork, change underlying asset of bit_jmj from core to bit_usd\");\n      change_backing_asset(*this, nathan_private_key, bit_jmj_id, bit_usd_id);\n\n      BOOST_TEST_MESSAGE(\"Verify feed producers have been reset\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 0ul);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"With underlying bitasset changed from one to another, price feeds should still be publish-able\");\n      BOOST_TEST_MESSAGE(\"Re-Adding Witness 1 price feed\");\n      publish_feed(active_witnesses[0], bit_usd_id, 1, bit_jmj_id, 30, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      BOOST_CHECK(bitasset.current_feed.core_exchange_rate.base.asset_id != bitasset.current_feed.core_exchange_rate.quote.asset_id);\n   }\n}\n\n/****\n * @brief make sure feeds work correctly after changing the backing asset on a non-witness-fed asset\n */\nBOOST_AUTO_TEST_CASE( reset_backing_asset_on_non_witness_asset )\n{\n   ACTORS((nathan)(dan)(ben)(vikram));\n\n   BOOST_TEST_MESSAGE(\"Advance to near hard fork\");\n   auto maint_interval = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks( HARDFORK_CORE_868_890_TIME - maint_interval);\n   trx.set_expiration(HARDFORK_CORE_868_890_TIME - fc::seconds(1));\n\n\n   BOOST_TEST_MESSAGE(\"Create USDBIT\");\n   asset_id_type bit_usd_id = create_bitasset(\"USDBIT\").get_id();\n   asset_id_type core_id = bit_usd_id(db).bitasset_data(db).options.short_backing_asset;\n\n   {\n      BOOST_TEST_MESSAGE(\"Update the USDBIT asset options\");\n      change_asset_options(*this, nathan_id, nathan_private_key, bit_usd_id, false );\n   }\n\n   BOOST_TEST_MESSAGE(\"Create JMJBIT based on USDBIT.\");\n   asset_id_type bit_jmj_id = create_bitasset(\"JMJBIT\").get_id();\n   {\n      BOOST_TEST_MESSAGE(\"Update the JMJBIT asset options\");\n      change_asset_options(*this, nathan_id, nathan_private_key, bit_jmj_id, false );\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Update the JMJBIT bitasset options\");\n      asset_update_bitasset_operation ba_op;\n      const asset_object& obj = bit_jmj_id(db);\n      ba_op.asset_to_update = obj.get_id();\n      ba_op.issuer = obj.issuer;\n      ba_op.new_options.short_backing_asset = bit_usd_id;\n      ba_op.new_options.minimum_feeds = 1;\n      trx.operations.push_back(ba_op);\n      sign(trx, nathan_private_key);\n      PUSH_TX(db, trx, ~0);\n      generate_block();\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Set feed producers for JMJBIT\");\n      asset_update_feed_producers_operation op;\n      op.asset_to_update = bit_jmj_id;\n      op.issuer = nathan_id;\n      op.new_feed_producers = {dan_id, ben_id, vikram_id};\n      trx.operations.push_back(op);\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx, ~0 );\n      generate_block();\n      trx.clear();\n   }\n\n   {\n      BOOST_TEST_MESSAGE(\"Verify feed producers are registered for JMJBIT\");\n      const asset_bitasset_data_object& obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(obj.feeds.size(), 3ul);\n      BOOST_CHECK( obj.current_feed.margin_call_params_equal( price_feed() ) );\n\n      BOOST_CHECK( bit_usd_id == obj.options.short_backing_asset );\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Adding Vikram's price feed\");\n      publish_feed(vikram_id, bit_usd_id, 1, bit_jmj_id, 300, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 300.0);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Adding Ben's pricing to JMJBIT\");\n      publish_feed(ben_id, bit_usd_id, 1, bit_jmj_id, 100, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 300);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Adding Dan's pricing to JMJBIT\");\n      publish_feed(dan_id, bit_usd_id, 1, bit_jmj_id, 1, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 100);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n      generate_block();\n      trx.clear();\n\n      BOOST_CHECK(bitasset.current_feed.core_exchange_rate.base.asset_id != bitasset.current_feed.core_exchange_rate.quote.asset_id);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Change underlying asset of bit_jmj from bit_usd to core\");\n      change_backing_asset(*this, nathan_private_key, bit_jmj_id, core_id);\n\n      BOOST_TEST_MESSAGE(\"Verify feed producers have not been reset\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 3ul);\n      for(const auto& feed : jmj_obj.feeds) {\n         BOOST_CHECK(!feed.second.second.settlement_price.is_null());\n      }\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Add a new (and correct) feed price for 1 feed producer\");\n      publish_feed(vikram_id, core_id, 1, bit_jmj_id, 300, core_id);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Advance to past hard fork\");\n      generate_blocks( HARDFORK_CORE_868_890_TIME + maint_interval);\n      trx.set_expiration(HARDFORK_CORE_868_890_TIME + fc::hours(48));\n\n      BOOST_TEST_MESSAGE(\"Verify that the incorrect feeds have been corrected\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 3ul);\n      int nan_count = 0;\n      for(const auto& feed : jmj_obj.feeds)\n      {\n         if (feed.second.second.settlement_price.is_null())\n            nan_count++;\n      }\n      BOOST_CHECK_EQUAL(nan_count, 2);\n      // the settlement price will be NaN until 50% of price feeds are valid\n      //BOOST_CHECK_EQUAL(jmj_obj.current_feed.settlement_price.to_real(), 300);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"After hardfork, change underlying asset of bit_jmj from core to bit_usd\");\n      change_backing_asset(*this, nathan_private_key, bit_jmj_id, bit_usd_id);\n\n      BOOST_TEST_MESSAGE(\"Verify feed producers have been reset\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 3ul);\n      for(const auto& feed : jmj_obj.feeds)\n      {\n         BOOST_CHECK(feed.second.second.settlement_price.is_null());\n      }\n   }\n   {\n      BOOST_TEST_MESSAGE(\"With underlying bitasset changed from one to another, price feeds should still be publish-able\");\n      BOOST_TEST_MESSAGE(\"Adding Vikram's price feed\");\n      publish_feed(vikram_id, bit_usd_id, 1, bit_jmj_id, 30, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      BOOST_TEST_MESSAGE(\"Adding Ben's pricing to JMJBIT\");\n      publish_feed(ben_id, bit_usd_id, 1, bit_jmj_id, 25, core_id);\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      BOOST_TEST_MESSAGE(\"Adding Dan's pricing to JMJBIT\");\n      publish_feed(dan_id, bit_usd_id, 1, bit_jmj_id, 10, core_id);\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 25);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n      generate_block();\n      trx.clear();\n\n      BOOST_CHECK(bitasset.current_feed.core_exchange_rate.base.asset_id != bitasset.current_feed.core_exchange_rate.quote.asset_id);\n   }\n}\n\n/*********\n * @brief Update median feeds after feed_lifetime_sec changed\n */\nBOOST_AUTO_TEST_CASE( hf_890_test )\n{\n   uint32_t skip = database::skip_witness_signature\n                 | database::skip_transaction_signatures\n                 | database::skip_transaction_dupe_check\n                 | database::skip_block_size_check\n                 | database::skip_tapos_check\n                 | database::skip_merkle_check\n                 ;\n   generate_blocks(HARDFORK_615_TIME, true, skip); // get around Graphene issue #615 feed expiration bug\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n\n   auto hf_time = HARDFORK_CORE_868_890_TIME;\n   if(hf2481)\n      hf_time = HARDFORK_CORE_2481_TIME;\n   else if(hf1270)\n      hf_time = HARDFORK_CORE_1270_TIME;\n\n   for( int i=0; i<2; ++i )\n   {\n      int blocks = 0;\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n      if( i == 1 ) // go beyond hard fork\n      {\n         blocks += generate_blocks(hf_time - mi, true, skip);\n         blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n      }\n      set_expiration( db, trx );\n\n      ACTORS((buyer)(seller)(borrower)(feedproducer));\n\n      int64_t init_balance(1000000);\n\n      transfer(committee_account, buyer_id, asset(init_balance));\n      transfer(committee_account, borrower_id, asset(init_balance));\n\n      const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n      asset_id_type usd_id = bitusd.get_id();\n\n      {\n         // change feed lifetime\n         const asset_object& asset_to_update = usd_id(db);\n         asset_update_bitasset_operation ba_op;\n         ba_op.asset_to_update = usd_id;\n         ba_op.issuer = asset_to_update.issuer;\n         ba_op.new_options = asset_to_update.bitasset_data(db).options;\n         ba_op.new_options.feed_lifetime_sec = 600;\n         trx.operations.push_back(ba_op);\n         PUSH_TX(db, trx, ~0);\n         trx.clear();\n      }\n\n      // prepare feed data\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n\n      // set price feed\n      update_feed_producers( usd_id(db), {feedproducer_id} );\n      current_feed.settlement_price = asset(100, usd_id) / asset(5);\n      publish_feed( usd_id, feedproducer_id, current_feed );\n\n      // Place some collateralized orders\n      // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700\n      borrow( borrower_id, asset(100, usd_id), asset(15) );\n\n      transfer( borrower_id, seller_id, asset(100, usd_id) );\n\n      // Adjust price feed to get call order into margin call territory\n      current_feed.settlement_price = asset(100, usd_id) / asset(10);\n      publish_feed( usd_id, feedproducer_id, current_feed );\n      // settlement price = 100 USD / 10 CORE, mssp = 100/11 USD/CORE\n\n      // let the feed expire\n      blocks += generate_blocks( db.head_block_time() + 1200, true, skip );\n      set_expiration( db, trx );\n\n      // check: median feed should be null\n      BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // place a sell order, it won't be matched with the call order\n      limit_order_id_type sell_id = create_sell_order(seller_id, asset(10, usd_id), asset(1))->get_id();\n\n      {\n         // change feed lifetime to longer\n         const asset_object& asset_to_update = usd_id(db);\n         asset_update_bitasset_operation ba_op;\n         ba_op.asset_to_update = usd_id;\n         ba_op.issuer = asset_to_update.issuer;\n         ba_op.new_options = asset_to_update.bitasset_data(db).options;\n         ba_op.new_options.feed_lifetime_sec = hf_time.sec_since_epoch()\n                                             - db.head_block_time().sec_since_epoch()\n                                             + mi\n                                             + 1800;\n         trx.operations.push_back(ba_op);\n         PUSH_TX(db, trx, ~0);\n         trx.clear();\n      }\n\n      // check\n      if( i == 0 ) // before hard fork, median feed is still null, and limit order is still there\n      {\n         BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n         BOOST_CHECK( db.find( sell_id ) );\n\n         // go beyond hard fork\n         blocks += generate_blocks(hf_time - mi, true, skip);\n         blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n      }\n\n      // after hard fork, median feed should become valid, and the limit order should be filled\n      {\n         BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n         BOOST_CHECK( !db.find( sell_id ) );\n      }\n\n      // undo above tx's and reset\n      generate_block( skip );\n      ++blocks;\n      while( blocks > 0 )\n      {\n         db.pop_block();\n         --blocks;\n      }\n   }\n}\n\nclass bitasset_evaluator_wrapper : public asset_update_bitasset_evaluator\n{\npublic:\n   void set_db(database& db)\n   {\n      this->trx_state = new transaction_evaluation_state(&db);\n   }\n};\n\nstruct assets_922_931\n{\n   asset_id_type bit_usd;\n   asset_id_type bit_usdbacked;\n   asset_id_type bit_usdbacked2;\n   asset_id_type bit_child_bitasset;\n   asset_id_type bit_parent;\n   asset_id_type user_issued;\n   asset_id_type six_precision;\n   asset_id_type prediction;\n};\n\nassets_922_931 create_assets_922_931(database_fixture* fixture)\n{\n   assets_922_931 asset_objs;\n   BOOST_TEST_MESSAGE( \"Create USDBIT\" );\n   asset_objs.bit_usd = fixture->create_bitasset( \"USDBIT\", GRAPHENE_COMMITTEE_ACCOUNT ).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create USDBACKED\" );\n   asset_objs.bit_usdbacked = fixture->create_bitasset( \"USDBACKED\", GRAPHENE_COMMITTEE_ACCOUNT,\n         100, charge_market_fee, 2, asset_objs.bit_usd ).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create USDBACKEDII\" );\n   asset_objs.bit_usdbacked2 = fixture->create_bitasset( \"USDBACKEDII\", GRAPHENE_WITNESS_ACCOUNT,\n         100, charge_market_fee, 2, asset_objs.bit_usd ).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create PARENT\" );\n   asset_objs.bit_parent = fixture->create_bitasset( \"PARENT\", GRAPHENE_WITNESS_ACCOUNT).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create CHILDUSER\" );\n   asset_objs.bit_child_bitasset = fixture->create_bitasset( \"CHILDUSER\", GRAPHENE_WITNESS_ACCOUNT,\n         100, charge_market_fee, 2, asset_objs.bit_parent ).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create user issued USERISSUED\" );\n   asset_objs.user_issued = fixture->create_user_issued_asset( \"USERISSUED\",\n         GRAPHENE_WITNESS_ACCOUNT(fixture->db), charge_market_fee ).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create a user-issued asset with a precision of 6\" );\n   asset_objs.six_precision = fixture->create_user_issued_asset( \"SIXPRECISION\", GRAPHENE_WITNESS_ACCOUNT(fixture->db),\n         charge_market_fee, price(asset(1, asset_id_type(1)), asset(1)), 6 ).get_id();\n\n   BOOST_TEST_MESSAGE( \"Create Prediction market with precision of 6, backed by SIXPRECISION\" );\n   asset_objs.prediction = fixture->create_prediction_market( \"PREDICTION\", GRAPHENE_WITNESS_ACCOUNT,\n         100, charge_market_fee, 6, asset_objs.six_precision ).get_id();\n\n   return asset_objs;\n}\n/******\n * @brief Test various bitasset asserts within the asset_evaluator before the HF 922 / 931\n */\nBOOST_AUTO_TEST_CASE( bitasset_evaluator_test_before_922_931 )\n{\n   BOOST_TEST_MESSAGE(\"Advance to near hard fork 922 / 931\");\n   auto global_params = db.get_global_properties().parameters;\n   generate_blocks( HARDFORK_CORE_922_931_TIME - global_params.maintenance_interval );\n   trx.set_expiration( HARDFORK_CORE_922_931_TIME - global_params.maintenance_interval + global_params.maximum_time_until_expiration );\n\n   ACTORS( (nathan) (john) );\n\n   assets_922_931 asset_objs = create_assets_922_931( this );\n   const asset_id_type bit_usd_id = asset_objs.bit_usd;\n\n   // make a generic operation\n   bitasset_evaluator_wrapper evaluator;\n   evaluator.set_db(db);\n   asset_update_bitasset_operation op;\n   op.asset_to_update = bit_usd_id;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n   op.new_options = asset_objs.bit_usd(db).bitasset_data(db).options;\n\n   // this should pass\n   BOOST_TEST_MESSAGE( \"Evaluating a good operation\" );\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n\n   // test with a market issued asset\n   BOOST_TEST_MESSAGE( \"Sending a non-bitasset.\" );\n   op.asset_to_update = asset_objs.user_issued;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"on a non-BitAsset.\" );\n   op.asset_to_update = bit_usd_id;\n\n   // test changing issuer\n   BOOST_TEST_MESSAGE( \"Test changing issuer.\" );\n   account_id_type original_issuer = op.issuer;\n   op.issuer = john_id;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Only asset issuer can update\" );\n   op.issuer = original_issuer;\n\n   // bad backing_asset\n   BOOST_TEST_MESSAGE( \"Non-existent backing asset.\" );\n   asset_id_type correct_asset_id = op.new_options.short_backing_asset;\n   op.new_options.short_backing_asset = asset_id_type();\n   op.new_options.short_backing_asset.instance = 123;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Unable to find Object\" );\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // now check the things that are wrong, but still pass before HF 922 / 931\n   BOOST_TEST_MESSAGE( \"Now check the things that are wrong, but still pass before HF 922 / 931\" );\n\n   // back by self\n   BOOST_TEST_MESSAGE( \"Message should contain: op.new_options.short_backing_asset == asset_obj.get_id()\" );\n   op.new_options.short_backing_asset = bit_usd_id;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // prediction market with different precision\n   BOOST_TEST_MESSAGE( \"Message should contain: for a PM, asset_obj.precision != new_backing_asset.precision\" );\n   op.asset_to_update = asset_objs.prediction;\n   op.issuer = asset_objs.prediction(db).issuer;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.asset_to_update = bit_usd_id;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n\n   // checking old backing asset instead of new backing asset\n   BOOST_TEST_MESSAGE( \"Message should contain: to be backed by an asset which is not market issued asset nor CORE\" );\n   op.new_options.short_backing_asset = asset_objs.six_precision;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   BOOST_TEST_MESSAGE( \"Message should contain: modified a blockchain-controlled market asset to be backed by an asset \"\n         \"which is not backed by CORE\" );\n   op.new_options.short_backing_asset = asset_objs.prediction;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // CHILDUSER is a non-committee asset backed by PARENT which is backed by CORE\n   // Cannot change PARENT's backing asset from CORE to something that is not [CORE | UIA]\n   // because that will make CHILD be backed by an asset that is not itself backed by CORE or a UIA.\n   BOOST_TEST_MESSAGE( \"Message should contain: but this asset is a backing asset for another MPA, which would cause MPA \"\n         \"backed by MPA backed by MPA.\" );\n   op.asset_to_update = asset_objs.bit_parent;\n   op.issuer = asset_objs.bit_parent(db).issuer;\n   op.new_options.short_backing_asset = asset_objs.bit_usdbacked;\n   // this should generate a warning in the log, but not fail.\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   // changing the backing asset to a UIA should work\n   BOOST_TEST_MESSAGE( \"Switching to a backing asset that is a UIA should work. No warning should be produced.\" );\n   op.new_options.short_backing_asset = asset_objs.user_issued;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   // A -> B -> C, change B to be backed by A (circular backing)\n   BOOST_TEST_MESSAGE( \"Message should contain: A cannot be backed by B which is backed by A.\" );\n   op.new_options.short_backing_asset = asset_objs.bit_child_bitasset;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.new_options.short_backing_asset = asset_objs.user_issued;\n   BOOST_TEST_MESSAGE( \"Message should contain: but this asset is a backing asset for a committee-issued asset.\" );\n   // CHILDCOMMITTEE is a committee asset backed by PARENT which is backed by CORE\n   // Cannot change PARENT's backing asset from CORE to something else because that will make CHILD be backed by\n   // an asset that is not itself backed by CORE\n   create_bitasset( \"CHILDCOMMITTEE\", GRAPHENE_COMMITTEE_ACCOUNT, 100, charge_market_fee, 2,\n         asset_objs.bit_parent );\n   // it should again work, generating 2 warnings in the log. 1 for the above, and 1 new one.\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.asset_to_update = asset_objs.bit_usd;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // USDBACKED is backed by USDBIT (which is backed by CORE)\n   // USDBACKEDII is backed by USDBIT\n   // We should not be able to make USDBACKEDII be backed by USDBACKED\n   // because that would be a MPA backed by MPA backed by MPA.\n   BOOST_TEST_MESSAGE( \"Message should contain: a BitAsset cannot be backed by a BitAsset that \"\n         \"itself is backed by a BitAsset.\" );\n   op.asset_to_update = asset_objs.bit_usdbacked2;\n   op.issuer = asset_objs.bit_usdbacked2(db).issuer;\n   op.new_options.short_backing_asset = asset_objs.bit_usdbacked;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   // set everything to a more normal state\n   op.asset_to_update = asset_objs.bit_usdbacked;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n   op.new_options.short_backing_asset = asset_id_type();\n\n   // Feed lifetime must exceed block interval\n   BOOST_TEST_MESSAGE( \"Message should contain: op.new_options.feed_lifetime_sec <= chain_parameters.block_interval\" );\n   const auto good_feed_lifetime = op.new_options.feed_lifetime_sec;\n   op.new_options.feed_lifetime_sec = db.get_global_properties().parameters.block_interval;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   BOOST_TEST_MESSAGE( \"Message should contain: op.new_options.feed_lifetime_sec <= chain_parameters.block_interval\" );\n   op.new_options.feed_lifetime_sec = db.get_global_properties().parameters.block_interval - 1; // default interval > 1\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.new_options.feed_lifetime_sec = good_feed_lifetime;\n\n   // Force settlement delay must exceed block interval.\n   BOOST_TEST_MESSAGE( \"Message should contain: op.new_options.force_settlement_delay_sec <= chain_parameters.block_interval\" );\n   const auto good_force_settlement_delay_sec = op.new_options.force_settlement_delay_sec;\n   op.new_options.force_settlement_delay_sec = db.get_global_properties().parameters.block_interval;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   BOOST_TEST_MESSAGE( \"Message should contain: op.new_options.force_settlement_delay_sec <= chain_parameters.block_interval\" );\n   op.new_options.force_settlement_delay_sec = db.get_global_properties().parameters.block_interval - 1; // default interval > 1\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   op.new_options.force_settlement_delay_sec = good_force_settlement_delay_sec;\n\n   // this should pass\n   BOOST_TEST_MESSAGE( \"We should be all good again.\" );\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n}\n\n/******\n * @brief Test various bitasset asserts within the asset_evaluator after the HF 922 / 931\n */\nBOOST_AUTO_TEST_CASE( bitasset_evaluator_test_after_922_931 )\n{\n   BOOST_TEST_MESSAGE(\"Advance to after hard fork 922 / 931\");\n   auto global_params = db.get_global_properties().parameters;\n   generate_blocks( HARDFORK_CORE_922_931_TIME + global_params.maintenance_interval );\n   trx.set_expiration( HARDFORK_CORE_922_931_TIME + global_params.maintenance_interval + global_params.maximum_time_until_expiration );\n\n   ACTORS( (nathan) (john) );\n\n   assets_922_931 asset_objs = create_assets_922_931( this );\n   const asset_id_type& bit_usd_id = asset_objs.bit_usd;\n\n   // make a generic operation\n   bitasset_evaluator_wrapper evaluator;\n   evaluator.set_db( db );\n   asset_update_bitasset_operation op;\n   op.asset_to_update = bit_usd_id;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n   op.new_options = asset_objs.bit_usd(db).bitasset_data(db).options;\n\n   // this should pass\n   BOOST_TEST_MESSAGE( \"Evaluating a good operation\" );\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n\n   // test with a market issued asset\n   BOOST_TEST_MESSAGE( \"Sending a non-bitasset.\" );\n   op.asset_to_update = asset_objs.user_issued;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Cannot update BitAsset-specific settings on a non-BitAsset\" );\n   op.asset_to_update = bit_usd_id;\n\n   // test changing issuer\n   BOOST_TEST_MESSAGE( \"Test changing issuer.\" );\n   account_id_type original_issuer = op.issuer;\n   op.issuer = john_id;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Only asset issuer can update\" );\n   op.issuer = original_issuer;\n\n   // bad backing_asset\n   BOOST_TEST_MESSAGE( \"Non-existent backing asset.\" );\n   asset_id_type correct_asset_id = op.new_options.short_backing_asset;\n   op.new_options.short_backing_asset = asset_id_type();\n   op.new_options.short_backing_asset.instance = 123;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Unable to find\" );\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // now check the things that are wrong and won't pass after HF 922 / 931\n   BOOST_TEST_MESSAGE( \"Now check the things that are wrong and won't pass after HF 922 / 931\" );\n\n   // back by self\n   BOOST_TEST_MESSAGE( \"Back by itself\" );\n   op.new_options.short_backing_asset = bit_usd_id;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Cannot update an asset to be backed by itself\" );\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // prediction market with different precision\n   BOOST_TEST_MESSAGE( \"Prediction market with different precision\" );\n   op.asset_to_update = asset_objs.prediction;\n   op.issuer = asset_objs.prediction(db).issuer;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"The precision of the asset and backing asset must\" );\n   op.asset_to_update = bit_usd_id;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n\n   // checking old backing asset instead of new backing asset\n   BOOST_TEST_MESSAGE( \"Correctly checking new backing asset rather than old backing asset\" );\n   op.new_options.short_backing_asset = asset_objs.six_precision;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"which is not market issued asset nor CORE.\" );\n   op.new_options.short_backing_asset = asset_objs.prediction;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"which is not backed by CORE\" );\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // CHILD is a non-committee asset backed by PARENT which is backed by CORE\n   // Cannot change PARENT's backing asset from CORE to something that is not [CORE | UIA]\n   // because that will make CHILD be backed by an asset that is not itself backed by CORE or a UIA.\n   BOOST_TEST_MESSAGE( \"Attempting to change PARENT to be backed by a non-core and non-user-issued asset\" );\n   op.asset_to_update = asset_objs.bit_parent;\n   op.issuer = asset_objs.bit_parent(db).issuer;\n   op.new_options.short_backing_asset = asset_objs.bit_usdbacked;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"A non-blockchain controlled BitAsset would be invalidated\" );\n   // changing the backing asset to a UIA should work\n   BOOST_TEST_MESSAGE( \"Switching to a backing asset that is a UIA should work.\" );\n   op.new_options.short_backing_asset = asset_objs.user_issued;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   // A -> B -> C, change B to be backed by A (circular backing)\n   BOOST_TEST_MESSAGE( \"Check for circular backing. This should generate an exception\" );\n   op.new_options.short_backing_asset = asset_objs.bit_child_bitasset;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"'A' backed by 'B' backed by 'A'\" );\n   op.new_options.short_backing_asset = asset_objs.user_issued;\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n   BOOST_TEST_MESSAGE( \"Creating CHILDCOMMITTEE\" );\n   // CHILDCOMMITTEE is a committee asset backed by PARENT which is backed by CORE\n   // Cannot change PARENT's backing asset from CORE to something else because that will make CHILDCOMMITTEE\n   // be backed by an asset that is not itself backed by CORE\n   create_bitasset( \"CHILDCOMMITTEE\", GRAPHENE_COMMITTEE_ACCOUNT, 100, charge_market_fee, 2,\n         asset_objs.bit_parent );\n   // it should again not work\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"A blockchain-controlled market asset would be invalidated\" );\n   op.asset_to_update = asset_objs.bit_usd;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n   op.new_options.short_backing_asset = correct_asset_id;\n\n   // USDBACKED is backed by USDBIT (which is backed by CORE)\n   // USDBACKEDII is backed by USDBIT\n   // We should not be able to make USDBACKEDII be backed by USDBACKED\n   // because that would be a MPA backed by MPA backed by MPA.\n   BOOST_TEST_MESSAGE( \"MPA -> MPA -> MPA not allowed\" );\n   op.asset_to_update = asset_objs.bit_usdbacked2;\n   op.issuer = asset_objs.bit_usdbacked2(db).issuer;\n   op.new_options.short_backing_asset = asset_objs.bit_usdbacked;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op),\n         \"A BitAsset cannot be backed by a BitAsset that itself is backed by a BitAsset\" );\n   // set everything to a more normal state\n   op.asset_to_update = asset_objs.bit_usdbacked;\n   op.issuer = asset_objs.bit_usd(db).issuer;\n   op.new_options.short_backing_asset = asset_id_type();\n\n   // Feed lifetime must exceed block interval\n   BOOST_TEST_MESSAGE( \"Feed lifetime less than or equal to block interval\" );\n   const auto good_feed_lifetime = op.new_options.feed_lifetime_sec;\n   op.new_options.feed_lifetime_sec = db.get_global_properties().parameters.block_interval;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Feed lifetime must exceed block\" );\n   op.new_options.feed_lifetime_sec = db.get_global_properties().parameters.block_interval - 1; // default interval > 1\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Feed lifetime must exceed block\" );\n   op.new_options.feed_lifetime_sec = good_feed_lifetime;\n\n   // Force settlement delay must exceed block interval.\n   BOOST_TEST_MESSAGE( \"Force settlement delay less than or equal to block interval\" );\n   const auto good_force_settlement_delay_sec = op.new_options.force_settlement_delay_sec;\n   op.new_options.force_settlement_delay_sec = db.get_global_properties().parameters.block_interval;\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Force settlement delay must\" );\n   op.new_options.force_settlement_delay_sec = db.get_global_properties().parameters.block_interval - 1; // default interval > 1\n   REQUIRE_EXCEPTION_WITH_TEXT( evaluator.evaluate(op), \"Force settlement delay must\" );\n   op.new_options.force_settlement_delay_sec = good_force_settlement_delay_sec;\n\n   // this should pass\n   BOOST_TEST_MESSAGE( \"We should be all good again.\" );\n   BOOST_CHECK( evaluator.evaluate(op).is_type<void_result>() );\n\n}\n\n/*********\n * @brief Call price inconsistent when MCR changed\n */\nBOOST_AUTO_TEST_CASE( hf_1270_test )\n{ try {\n   uint32_t skip = database::skip_witness_signature\n                 | database::skip_transaction_signatures\n                 | database::skip_transaction_dupe_check\n                 | database::skip_block_size_check\n                 | database::skip_tapos_check\n                 | database::skip_merkle_check\n                 ;\n   generate_blocks( HARDFORK_615_TIME, true, skip ); // get around Graphene issue #615 feed expiration bug\n   generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip );\n   generate_block( skip );\n\n   for( int i = 0; i < 12; ++i )\n   {\n      idump( (i) );\n      int blocks = 0;\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n      if( i == 2) // go beyond hard fork 890 (which is the same as 935 BTW)\n      {\n         generate_blocks( HARDFORK_CORE_868_890_TIME - mi, true, skip );\n         generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip );\n      }\n      else if( i == 6 ) // go beyond hard fork 1270\n      {\n         generate_blocks( HARDFORK_CORE_1270_TIME - mi, true, skip );\n         generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip );\n      }\n      else if( i == 8 ) // go beyond hard fork BSIP77\n      {\n         generate_blocks( HARDFORK_BSIP_77_TIME, true, skip );\n      }\n      else if( i == 10 ) // go beyond hard fork 2481\n      {\n         generate_blocks( HARDFORK_CORE_2481_TIME - mi, true, skip );\n         generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip );\n      }\n      set_expiration( db, trx );\n\n      ACTORS( (seller)(borrower)(feedproducer)(feedproducer2)(feedproducer3) );\n\n      int64_t init_balance( 1000000 );\n\n      transfer( committee_account, borrower_id, asset(init_balance) );\n\n      const auto& bitusd = create_bitasset( \"USDBIT\", feedproducer_id );\n      asset_id_type usd_id = bitusd.get_id();\n\n      {\n         // set a short feed lifetime\n         const asset_object& asset_to_update = usd_id(db);\n         asset_update_bitasset_operation ba_op;\n         ba_op.asset_to_update = usd_id;\n         ba_op.issuer = asset_to_update.issuer;\n         ba_op.new_options = asset_to_update.bitasset_data(db).options;\n         ba_op.new_options.feed_lifetime_sec = 300;\n         trx.operations.push_back(ba_op);\n         PUSH_TX(db, trx, ~0);\n         trx.clear();\n      }\n\n      // set feed producers\n      flat_set<account_id_type> producers;\n      producers.insert( feedproducer_id );\n      producers.insert( feedproducer2_id );\n      producers.insert( feedproducer3_id );\n      update_feed_producers( usd_id(db), producers );\n\n      // prepare feed data\n      price_feed current_feed;\n      if( i % 2 == 0 ) // MCR test\n      {\n         current_feed.maintenance_collateral_ratio = 3500;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         current_feed.settlement_price = asset(100, usd_id) / asset(5);\n      }\n      else // MSSR test\n      {\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1250;\n         current_feed.settlement_price = asset(100, usd_id) / asset(10);\n         // mssp = 1000/125\n      }\n\n      // set 2 price feeds which should call some later\n      publish_feed( usd_id, feedproducer_id, current_feed );\n      publish_feed( usd_id, feedproducer2_id, current_feed );\n\n      // check median\n      BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n      if( i % 2 == 0 ) // MCR test, MCR should be 350%\n         BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500 );\n      else // MSSR test, MSSR should be 125%\n         BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250 );\n\n      // generate some blocks, let the feeds expire\n      blocks += generate_blocks( db.head_block_time() + 360, true, skip );\n      set_expiration( db, trx );\n\n      // check median, should be null\n      BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // publish a new feed with 175% MCR and 110% MSSR\n      current_feed.settlement_price = asset(100, usd_id) / asset(5);\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      publish_feed( usd_id, feedproducer3_id, current_feed );\n\n      // check median, MCR would be 175%, MSSR would be 110%\n      BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n      BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 1750 );\n      BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1100 );\n\n      // Place some collateralized orders\n      // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700\n      borrow( borrower_id, asset(100, usd_id), asset(15) );\n\n      transfer( borrower_id, seller_id, asset(100, usd_id) );\n\n      if( i % 2 == 1) // MSSR test\n      {\n         // publish a new feed to put the call order into margin call territory\n         current_feed.settlement_price = asset(100, usd_id) / asset(10);\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         publish_feed( usd_id, feedproducer3_id, current_feed );\n         // mssp = 100/11\n      }\n\n      // place a sell order, it won't be matched with the call order now.\n      // For MCR test, the sell order is at feed price (100/5),\n      //   when median MCR changed to 350%, the call order with 300% collateral will be in margin call territory,\n      //   then this limit order should be filled\n      // For MSSR test, the sell order is above 110% of feed price (100/10) but below 125% of feed price,\n      //   when median MSSR changed to 125%, the call order will be matched,\n      //   then this limit order should be filled\n      limit_order_id_type sell_id = ( i % 2 == 0 ) ?\n                     create_sell_order( seller_id, asset(20, usd_id), asset(1) )->get_id() : // for MCR test\n                     create_sell_order( seller_id, asset(8, usd_id), asset(1) )->get_id();  // for MSSR test\n\n      {\n         // change feed lifetime to longer, let all 3 feeds be valid\n         const asset_object& asset_to_update = usd_id(db);\n         asset_update_bitasset_operation ba_op;\n         ba_op.asset_to_update = usd_id;\n         ba_op.issuer = asset_to_update.issuer;\n         ba_op.new_options = asset_to_update.bitasset_data(db).options;\n         ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_1270_TIME.sec_since_epoch()\n                                             + mi * 3 + 86400 * 2\n                                             - db.head_block_time().sec_since_epoch();\n         trx.operations.push_back(ba_op);\n         PUSH_TX(db, trx, ~0);\n         trx.clear();\n      }\n\n      bool affected_by_hf_343 = false;\n\n      // check\n      if( i / 2 == 0 ) // before hard fork 890\n      {\n         // median feed won't change (issue 890)\n         BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n         BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 1750 );\n         BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1100 );\n         // limit order is still there\n         BOOST_CHECK( db.find( sell_id ) );\n\n         // go beyond hard fork 890\n         blocks += generate_blocks( HARDFORK_CORE_868_890_TIME - mi, true, skip );\n         bool was_before_hf_343 = ( db.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_343_TIME );\n\n         blocks += generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip );\n         bool now_after_hf_343 = ( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_343_TIME );\n\n         if( was_before_hf_343 && now_after_hf_343 ) // if hf 343 executed at same maintenance interval, actually after hf 890\n            affected_by_hf_343 = true;\n      }\n\n      // after hard fork 890, if it's before hard fork 935\n      if( db.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_935_TIME )\n      {\n         // median should have changed\n         BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n         if( i % 2 == 0 ) // MCR test, MCR should be 350%\n            BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500 );\n         else // MSSR test, MSSR should be 125%\n            BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250 );\n\n         if( affected_by_hf_343 ) // if updated bitasset before hf 890, and hf 343 executed after hf 890\n            // the limit order should have been filled\n            BOOST_CHECK( !db.find( sell_id ) );\n         else // if not affected by hf 343\n            // the limit order should be still there, because `check_call_order` was incorrectly skipped\n            BOOST_CHECK( db.find( sell_id ) );\n\n         // go beyond hard fork 935\n         blocks += generate_blocks(HARDFORK_CORE_935_TIME - mi, true, skip);\n         blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n      }\n\n      // after hard fork 935, the limit order is filled only for the MSSR test\n      if( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_935_TIME &&\n          db.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_1270_TIME)\n      {\n         // check median\n         BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n         if( i % 2 == 0) { // MCR test, median MCR should be 350% and order will not be filled except when i = 0\n            BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500);\n            if( affected_by_hf_343 )\n               BOOST_CHECK(!db.find(sell_id));\n            else\n               BOOST_CHECK(db.find(sell_id)); // MCR bug, order still there\n         }\n         else { // MSSR test, MSSR should be 125% and order is filled\n            BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250);\n            BOOST_CHECK(!db.find(sell_id)); // order filled\n         }\n\n         // go beyond hard fork 1270\n         blocks += generate_blocks(HARDFORK_CORE_1270_TIME - mi, true, skip);\n         blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n      }\n\n      // after hard fork 1270, the limit order should be filled for MCR test\n      if( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME)\n      {\n         // check median\n         BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n         if( i % 2 == 0 ) { // MCR test, order filled\n            BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500);\n            BOOST_CHECK(!db.find(sell_id));\n         }\n      }\n\n      // undo above tx's and reset\n      generate_block( skip );\n      ++blocks;\n      while( blocks > 0 )\n      {\n         db.pop_block();\n         --blocks;\n      }\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( bitasset_secondary_index )\n{\n   ACTORS( (nathan) );\n\n   graphene::chain::asset_id_type core_id;\n   BOOST_TEST_MESSAGE( \"Running test bitasset_secondary_index\" );\n   BOOST_TEST_MESSAGE( \"Core asset id: \" + fc::json::to_pretty_string( core_id ) );\n   BOOST_TEST_MESSAGE(\"Create coins\");\n   try\n   {\n      // make 5 coins (backed by core)\n      for(int i = 0; i < 5; i++)\n      {\n         create_bitasset_backed(*this, i, core_id, nathan_private_key);\n      }\n      // make the next 5 (10-14) be backed by COIN1\n      graphene::chain::asset_id_type coin1_id = get_asset(\"COIN1TEST\").get_id();\n      for(int i = 5; i < 10; i++)\n      {\n         create_bitasset_backed(*this, i, coin1_id, nathan_private_key);\n      }\n      // make the next 5 (15-19) be backed by COIN2\n      graphene::chain::asset_id_type coin2_id = get_asset(\"COIN2TEST\").get_id();\n      for(int i = 10; i < 15; i++)\n      {\n         create_bitasset_backed(*this, i, coin2_id, nathan_private_key);\n      }\n      // make the last 5 be backed by core\n      for(int i = 15; i < 20; i++)\n      {\n         create_bitasset_backed(*this, i, core_id, nathan_private_key);\n      }\n\n      BOOST_TEST_MESSAGE(\"Searching for all coins backed by CORE\");\n      const auto& idx = db.get_index_type<graphene::chain::asset_bitasset_data_index>().indices().get<graphene::chain::by_short_backing_asset>();\n      auto core_itr = idx.equal_range( core_id );\n      BOOST_TEST_MESSAGE(\"Searching for all coins backed by COIN1\");\n      auto coin1_itr = idx.equal_range( coin1_id );\n      BOOST_TEST_MESSAGE(\"Searching for all coins backed by COIN2\");\n      auto coin2_itr = idx.equal_range( coin2_id );\n\n      int core_count = 0, coin1_count = 0, coin2_count = 0;\n\n      BOOST_TEST_MESSAGE(\"Counting coins in each category\");\n\n      for( auto itr = core_itr.first ; itr != core_itr.second; ++itr)\n      {\n         BOOST_CHECK(itr->options.short_backing_asset == core_id);\n         BOOST_TEST_MESSAGE( fc::json::to_pretty_string(itr->asset_id) + \" is backed by CORE\" );\n         core_count++;\n      }\n      for( auto itr = coin1_itr.first ; itr != coin1_itr.second; ++itr )\n      {\n         BOOST_CHECK(itr->options.short_backing_asset == coin1_id);\n         BOOST_TEST_MESSAGE( fc::json::to_pretty_string( itr->asset_id) + \" is backed by COIN1TEST\" );\n         coin1_count++;\n      }\n      for( auto itr = coin2_itr.first; itr != coin2_itr.second; ++itr )\n      {\n         BOOST_CHECK(itr->options.short_backing_asset == coin2_id);\n         BOOST_TEST_MESSAGE( fc::json::to_pretty_string( itr->asset_id) + \" is backed by COIN2TEST\" );\n         coin2_count++;\n      }\n\n      BOOST_CHECK( core_count >= 10 );\n      BOOST_CHECK_EQUAL( coin1_count, 5 );\n      BOOST_CHECK_EQUAL( coin2_count, 5 );\n   }\n   catch (fc::exception& ex)\n   {\n      BOOST_FAIL(ex.to_string(fc::log_level(fc::log_level::all)));\n   }\n}\n\n\n/*****\n * @brief make sure feeds work correctly after changing from non-witness-fed to witness-fed before the 868 fork\n * NOTE: This test case is a different issue than what is currently being worked on, and fails. Hopefully it\n * will help when the fix for that issue is being coded.\n */\n/*\nBOOST_AUTO_TEST_CASE( reset_backing_asset_switching_to_witness_fed )\n{\n   ACTORS((nathan)(dan)(ben)(vikram));\n\n   BOOST_TEST_MESSAGE(\"Advance to near hard fork\");\n   auto maint_interval = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks( HARDFORK_CORE_868_890_TIME - maint_interval);\n   trx.set_expiration(HARDFORK_CORE_868_890_TIME - fc::seconds(1));\n\n\n   BOOST_TEST_MESSAGE(\"Create USDBIT\");\n   asset_id_type bit_usd_id = create_bitasset(\"USDBIT\").get_id();\n   asset_id_type core_id = bit_usd_id(db).bitasset_data(db).options.short_backing_asset;\n\n   {\n      BOOST_TEST_MESSAGE(\"Update the USDBIT asset options\");\n      change_asset_options(*this, nathan_id, nathan_private_key, bit_usd_id, false );\n   }\n\n   BOOST_TEST_MESSAGE(\"Create JMJBIT based on USDBIT.\");\n   asset_id_type bit_jmj_id = create_bitasset(\"JMJBIT\").get_id();\n   {\n      BOOST_TEST_MESSAGE(\"Update the JMJBIT asset options\");\n      change_asset_options(*this, nathan_id, nathan_private_key, bit_jmj_id, false );\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Update the JMJBIT bitasset options\");\n      asset_update_bitasset_operation ba_op;\n      const asset_object& obj = bit_jmj_id(db);\n      ba_op.asset_to_update = obj.get_id();\n      ba_op.issuer = obj.issuer;\n      ba_op.new_options.short_backing_asset = bit_usd_id;\n      ba_op.new_options.minimum_feeds = 1;\n      trx.operations.push_back(ba_op);\n      sign(trx, nathan_private_key);\n      PUSH_TX(db, trx, ~0);\n      generate_block();\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Set feed producers for JMJBIT\");\n      asset_update_feed_producers_operation op;\n      op.asset_to_update = bit_jmj_id;\n      op.issuer = nathan_id;\n      op.new_feed_producers = {dan_id, ben_id, vikram_id};\n      trx.operations.push_back(op);\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx, ~0 );\n      generate_block();\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Verify feed producers are registered for JMJBIT\");\n      const asset_bitasset_data_object& obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(obj.feeds.size(), 3ul);\n      BOOST_CHECK(obj.current_feed == price_feed());\n\n\n      BOOST_CHECK_EQUAL(\"1\", std::to_string(obj.options.short_backing_asset.space_id));\n      BOOST_CHECK_EQUAL(\"3\", std::to_string(obj.options.short_backing_asset.type_id));\n      BOOST_CHECK_EQUAL(\"1\", std::to_string(obj.options.short_backing_asset.instance.value));\n\n      BOOST_CHECK_EQUAL(\"1\", std::to_string(bit_jmj_id.space_id));\n      BOOST_CHECK_EQUAL(\"3\", std::to_string(bit_jmj_id.type_id));\n      BOOST_CHECK_EQUAL(\"2\", std::to_string(bit_jmj_id.instance.value));\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Adding Vikram's price feed\");\n      add_price_feed(*this, vikram_id, bit_usd_id, 1, bit_jmj_id, 300, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 300.0);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Change JMJBIT to be witness_fed\");\n      optional<account_id_type> noone;\n      change_asset_options(*this, noone, nathan_private_key, bit_jmj_id, true );\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Change underlying asset of bit_jmj from bit_usd to core\");\n      change_backing_asset(*this, nathan_private_key, bit_jmj_id, core_id);\n\n      BOOST_TEST_MESSAGE(\"Verify feed producers have not been reset\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 3ul);\n      int nan_count = 0;\n      for(const auto& feed : jmj_obj.feeds) {\n         if(feed.second.second.settlement_price.is_null())\n            ++nan_count;\n      }\n      BOOST_CHECK_EQUAL(nan_count, 2);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Add a new (and correct) feed price from a witness\");\n      auto& global_props = db.get_global_properties();\n      std::vector<account_id_type> active_witnesses;\n      const witness_id_type& first_witness_id = (*global_props.active_witnesses.begin());\n      const account_id_type witness_account_id = first_witness_id(db).witness_account;\n      add_price_feed(*this, witness_account_id, core_id, 1, bit_jmj_id, 300, core_id);\n\n      // we should have 2 feeds nan, 1 old feed with wrong asset, and 1 witness feed\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 4ul);\n      int nan_count = 0;\n      for(const auto& feed : jmj_obj.feeds) {\n         if ( feed.second.second.settlement_price.is_null() )\n            ++nan_count;\n      }\n      BOOST_CHECK_EQUAL(nan_count, 2);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Advance to past hard fork\");\n      generate_blocks( HARDFORK_CORE_868_890_TIME + maint_interval);\n      trx.set_expiration(HARDFORK_CORE_868_890_TIME + fc::hours(48));\n\n      BOOST_TEST_MESSAGE(\"Verify that the incorrect feeds have been removed\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 1ul);\n      BOOST_CHECK( ! (*jmj_obj.feeds.begin()).second.second.settlement_price.is_null() );\n      // the settlement price will be NaN until 50% of price feeds are valid\n      //BOOST_CHECK_EQUAL(jmj_obj.current_feed.settlement_price.to_real(), 300);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"After hardfork, change underlying asset of bit_jmj from core to bit_usd\");\n      change_backing_asset(*this, nathan_private_key, bit_jmj_id, bit_usd_id);\n\n      BOOST_TEST_MESSAGE(\"Verify feed producers have been reset\");\n      const asset_bitasset_data_object& jmj_obj = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(jmj_obj.feeds.size(), 0ul);\n   }\n   {\n      BOOST_TEST_MESSAGE(\"With underlying bitasset changed from one to another, price feeds should still be publish-able\");\n      auto& global_props = db.get_global_properties();\n      std::vector<account_id_type> active_witnesses;\n      for(const auto& witness_id : global_props.active_witnesses)\n      {\n         active_witnesses.push_back(witness_id(db).witness_account);\n      }\n      BOOST_TEST_MESSAGE(\"Adding Witness 0's price feed\");\n      add_price_feed(*this, active_witnesses[0], bit_usd_id, 1, bit_jmj_id, 30, core_id);\n\n      const asset_bitasset_data_object& bitasset = bit_jmj_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      BOOST_TEST_MESSAGE(\"Adding Witness 1's pricing to JMJBIT\");\n      add_price_feed(*this, active_witnesses[0], bit_usd_id, 1, bit_jmj_id, 25, core_id);\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      BOOST_TEST_MESSAGE(\"Adding Witness 2's pricing to JMJBIT\");\n      add_price_feed(*this, active_witnesses[2], bit_usd_id, 1, bit_jmj_id, 10, core_id);\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 25);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n      generate_block();\n      trx.clear();\n\n      BOOST_CHECK(bitasset.current_feed.core_exchange_rate.base.asset_id != bitasset.current_feed.core_exchange_rate.quote.asset_id);\n   }\n}\n*/\nBOOST_AUTO_TEST_CASE(hf_890_test_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(hf_890_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hf_890_test_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(hf_890_test);\n\n} FC_LOG_AND_RETHROW() }\n\n\n   /**\n    * Test the claiming of collateral asset fees before HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME.\n    *\n    * Test prohibitions against changing of the backing/collateral asset for a smart asset\n    * if any collateral asset fees are available to be claimed.\n    */\n   BOOST_AUTO_TEST_CASE(change_backing_asset_prohibitions) {\n      try {\n         /**\n          * Initialize\n          */\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((smartissuer)(feedproducer)); // Actors for smart asset\n         ACTORS((jill)(izzy)); // Actors for user-issued assets\n         ACTORS((alice)); // Actors who hold balances\n\n         price price(asset(1, asset_id_type(1)), asset(1));\n         uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2, market_fee_percent);\n         generate_block(); trx.clear(); set_expiration(db, trx);\n         const asset_object jillcoin = get_asset(\"JCOIN\");\n         const int64_t jillcoin_unit = 100; // 100 satoshi JILLCOIN in 1 JILLCOIN\n\n         create_user_issued_asset(\"ICOIN\", izzy_id(db), charge_market_fee, price, 2, market_fee_percent);\n         generate_block();\n         const asset_object izzycoin = get_asset(\"ICOIN\");\n\n         // Create the smart asset backed by JCOIN\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent,\n                         charge_market_fee, 2, jillcoin.get_id());\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block(); trx.clear(); set_expiration(db, trx);\n         const asset_object &smartbit = get_asset(\"SMARTBIT\");\n         const asset_bitasset_data_object& smartbit_bitasset_data = (*smartbit.bitasset_data_id)(db);\n         // Confirm that the asset is to be backed by JCOIN\n         BOOST_CHECK(smartbit_bitasset_data.options.short_backing_asset == jillcoin.get_id());\n\n         // Fund balances of the actors\n         issue_uia(alice, jillcoin.amount(5000 * jillcoin_unit));\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 5000 * jillcoin_unit);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), 0);\n\n\n         /**\n          * Claim any amount of collateral asset fees.  This should fail because claiming such fees are prohibited\n          * before the HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME.\n          */\n         trx.clear();\n         asset_claim_fees_operation claim_op;\n         claim_op.issuer = smartissuer_id;\n         claim_op.extensions.value.claim_from_asset_id = smartbit.id;\n         claim_op.amount_to_claim = jillcoin.amount(5 * jillcoin_unit);\n         trx.operations.push_back(claim_op);\n         sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Collateral-denominated fees are not yet active\");\n\n\n         /**\n          * Propose to claim any amount of collateral asset fees.\n          * This should fail because claiming such fees are prohibited before HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME.\n          */\n         proposal_create_operation cop;\n         cop.review_period_seconds = 86400;\n         uint32_t buffer_seconds = 60 * 60;\n         cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.proposed_ops.emplace_back(claim_op);\n\n         trx.clear();\n         trx.operations.push_back(cop);\n         // sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Collateral-denominated fees are not yet active\");\n\n\n         /**\n          * Advance to when the collateral fee container is activated\n          */\n         generate_blocks(HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME);\n         generate_block(); trx.clear(); set_expiration(db, trx);\n\n\n         /**\n          * Cause some collateral of JCOIN to be accumulated as collateral fee within the SMARTBIT asset type\n          */\n         // HACK: Before BSIP74 or BSIP87 are introduced, it is not formally possible to accumulate collateral fees.\n         // Therefore, the accumulation for this test will be informally induced by direct manipulation of the database.\n         // More formal tests will be provided with the PR for either BSIP74 or BSIP87.\n         // IMPORTANT: The use of this hack requires that no additional blocks are subsequently generated!\n         asset accumulation_amount = jillcoin.amount(40 * jillcoin_unit); // JCOIN\n         db.adjust_balance(alice_id, -accumulation_amount); // Deduct 40 JCOIN from alice as a \"collateral fee\"\n         smartbit.accumulate_fee(db, accumulation_amount); // Add 40 JCOIN from alice as a \"collateral fee\"\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), (5000 * jillcoin_unit) - (40 * jillcoin_unit));\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees == accumulation_amount.amount);\n\n\n         /**\n          * Attempt to change the backing asset.  This should fail because there are unclaimed collateral fees.\n          */\n         trx.clear();\n         asset_update_bitasset_operation change_backing_asset_op;\n         change_backing_asset_op.asset_to_update = smartbit.id;\n         change_backing_asset_op.issuer = smartissuer_id;\n         change_backing_asset_op.new_options.short_backing_asset = izzycoin.id;\n         trx.operations.push_back(change_backing_asset_op);\n         sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Must claim collateral-denominated fees\");\n\n\n         /**\n          * Attempt to claim a negative amount of the collateral asset fees.\n          * This should fail because positive amounts are required.\n          */\n         trx.clear();\n         claim_op.amount_to_claim = jillcoin.amount(-9 * jillcoin_unit);\n         trx.operations.push_back(claim_op);\n         sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"amount_to_claim.amount > 0\");\n\n\n         /**\n          * Attempt to claim 0 amount of the collateral asset fees.\n          * This should fail because positive amounts are required.\n          */\n         trx.clear();\n         claim_op.amount_to_claim = jillcoin.amount(0 * jillcoin_unit);\n         trx.operations.push_back(claim_op);\n         sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"amount_to_claim.amount > 0\");\n\n\n         /**\n          * Attempt to claim excessive amount of collateral asset fees.\n          * This should fail because there are insufficient collateral fees.\n          */\n         trx.clear();\n         claim_op.amount_to_claim = accumulation_amount + jillcoin.amount(1);\n         trx.operations.push_back(claim_op);\n         sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Attempt to claim more backing-asset fees\");\n\n\n         /**\n          * Claim some of the collateral asset fees\n          */\n         share_type part_of_accumulated_fees = accumulation_amount.amount / 4;\n         FC_ASSERT(part_of_accumulated_fees.value > 0); // Partial claim should be positive\n         share_type remainder_accumulated_fees = accumulation_amount.amount - part_of_accumulated_fees;\n         FC_ASSERT(remainder_accumulated_fees.value > 0); // Planned remainder should be positive\n         trx.clear();\n         claim_op.amount_to_claim = jillcoin.amount(part_of_accumulated_fees);\n         trx.operations.push_back(claim_op);\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx);\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees == remainder_accumulated_fees);\n\n\n         /**\n          * Claim all the remaining collateral asset fees\n          */\n         trx.clear();\n         claim_op.amount_to_claim = jillcoin.amount(remainder_accumulated_fees);\n         trx.operations.push_back(claim_op);\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx);\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); // 0 remainder\n\n\n         /**\n          * Attempt to change the backing asset.\n          * This should succeed because there are no collateral asset fees are waiting to be claimed.\n          */\n         trx.clear();\n         trx.operations.push_back(change_backing_asset_op);\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx);\n\n         // Confirm the change to the backing asset\n         BOOST_CHECK(smartbit_bitasset_data.options.short_backing_asset == izzycoin.id);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/block_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/witness_schedule_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/crypto/digest.hpp>\n#include <fc/io/fstream.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\ngenesis_state_type make_genesis() {\n   genesis_state_type genesis_state;\n\n   genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP );\n\n   auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")));\n   genesis_state.initial_active_witnesses = 10;\n   genesis_state.immutable_parameters.min_committee_member_count = INITIAL_COMMITTEE_MEMBER_COUNT;\n   genesis_state.immutable_parameters.min_witness_count = INITIAL_WITNESS_COUNT;\n\n   for( unsigned int i = 0; i < genesis_state.initial_active_witnesses; ++i )\n   {\n      auto name = \"init\"+fc::to_string(i);\n      genesis_state.initial_accounts.emplace_back(name,\n                                                  init_account_priv_key.get_public_key(),\n                                                  init_account_priv_key.get_public_key(),\n                                                  true);\n      genesis_state.initial_committee_candidates.push_back({name});\n      genesis_state.initial_witness_candidates.push_back({name, init_account_priv_key.get_public_key()});\n   }\n   genesis_state.initial_parameters.get_mutable_fees().zero_all_fees();\n   return genesis_state;\n}\n\nBOOST_AUTO_TEST_SUITE(block_tests)\n\nBOOST_AUTO_TEST_CASE( block_database_test )\n{\n   try {\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n\n      block_database bdb;\n      bdb.open( data_dir.path() );\n      FC_ASSERT( bdb.is_open() );\n      bdb.close();\n      FC_ASSERT( !bdb.is_open() );\n      bdb.open( data_dir.path() );\n\n      clearable_block b;\n      for( uint32_t i = 0; i < 5; ++i )\n      {\n         if( i > 0 ) b.previous = b.id();\n         b.witness = witness_id_type(i+1);\n         b.clear();\n         bdb.store( b.id(), b );\n\n         auto fetch = bdb.fetch_by_number( b.block_num() );\n         FC_ASSERT( fetch.valid() );\n         FC_ASSERT( fetch->witness ==  b.witness );\n         fetch = bdb.fetch_by_number( i+1 );\n         FC_ASSERT( fetch.valid() );\n         FC_ASSERT( fetch->witness ==  b.witness );\n         fetch = bdb.fetch_optional( b.id() );\n         FC_ASSERT( fetch.valid() );\n         FC_ASSERT( fetch->witness ==  b.witness );\n      }\n\n      for( uint32_t i = 1; i < 5; ++i )\n      {\n         auto blk = bdb.fetch_by_number( i );\n         FC_ASSERT( blk.valid() );\n         FC_ASSERT( blk->witness == witness_id_type(blk->block_num()) );\n      }\n\n      auto last = bdb.last();\n      FC_ASSERT( last );\n      FC_ASSERT( last->id() == b.id() );\n\n      bdb.close();\n      bdb.open( data_dir.path() );\n      last = bdb.last();\n      FC_ASSERT( last );\n      FC_ASSERT( last->id() == b.id() );\n\n      for( uint32_t i = 0; i < 5; ++i )\n      {\n         auto blk = bdb.fetch_by_number( i+1 );\n         FC_ASSERT( blk.valid() );\n         FC_ASSERT( blk->witness == witness_id_type(blk->block_num()) );\n      }\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( generate_empty_blocks )\n{\n   try {\n      fc::time_point_sec now( GRAPHENE_TESTING_GENESIS_TIMESTAMP );\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n      signed_block b;\n\n      // TODO:  Don't generate this here\n      auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      signed_block cutoff_block;\n      uint32_t last_block;\n      {\n         database db;\n         db.open(data_dir.path(), make_genesis, \"TEST\" );\n         b = db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n\n         // TODO:  Change this test when we correct #406\n         // n.b. we generate GRAPHENE_MIN_UNDO_HISTORY+1 extra blocks which will be discarded on save\n         for( uint32_t i = 1; ; ++i )\n         {\n            BOOST_CHECK( db.head_block_id() == b.id() );\n            //witness_id_type prev_witness = b.witness;\n            witness_id_type cur_witness = db.get_scheduled_witness(1);\n            //BOOST_CHECK( cur_witness != prev_witness );\n            b = db.generate_block(db.get_slot_time(1), cur_witness, init_account_priv_key, database::skip_nothing);\n            BOOST_CHECK( b.witness == cur_witness );\n            uint32_t cutoff_height = db.get_dynamic_global_properties().last_irreversible_block_num;\n            if( cutoff_height >= 200 )\n            {\n               cutoff_block = *(db.fetch_block_by_number( cutoff_height ));\n               last_block = db.head_block_num();\n               break;\n            }\n         }\n         db.close();\n      }\n      {\n         database db;\n         db.open(data_dir.path(), []{return genesis_state_type();}, \"TEST\");\n         BOOST_CHECK_EQUAL( db.head_block_num(), last_block );\n         while( db.head_block_num() > cutoff_block.block_num() )\n            db.pop_block();\n         b = cutoff_block;\n         for( uint32_t i = 0; i < 200; ++i )\n         {\n            BOOST_CHECK( db.head_block_id() == b.id() );\n            //witness_id_type prev_witness = b.witness;\n            witness_id_type cur_witness = db.get_scheduled_witness(1);\n            //BOOST_CHECK( cur_witness != prev_witness );\n            b = db.generate_block(db.get_slot_time(1), cur_witness, init_account_priv_key, database::skip_nothing);\n         }\n         BOOST_CHECK_EQUAL( db.head_block_num(), cutoff_block.block_num()+200 );\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( undo_block )\n{\n   try {\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n      {\n         database db;\n         db.open(data_dir.path(), make_genesis, \"TEST\");\n         fc::time_point_sec now( GRAPHENE_TESTING_GENESIS_TIMESTAMP );\n         std::vector< time_point_sec > time_stack;\n\n         auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n         for( uint32_t i = 0; i < 5; ++i )\n         {\n            now = db.get_slot_time(1);\n            time_stack.push_back( now );\n            auto b = db.generate_block( now, db.get_scheduled_witness( 1 ), init_account_priv_key, database::skip_nothing );\n         }\n         BOOST_CHECK( db.head_block_num() == 5 );\n         BOOST_CHECK( db.head_block_time() == now );\n         db.pop_block();\n         time_stack.pop_back();\n         now = time_stack.back();\n         BOOST_CHECK( db.head_block_num() == 4 );\n         BOOST_CHECK( db.head_block_time() == now );\n         db.pop_block();\n         time_stack.pop_back();\n         now = time_stack.back();\n         BOOST_CHECK( db.head_block_num() == 3 );\n         BOOST_CHECK( db.head_block_time() == now );\n         db.pop_block();\n         time_stack.pop_back();\n         now = time_stack.back();\n         BOOST_CHECK( db.head_block_num() == 2 );\n         BOOST_CHECK( db.head_block_time() == now );\n         for( uint32_t i = 0; i < 5; ++i )\n         {\n            now = db.get_slot_time(1);\n            time_stack.push_back( now );\n            auto b = db.generate_block( now, db.get_scheduled_witness( 1 ), init_account_priv_key, database::skip_nothing );\n         }\n         BOOST_CHECK( db.head_block_num() == 7 );\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( change_signing_key_test )\n{\n   try {\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n\n      auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      auto init_pub_key = init_account_priv_key.get_public_key();\n      auto new_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"new_key\")) );\n      auto new_pub_key = new_key.get_public_key();\n\n      std::map< public_key_type, fc::ecc::private_key > key_map;\n      key_map[init_pub_key] = init_account_priv_key;\n      key_map[new_pub_key] = new_key;\n\n      std::set< witness_id_type > witnesses;\n      for( uint32_t i = 0; i <= 11; ++i ) // 11 init witnesses and 0 is reserved\n         witnesses.insert( witness_id_type(i) );\n\n      auto change_signing_key = [&init_account_priv_key]( database& db, witness_id_type wit, public_key_type new_signing_key ) {\n         witness_update_operation wuop;\n         wuop.witness_account = wit(db).witness_account;\n         wuop.witness = wit;\n         wuop.new_signing_key = new_signing_key;\n         signed_transaction wu_trx;\n         wu_trx.operations.push_back( wuop );\n         wu_trx.set_reference_block( db.head_block_id() );\n         wu_trx.set_expiration( db.head_block_time()\n                               + fc::seconds( 0x1000 * db.get_global_properties().parameters.block_interval ) );\n         wu_trx.sign( init_account_priv_key, db.get_chain_id() );\n         PUSH_TX( db, wu_trx, 0 );\n      };\n\n      {\n         database db;\n\n         // open database\n         db.open(data_dir.path(), make_genesis, \"TEST\");\n\n         // generate some empty blocks with init keys\n         for( uint32_t i = 0; i < 30; ++i )\n         {\n            auto now = db.get_slot_time(1);\n            auto next_witness = db.get_scheduled_witness( 1 );\n            db.generate_block( now, next_witness, init_account_priv_key, database::skip_nothing );\n         }\n\n         // generate some blocks and change keys in same block\n         for( uint32_t i = 0; i < 9; ++i )\n         {\n            auto now = db.get_slot_time(1);\n            auto next_witness = db.get_scheduled_witness( 1 );\n            public_key_type current_key = next_witness(db).signing_key;\n            change_signing_key( db, next_witness, new_key.get_public_key() );\n            idump( (i)(now)(next_witness) );\n            auto b = db.generate_block( now, next_witness, key_map[current_key], database::skip_nothing );\n            idump( (b) );\n         }\n\n         // pop a few blocks and clear pending, some signing keys should be changed back\n         for( uint32_t i = 0; i < 4; ++i )\n         {\n            db.pop_block();\n         }\n         db._popped_tx.clear();\n         db.clear_pending();\n\n         // generate a few blocks and change keys in same block\n         for( uint32_t i = 0; i < 2; ++i )\n         {\n            auto now = db.get_slot_time(1);\n            auto next_witness = db.get_scheduled_witness( 1 );\n            public_key_type current_key = next_witness(db).signing_key;\n            change_signing_key( db, next_witness, new_key.get_public_key() );\n            idump( (i)(now)(next_witness) );\n            auto b = db.generate_block( now, next_witness, key_map[current_key], database::skip_nothing );\n            idump( (b) );\n         }\n\n         // generate some blocks but don't change a key\n         for( uint32_t i = 0; i < 25; ++i )\n         {\n            auto now = db.get_slot_time(1);\n            auto next_witness = db.get_scheduled_witness( 1 );\n            public_key_type current_key = next_witness(db).signing_key;\n            idump( (i)(now)(next_witness) );\n            auto b = db.generate_block( now, next_witness, key_map[current_key], database::skip_nothing );\n            idump( (b) );\n         }\n\n         // close the database, flush all data to disk\n         db.close();\n      }\n      {\n         database db;\n\n         // reopen database, all data should be unchanged\n         db.open(data_dir.path(), make_genesis, \"TEST\");\n\n         // generate more blocks and change keys in same block\n         for( uint32_t i = 0; i < 25; ++i )\n         {\n            auto now = db.get_slot_time(1);\n            auto next_witness = db.get_scheduled_witness( 1 );\n            public_key_type current_key = next_witness(db).signing_key;\n            change_signing_key( db, next_witness, new_key.get_public_key() );\n            idump( (i)(now)(next_witness) );\n            auto b = db.generate_block( now, next_witness, key_map[current_key], database::skip_nothing );\n            idump( (b) );\n         }\n\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( fork_blocks )\n{\n   try {\n      fc::temp_directory data_dir1( graphene::utilities::temp_directory_path() );\n      fc::temp_directory data_dir2( graphene::utilities::temp_directory_path() );\n\n      database db1;\n      db1.open(data_dir1.path(), make_genesis, \"TEST\");\n      database db2;\n      db2.open(data_dir2.path(), make_genesis, \"TEST\");\n      BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() );\n\n      auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n\n      {\n         const auto first_slot = db1.get_slot_at_time( fc::time_point::now() );\n         auto b = db1.generate_block( db1.get_slot_time(first_slot), db1.get_scheduled_witness(first_slot),\n                                      init_account_priv_key, database::skip_nothing);\n         PUSH_BLOCK( db2, b );\n      }\n\n      BOOST_TEST_MESSAGE( \"Adding blocks 2 through 11\" );\n      for( uint32_t i = 1; i <= 10; ++i )\n      {\n         auto b = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n         try {\n            PUSH_BLOCK( db2, b );\n         } FC_CAPTURE_AND_RETHROW( (\"db2\") );\n      }\n\n      for( uint32_t j = 0; j <= 4; j += 4 )\n      {\n         // add blocks 12 through 14 to db1 only\n         BOOST_TEST_MESSAGE( \"Adding 3 blocks to db1 only\" );\n         for( uint32_t i = 12 + j; i <= 14 + j; ++i )\n         {\n            BOOST_TEST_MESSAGE( i );\n            auto b = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n         }\n         string db1_tip = db1.head_block_id().str();\n\n         // add different blocks 12 through 14 to db2 only\n         BOOST_TEST_MESSAGE( \"Add 3 different blocks to db2 only\" );\n         uint32_t next_slot = 3;\n         for( uint32_t i = 12 + j; i <= 14 + j; ++i )\n         {\n            BOOST_TEST_MESSAGE( i );\n            auto b =  db2.generate_block(db2.get_slot_time(next_slot), db2.get_scheduled_witness(next_slot), init_account_priv_key, database::skip_nothing);\n            next_slot = 1;\n            // notify both databases of the new block.\n            // only db2 should switch to the new fork, db1 should not\n            PUSH_BLOCK( db1, b );\n            BOOST_CHECK_EQUAL(db1.head_block_id().str(), db1_tip);\n            BOOST_CHECK_EQUAL(db2.head_block_id().str(), b.id().str());\n         }\n\n         //The two databases are on distinct forks now, but at the same height.\n         BOOST_CHECK_EQUAL(db1.head_block_num(), 14u + j);\n         BOOST_CHECK_EQUAL(db2.head_block_num(), 14u + j);\n         BOOST_CHECK( db1.head_block_id() != db2.head_block_id() );\n\n         //Make a block on db2, make it invalid, then\n         //pass it to db1 and assert that db1 doesn't switch to the new fork.\n         signed_block good_block;\n         {\n            auto b = db2.generate_block(db2.get_slot_time(1), db2.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n            good_block = b;\n            b.transactions.emplace_back(signed_transaction());\n            b.transactions.back().operations.emplace_back(transfer_operation());\n            b.sign( init_account_priv_key );\n            BOOST_CHECK_EQUAL(b.block_num(), 15u + j);\n            GRAPHENE_CHECK_THROW(PUSH_BLOCK( db1, b ), fc::exception);\n\n            // At this point, `fetch_block_by_number` will fetch block from fork_db,\n            //    so unable to reproduce the issue which is fixed in PR #938\n            //    https://github.com/bitshares/bitshares-core/pull/938\n            fc::optional<signed_block> previous_block = db1.fetch_block_by_number(1);\n            BOOST_CHECK ( previous_block.valid() );\n            uint32_t db1_blocks = db1.head_block_num();\n            for( uint32_t curr_block_num = 2; curr_block_num <= db1_blocks; ++curr_block_num )\n            {\n               fc::optional<signed_block> curr_block = db1.fetch_block_by_number( curr_block_num );\n               BOOST_CHECK( curr_block.valid() );\n               BOOST_CHECK_EQUAL( curr_block->previous.str(), previous_block->id().str() );\n               previous_block = curr_block;\n            }\n         }\n         BOOST_CHECK_EQUAL(db1.head_block_num(), 14u + j);\n         BOOST_CHECK_EQUAL(db1.head_block_id().str(), db1_tip);\n\n         if( j == 0 )\n         {\n            // assert that db1 switches to new fork with good block\n            BOOST_CHECK_EQUAL(db2.head_block_num(), 15u + j);\n            PUSH_BLOCK( db1, good_block );\n            BOOST_CHECK_EQUAL(db1.head_block_id().str(), db2.head_block_id().str());\n         }\n      }\n\n      // generate more blocks to push the forked blocks out of fork_db\n      BOOST_TEST_MESSAGE( \"Adding more blocks to db1, push the forked blocks out of fork_db\" );\n      for( uint32_t i = 1; i <= 50; ++i )\n      {\n         db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      }\n\n      {\n         // PR #938 make sure db is in a good state https://github.com/bitshares/bitshares-core/pull/938\n         BOOST_TEST_MESSAGE( \"Checking whether all blocks on disk are good\" );\n         fc::optional<signed_block> previous_block = db1.fetch_block_by_number(1);\n         BOOST_CHECK ( previous_block.valid() );\n         uint32_t db1_blocks = db1.head_block_num();\n         for( uint32_t curr_block_num = 2; curr_block_num <= db1_blocks; ++curr_block_num )\n         {\n            fc::optional<signed_block> curr_block = db1.fetch_block_by_number( curr_block_num );\n            BOOST_CHECK( curr_block.valid() );\n            BOOST_CHECK_EQUAL( curr_block->previous.str(), previous_block->id().str() );\n            previous_block = curr_block;\n         }\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n\n/**\n *  These test has been disabled, out of order blocks should result in the node getting disconnected.\n *  \nBOOST_AUTO_TEST_CASE( fork_db_tests )\n{\n   try {\n     fork_database fdb;\n     signed_block prev;\n     signed_block skipped_block;\n     for( uint32_t i = 0; i < 2000; ++i )\n     {\n        signed_block b;\n        b.previous = prev.id();\n        if( b.block_num() == 1800 )\n           skipped_block = b;\n        else\n           fdb.push_block( b );\n        prev = b;\n     }\n     auto head = fdb.head();\n     FC_ASSERT( head && head->data.block_num() == 1799 );\n\n     fdb.push_block(skipped_block);\n     head = fdb.head();\n     FC_ASSERT( head && head->data.block_num() == 2001, \"\", (\"head\",head->data.block_num()) );\n  } FC_LOG_AND_RETHROW() \n}\nBOOST_AUTO_TEST_CASE( out_of_order_blocks )\n{\n   try {\n      fc::temp_directory data_dir1( graphene::utilities::temp_directory_path() );\n      fc::temp_directory data_dir2( graphene::utilities::temp_directory_path() );\n\n      database db1;\n      db1.open(data_dir1.path(), make_genesis);\n      database db2;\n      db2.open(data_dir2.path(), make_genesis);\n      BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() );\n\n      auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      auto b1 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b2 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b3 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b4 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b5 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b6 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b7 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b8 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b9 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b10 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b11 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      auto b12 = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      BOOST_CHECK_EQUAL(db1.head_block_num(), 12);\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 0);\n      PUSH_BLOCK( db2, b1 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 1);\n      PUSH_BLOCK( db2, b3 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 1);\n      PUSH_BLOCK( db2, b2 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 3);\n      PUSH_BLOCK( db2, b5 );\n      PUSH_BLOCK( db2, b6 );\n      PUSH_BLOCK( db2, b7 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 3);\n      PUSH_BLOCK( db2, b4 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 7);\n      PUSH_BLOCK( db2, b8 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 8);\n      PUSH_BLOCK( db2, b11 );\n      PUSH_BLOCK( db2, b10 );\n      PUSH_BLOCK( db2, b12 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 8);\n      PUSH_BLOCK( db2, b9 );\n      BOOST_CHECK_EQUAL(db2.head_block_num(), 12);\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n */\n\nBOOST_AUTO_TEST_CASE( undo_pending )\n{\n   try {\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n      {\n         database db;\n         db.open(data_dir.path(), make_genesis, \"TEST\");\n\n         auto init_account_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n         public_key_type init_account_pub_key  = init_account_priv_key.get_public_key();\n         const graphene::db::index& account_idx = db.get_index(protocol_ids, account_object_type);\n\n         transfer_operation t;\n         t.to = account_id_type(1);\n         t.amount = asset( 10000000 );\n         {\n            signed_transaction trx;\n            set_expiration( db, trx );\n\n            trx.operations.push_back(t);\n            PUSH_TX( db, trx, ~0 );\n\n            auto b = db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, ~0);\n         }\n\n         signed_transaction trx;\n         set_expiration( db, trx );\n         account_id_type nathan_id { account_idx.get_next_id() };\n         account_create_operation cop;\n         cop.registrar = GRAPHENE_TEMP_ACCOUNT;\n         cop.name = \"nathan\";\n         cop.owner = authority(1, init_account_pub_key, 1);\n         cop.active = cop.owner;\n         trx.operations.push_back(cop);\n         //sign( trx,  init_account_priv_key  );\n         PUSH_TX( db, trx );\n\n         auto b = db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n\n         BOOST_CHECK(nathan_id(db).name == \"nathan\");\n\n         trx.clear();\n         set_expiration( db, trx );\n         t.fee = asset(1);\n         t.from = account_id_type(1);\n         t.to = nathan_id;\n         t.amount = asset(5000);\n         trx.operations.push_back(t);\n         PUSH_TX(db, trx, ~0);\n         trx.clear();\n         set_expiration( db, trx );\n         trx.operations.push_back(t);\n         PUSH_TX(db, trx, ~0);\n\n         BOOST_CHECK(db.get_balance(nathan_id, asset_id_type()).amount == 10000);\n         db.clear_pending();\n         BOOST_CHECK(db.get_balance(nathan_id, asset_id_type()).amount == 0);\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( switch_forks_undo_create )\n{\n   try {\n      fc::temp_directory dir1( graphene::utilities::temp_directory_path() ),\n                         dir2( graphene::utilities::temp_directory_path() );\n      database db1,\n               db2;\n      db1.open(dir1.path(), make_genesis, \"TEST\");\n      db2.open(dir2.path(), make_genesis, \"TEST\");\n      BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() );\n\n      auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      public_key_type init_account_pub_key  = init_account_priv_key.get_public_key();\n      const graphene::db::index& account_idx = db1.get_index(protocol_ids, account_object_type);\n\n      signed_transaction trx;\n      set_expiration( db1, trx );\n      account_id_type nathan_id { account_idx.get_next_id() };\n      account_create_operation cop;\n      cop.registrar = GRAPHENE_TEMP_ACCOUNT;\n      cop.name = \"nathan\";\n      cop.owner = authority(1, init_account_pub_key, 1);\n      cop.active = cop.owner;\n      trx.operations.push_back(cop);\n      PUSH_TX( db1, trx );\n\n      // generate blocks\n      // db1 : A\n      // db2 : B C D\n\n      auto aw = db1.get_global_properties().active_witnesses;\n      auto b = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n\n      BOOST_CHECK(nathan_id(db1).name == \"nathan\");\n\n      b = db2.generate_block(db2.get_slot_time(1), db2.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      db1.push_block(b);\n      aw = db2.get_global_properties().active_witnesses;\n      b = db2.generate_block(db2.get_slot_time(1), db2.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      db1.push_block(b);\n      GRAPHENE_REQUIRE_THROW(nathan_id(db2), fc::exception);\n      nathan_id(db1); /// it should be included in the pending state\n      db1.clear_pending(); // clear it so that we can verify it was properly removed from pending state.\n      GRAPHENE_REQUIRE_THROW(nathan_id(db1), fc::exception);\n\n      PUSH_TX( db2, trx );\n\n      aw = db2.get_global_properties().active_witnesses;\n      b = db2.generate_block(db2.get_slot_time(1), db2.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      db1.push_block(b);\n\n      BOOST_CHECK(nathan_id(db1).name == \"nathan\");\n      BOOST_CHECK(nathan_id(db2).name == \"nathan\");\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( duplicate_transactions )\n{\n   try {\n      fc::temp_directory dir1( graphene::utilities::temp_directory_path() ),\n                         dir2( graphene::utilities::temp_directory_path() );\n      database db1,\n               db2;\n      db1.open(dir1.path(), make_genesis, \"TEST\");\n      db2.open(dir2.path(), make_genesis, \"TEST\");\n      BOOST_CHECK( db1.get_chain_id() == db2.get_chain_id() );\n\n      auto skip_sigs = database::skip_transaction_signatures;\n\n      auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      public_key_type init_account_pub_key  = init_account_priv_key.get_public_key();\n      const graphene::db::index& account_idx = db1.get_index(protocol_ids, account_object_type);\n\n      signed_transaction trx;\n      set_expiration( db1, trx );\n      account_id_type nathan_id { account_idx.get_next_id() };\n      account_create_operation cop;\n      cop.name = \"nathan\";\n      cop.owner = authority(1, init_account_pub_key, 1);\n      cop.active = cop.owner;\n      trx.operations.push_back(cop);\n      trx.sign( init_account_priv_key, db1.get_chain_id() );\n      PUSH_TX( db1, trx, skip_sigs );\n\n      trx = decltype(trx)();\n      set_expiration( db1, trx );\n      transfer_operation t;\n      t.to = nathan_id;\n      t.amount = asset(500);\n      trx.operations.push_back(t);\n      trx.sign( init_account_priv_key, db1.get_chain_id() );\n      PUSH_TX( db1, trx, skip_sigs );\n\n      GRAPHENE_CHECK_THROW(PUSH_TX( db1, trx, skip_sigs ), fc::exception);\n\n      auto b = db1.generate_block( db1.get_slot_time(1), db1.get_scheduled_witness( 1 ), init_account_priv_key, skip_sigs );\n      PUSH_BLOCK( db2, b, skip_sigs );\n\n      GRAPHENE_CHECK_THROW(PUSH_TX( db1, trx, skip_sigs ), fc::exception);\n      GRAPHENE_CHECK_THROW(PUSH_TX( db2, trx, skip_sigs ), fc::exception);\n      BOOST_CHECK_EQUAL(db1.get_balance(nathan_id, asset_id_type()).amount.value, 500);\n      BOOST_CHECK_EQUAL(db2.get_balance(nathan_id, asset_id_type()).amount.value, 500);\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( tapos )\n{\n   try {\n      fc::temp_directory dir1( graphene::utilities::temp_directory_path() );\n      database db1;\n      db1.open(dir1.path(), make_genesis, \"TEST\");\n\n      const account_object& init1 = *db1.get_index_type<account_index>().indices().get<by_name>().find(\"init1\");\n\n      auto init_account_priv_key  = fc::ecc::private_key::regenerate(fc::sha256::hash(string(\"null_key\")) );\n      public_key_type init_account_pub_key  = init_account_priv_key.get_public_key();\n      const graphene::db::index& account_idx = db1.get_index(protocol_ids, account_object_type);\n\n      auto b = db1.generate_block( db1.get_slot_time(1), db1.get_scheduled_witness( 1 ), init_account_priv_key, database::skip_nothing);\n\n      signed_transaction trx;\n      //This transaction must be in the next block after its reference, or it is invalid.\n      trx.set_expiration( db1.head_block_time() ); //db1.get_slot_time(1) );\n      trx.set_reference_block( db1.head_block_id() );\n\n      account_id_type nathan_id { account_idx.get_next_id() };\n      account_create_operation cop;\n      cop.registrar = init1.id;\n      cop.name = \"nathan\";\n      cop.owner = authority(1, init_account_pub_key, 1);\n      cop.active = cop.owner;\n      trx.operations.push_back(cop);\n      trx.sign( init_account_priv_key, db1.get_chain_id() );\n      PUSH_TX(db1, trx);\n      b = db1.generate_block(db1.get_slot_time(1), db1.get_scheduled_witness(1), init_account_priv_key, database::skip_nothing);\n      trx.clear();\n\n      transfer_operation t;\n      t.to = nathan_id;\n      t.amount = asset(50);\n      trx.operations.push_back(t);\n      trx.sign( init_account_priv_key, db1.get_chain_id() );\n      //relative_expiration is 1, but ref block is 2 blocks old, so this should fail.\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db1, trx, database::skip_transaction_signatures ), fc::exception);\n      set_expiration( db1, trx );\n      trx.clear_signatures();\n      trx.sign( init_account_priv_key, db1.get_chain_id() );\n      PUSH_TX( db1, trx, database::skip_transaction_signatures );\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( optional_tapos, database_fixture )\n{\n   try\n   {\n      ACTORS( (alice)(bob) );\n\n      generate_block();\n\n      BOOST_TEST_MESSAGE( \"Create transaction\" );\n\n      transfer( account_id_type(), alice_id, asset( 1000000 ) );\n      transfer_operation op;\n      op.from = alice_id;\n      op.to = bob_id;\n      op.amount = asset( 1000 );\n      signed_transaction tx;\n      tx.operations.push_back( op );\n      set_expiration( db, tx );\n\n      BOOST_TEST_MESSAGE( \"ref_block_num=0, ref_block_prefix=0\" );\n\n      tx.ref_block_num = 0;\n      tx.ref_block_prefix = 0;\n      tx.clear_signatures();\n      sign( tx, alice_private_key );\n      PUSH_TX( db, tx );\n\n      BOOST_TEST_MESSAGE( \"proper ref_block_num, ref_block_prefix\" );\n\n      set_expiration( db, tx );\n      tx.clear_signatures();\n      sign( tx, alice_private_key );\n      PUSH_TX( db, tx );\n\n      BOOST_TEST_MESSAGE( \"ref_block_num=0, ref_block_prefix=12345678\" );\n\n      tx.ref_block_num = 0;\n      tx.ref_block_prefix = 0x12345678;\n      tx.clear_signatures();\n      sign( tx, alice_private_key );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"ref_block_num=1, ref_block_prefix=12345678\" );\n\n      tx.ref_block_num = 1;\n      tx.ref_block_prefix = 0x12345678;\n      tx.clear_signatures();\n      sign( tx, alice_private_key );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"ref_block_num=9999, ref_block_prefix=12345678\" );\n\n      tx.ref_block_num = 9999;\n      tx.ref_block_prefix = 0x12345678;\n      tx.clear_signatures();\n      sign( tx, alice_private_key );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx ), fc::exception );\n   }\n   catch (fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( maintenance_interval, database_fixture )\n{\n   try {\n      generate_block();\n      BOOST_CHECK_EQUAL(db.head_block_num(), 2u);\n\n      fc::time_point_sec maintenence_time = db.get_dynamic_global_properties().next_maintenance_time;\n      BOOST_CHECK_GT(maintenence_time.sec_since_epoch(), db.head_block_time().sec_since_epoch());\n      auto initial_properties = db.get_global_properties();\n      const account_object& nathan = create_account(\"nathan\");\n      upgrade_to_lifetime_member(nathan);\n      const committee_member_object nathans_committee_member = create_committee_member(nathan);\n      {\n         account_update_operation op;\n         op.account = nathan.id;\n         op.new_options = nathan.options;\n         op.new_options->votes.insert(nathans_committee_member.vote_id);\n         trx.operations.push_back(op);\n         PUSH_TX( db, trx, ~0 );\n         trx.operations.clear();\n      }\n      transfer(account_id_type()(db), nathan, asset(5000));\n\n      generate_blocks(maintenence_time - initial_properties.parameters.block_interval);\n      BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maximum_transaction_size,\n                        initial_properties.parameters.maximum_transaction_size);\n      BOOST_CHECK_EQUAL(db.get_dynamic_global_properties().next_maintenance_time.sec_since_epoch(),\n                        db.head_block_time().sec_since_epoch() + db.get_global_properties().parameters.block_interval);\n      BOOST_CHECK(db.get_global_properties().active_witnesses == initial_properties.active_witnesses);\n      BOOST_CHECK(db.get_global_properties().active_committee_members == initial_properties.active_committee_members);\n\n      generate_block();\n\n      auto new_properties = db.get_global_properties();\n      BOOST_CHECK(new_properties.active_committee_members != initial_properties.active_committee_members);\n      BOOST_CHECK(std::find(new_properties.active_committee_members.begin(),\n                            new_properties.active_committee_members.end(), nathans_committee_member.id) !=\n                  new_properties.active_committee_members.end());\n      BOOST_CHECK_EQUAL(db.get_dynamic_global_properties().next_maintenance_time.sec_since_epoch(),\n                        maintenence_time.sec_since_epoch() + new_properties.parameters.maintenance_interval);\n      maintenence_time = db.get_dynamic_global_properties().next_maintenance_time;\n      BOOST_CHECK_GT(maintenence_time.sec_since_epoch(), db.head_block_time().sec_since_epoch());\n      db.close();\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n\nBOOST_FIXTURE_TEST_CASE( limit_order_expiration, database_fixture )\n{ try {\n   //Get a sane head block time\n   generate_block();\n\n   auto* test = &create_bitasset(\"MIATEST\");\n   auto* core = &asset_id_type()(db);\n   auto* nathan = &create_account(\"nathan\");\n   auto* committee = &account_id_type()(db);\n\n   transfer(*committee, *nathan, core->amount(50000));\n\n   BOOST_CHECK_EQUAL( get_balance(*nathan, *core), 50000 );\n\n   limit_order_create_operation op;\n   op.seller = nathan->id;\n   op.amount_to_sell = core->amount(500);\n   op.min_to_receive = test->amount(500);\n   op.expiration = db.head_block_time() + fc::seconds(10);\n   trx.operations.push_back(op);\n   auto ptrx = PUSH_TX( db, trx, ~0 );\n\n   BOOST_CHECK_EQUAL( get_balance(*nathan, *core), 49500 );\n\n   auto ptrx_id = ptrx.operation_results.back().get<object_id_type>();\n   auto limit_index = db.get_index_type<limit_order_index>().indices();\n   auto limit_itr = limit_index.begin();\n   BOOST_REQUIRE( limit_itr != limit_index.end() );\n   BOOST_REQUIRE( limit_itr->id == ptrx_id );\n   BOOST_REQUIRE( db.find_object(limit_itr->id) );\n   BOOST_CHECK_EQUAL( get_balance(*nathan, *core), 49500 );\n   auto id = limit_itr->id;\n\n   generate_blocks(op.expiration, false);\n   test = &get_asset(\"MIATEST\");\n   core = &asset_id_type()(db);\n   nathan = &get_account(\"nathan\");\n   committee = &account_id_type()(db);\n\n   BOOST_CHECK(db.find_object(id) == nullptr);\n   BOOST_CHECK_EQUAL( get_balance(*nathan, *core), 50000 );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( double_sign_check, database_fixture )\n{ try {\n   generate_block();\n   const auto& alice = account_id_type()(db);\n   ACTOR(bob);\n   asset amount(1000);\n\n   set_expiration( db, trx );\n   transfer_operation t;\n   t.from = alice.id;\n   t.to = bob.id;\n   t.amount = amount;\n   trx.operations.push_back(t);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n\n   PUSH_TX(db, trx, ~0);\n\n   trx.operations.clear();\n   t.from = bob.id;\n   t.to = alice.id;\n   t.amount = amount;\n   trx.operations.push_back(t);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n\n   BOOST_TEST_MESSAGE( \"Verify that not-signing causes an exception\" );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, 0), fc::exception );\n\n   BOOST_TEST_MESSAGE( \"Verify that double-signing causes an exception\" );\n   sign( trx, bob_private_key );\n   sign( trx, bob_private_key );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, 0), tx_duplicate_sig );\n\n   BOOST_TEST_MESSAGE( \"Verify that signing with an extra, unused key fails\" );\n   trx.signatures.pop_back();\n   sign( trx, generate_private_key(\"bogus\" ));\n   GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, 0), tx_irrelevant_sig );\n\n   BOOST_TEST_MESSAGE( \"Verify that signing once with the proper key passes\" );\n   trx.signatures.pop_back();\n   PUSH_TX(db, trx, 0);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( change_block_interval, database_fixture )\n{ try {\n   generate_block();\n\n   db.modify(db.get_global_properties(), [](global_property_object& p) {\n      p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();\n   });\n\n   BOOST_TEST_MESSAGE( \"Creating a proposal to change the block_interval to 1 second\" );\n   {\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties().parameters, db.head_block_time());\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation uop;\n      uop.new_parameters.block_interval = 1;\n      cop.proposed_ops.emplace_back(uop);\n      trx.operations.push_back(cop);\n      PUSH_TX(db, trx);\n   }\n   BOOST_TEST_MESSAGE( \"Updating proposal by signing with the committee_member private key\" );\n   {\n      proposal_update_operation uop;\n      uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      uop.active_approvals_to_add = {get_account(\"init0\").get_id(), get_account(\"init1\").get_id(),\n                                     get_account(\"init2\").get_id(), get_account(\"init3\").get_id(),\n                                     get_account(\"init4\").get_id(), get_account(\"init5\").get_id(),\n                                     get_account(\"init6\").get_id(), get_account(\"init7\").get_id()};\n      trx.operations.push_back(uop);\n      sign( trx, init_account_priv_key );\n      /*\n      sign( trx, get_account(\"init1\" ).active.get_keys().front(),init_account_priv_key);\n      sign( trx, get_account(\"init2\" ).active.get_keys().front(),init_account_priv_key);\n      sign( trx, get_account(\"init3\" ).active.get_keys().front(),init_account_priv_key);\n      sign( trx, get_account(\"init4\" ).active.get_keys().front(),init_account_priv_key);\n      sign( trx, get_account(\"init5\" ).active.get_keys().front(),init_account_priv_key);\n      sign( trx, get_account(\"init6\" ).active.get_keys().front(),init_account_priv_key);\n      sign( trx, get_account(\"init7\" ).active.get_keys().front(),init_account_priv_key);\n      */\n      PUSH_TX(db, trx);\n      BOOST_CHECK(proposal_id_type()(db).is_authorized_to_execute(db));\n   }\n   BOOST_TEST_MESSAGE( \"Verifying that the interval didn't change immediately\" );\n\n   BOOST_CHECK_EQUAL(db.get_global_properties().parameters.block_interval, 5u);\n   auto past_time = db.head_block_time().sec_since_epoch();\n   generate_block();\n   BOOST_CHECK_EQUAL(db.head_block_time().sec_since_epoch() - past_time, 5u);\n   generate_block();\n   BOOST_CHECK_EQUAL(db.head_block_time().sec_since_epoch() - past_time, 10u);\n\n   BOOST_TEST_MESSAGE( \"Generating blocks until proposal expires\" );\n   generate_blocks(proposal_id_type()(db).expiration_time + 5);\n   BOOST_TEST_MESSAGE( \"Verify that the block interval is still 5 seconds\" );\n   BOOST_CHECK_EQUAL(db.get_global_properties().parameters.block_interval, 5u);\n\n   BOOST_TEST_MESSAGE( \"Generating blocks until next maintenance interval\" );\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();   // get the maintenance skip slots out of the way\n\n   BOOST_TEST_MESSAGE( \"Verify that the new block interval is 1 second\" );\n   BOOST_CHECK_EQUAL(db.get_global_properties().parameters.block_interval, 1);\n   past_time = db.head_block_time().sec_since_epoch();\n   generate_block();\n   BOOST_CHECK_EQUAL(db.head_block_time().sec_since_epoch() - past_time, 1u);\n   generate_block();\n   BOOST_CHECK_EQUAL(db.head_block_time().sec_since_epoch() - past_time, 2u);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( pop_block_twice, database_fixture )\n{\n   try\n   {\n      uint32_t skip_flags = (\n           database::skip_witness_signature\n         | database::skip_transaction_signatures\n         );\n\n      const asset_object& core = asset_id_type()(db);\n\n      // Sam is the creator of accounts\n      private_key_type committee_key = init_account_priv_key;\n      private_key_type sam_key = generate_private_key(\"sam\");\n      account_object sam_account_object = create_account(\"sam\", sam_key);\n\n      //Get a sane head block time\n      generate_block( skip_flags );\n\n      db.modify(db.get_global_properties(), [](global_property_object& p) {\n         p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();\n      });\n\n      transaction tx;\n      processed_transaction ptx;\n\n      account_object committee_account_object = committee_account(db);\n      // transfer from committee account to Sam account\n      transfer(committee_account_object, sam_account_object, core.amount(100000));\n\n      generate_block(skip_flags);\n\n      create_account(\"alice\");\n      generate_block(skip_flags);\n      create_account(\"bob\");\n      generate_block(skip_flags);\n\n      db.pop_block();\n      db.pop_block();\n   } catch(const fc::exception& e) {\n      edump( (e.to_detail_string()) );\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( rsf_missed_blocks, database_fixture )\n{\n   try\n   {\n      generate_block();\n\n      auto rsf = [this]() -> string\n      {\n\t fc::uint128_t rsf = db.get_dynamic_global_properties().recent_slots_filled;\n         string result = \"\";\n         result.reserve(128);\n         for( int i=0; i<128; i++ )\n         {\n            result += rsf & 1 ? '1' : '0';\n            rsf >>= 1;\n         }\n         return result;\n      };\n\n      auto pct = []( uint32_t x ) -> uint32_t\n      {\n         return uint64_t( GRAPHENE_100_PERCENT ) * x / 128;\n      };\n\n      BOOST_CHECK_EQUAL( rsf(),\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), (uint32_t)GRAPHENE_100_PERCENT );\n\n      generate_block( ~0, init_account_priv_key, 1 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0111111111111111111111111111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(127) );\n\n      generate_block( ~0, init_account_priv_key, 1 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0101111111111111111111111111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(126) );\n\n      generate_block( ~0, init_account_priv_key, 2 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0010101111111111111111111111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(124) );\n\n      generate_block( ~0, init_account_priv_key, 3 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0001001010111111111111111111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(121) );\n\n      generate_block( ~0, init_account_priv_key, 5 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0000010001001010111111111111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(116) );\n\n      generate_block( ~0, init_account_priv_key, 8 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0000000010000010001001010111111111111111111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(108) );\n\n      generate_block( ~0, init_account_priv_key, 13 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0000000000000100000000100000100010010101111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(95) );\n\n      generate_block();\n      BOOST_CHECK_EQUAL( rsf(),\n         \"1000000000000010000000010000010001001010111111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(95) );\n\n      generate_block();\n      BOOST_CHECK_EQUAL( rsf(),\n         \"1100000000000001000000001000001000100101011111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(95) );\n\n      generate_block();\n      BOOST_CHECK_EQUAL( rsf(),\n         \"1110000000000000100000000100000100010010101111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(95) );\n\n      generate_block();\n      BOOST_CHECK_EQUAL( rsf(),\n         \"1111000000000000010000000010000010001001010111111111111111111111\"\n         \"1111111111111111111111111111111111111111111111111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(95) );\n\n      generate_block( ~0, init_account_priv_key, 64 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0000000000000000000000000000000000000000000000000000000000000000\"\n         \"1111100000000000001000000001000001000100101011111111111111111111\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(31) );\n\n      generate_block( ~0, init_account_priv_key, 32 );\n      BOOST_CHECK_EQUAL( rsf(),\n         \"0000000000000000000000000000000010000000000000000000000000000000\"\n         \"0000000000000000000000000000000001111100000000000001000000001000\"\n      );\n      BOOST_CHECK_EQUAL( db.witness_participation_rate(), pct(8) );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_FIXTURE_TEST_CASE( transaction_invalidated_in_cache, database_fixture )\n{\n   try\n   {\n      ACTORS( (alice)(bob) );\n\n      auto generate_block = [&]( database& d, uint32_t skip ) -> signed_block\n      {\n         return d.generate_block(d.get_slot_time(1), d.get_scheduled_witness(1), init_account_priv_key, skip);\n      };\n\n      // tx's created by ACTORS() have bogus authority, so we need to\n      // skip_transaction_signatures in the block where they're included\n      signed_block b1 = generate_block(db, database::skip_transaction_signatures);\n\n      fc::temp_directory data_dir2( graphene::utilities::temp_directory_path() );\n\n      database db2;\n      {\n         std::string genesis_json;\n         fc::read_file_contents( data_dir.path() / \"genesis.json\", genesis_json );\n         genesis_state_type genesis = fc::json::from_string( genesis_json ).as<genesis_state_type>( 50 );\n         genesis.initial_chain_id = fc::sha256::hash( genesis_json );\n         db2.open(data_dir2.path(), [&genesis] () { return genesis; }, \"TEST\");\n      }\n      BOOST_CHECK( db.get_chain_id() == db2.get_chain_id() );\n\n      while( db2.head_block_num() < db.head_block_num() )\n      {\n         optional< signed_block > b = db.fetch_block_by_number( db2.head_block_num()+1 );\n         db2.push_block(*b, database::skip_witness_signature\n                           |database::skip_transaction_signatures );\n      }\n      BOOST_CHECK( db2.get( alice_id ).name == \"alice\" );\n      BOOST_CHECK( db2.get( bob_id ).name == \"bob\" );\n\n      db2.push_block(generate_block(db, database::skip_nothing));\n      transfer( account_id_type(), alice_id, asset( 1000 ) );\n      transfer( account_id_type(),   bob_id, asset( 1000 ) );\n      // need to skip authority check here as well for same reason as above\n      db2.push_block(generate_block(db, database::skip_transaction_signatures), database::skip_transaction_signatures);\n\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 1000);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 1000);\n      BOOST_CHECK_EQUAL(db2.get_balance(alice_id, asset_id_type()).amount.value, 1000);\n      BOOST_CHECK_EQUAL(db2.get_balance(  bob_id, asset_id_type()).amount.value, 1000);\n\n      auto generate_and_send = [&]( int n )\n      {\n         for( int i=0; i<n; i++ )\n         {\n            signed_block b = generate_block(db2, database::skip_nothing);\n            PUSH_BLOCK( db, b );\n         }\n      };\n\n      auto generate_xfer_tx = [&]( account_id_type from, account_id_type to, share_type amount, int blocks_to_expire ) -> signed_transaction\n      {\n         signed_transaction tx;\n         transfer_operation xfer_op;\n         xfer_op.from = from;\n         xfer_op.to = to;\n         xfer_op.amount = asset( amount, asset_id_type() );\n         xfer_op.fee = asset( 0, asset_id_type() );\n         tx.operations.push_back( xfer_op );\n         tx.set_expiration( db.head_block_time() + blocks_to_expire * db.get_global_properties().parameters.block_interval );\n         if( from == alice_id )\n            sign( tx, alice_private_key );\n         else\n            sign( tx, bob_private_key );\n         return tx;\n      };\n\n      signed_transaction tx = generate_xfer_tx( alice_id, bob_id, 1000, 2 );\n      tx.set_expiration( db.head_block_time() + 2 * db.get_global_properties().parameters.block_interval );\n      tx.clear_signatures();\n      sign( tx, alice_private_key );\n      // put the tx in db tx cache\n      PUSH_TX( db, tx );\n\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value,    0);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 2000);\n\n      // generate some blocks with db2, make tx expire in db's cache\n      generate_and_send(3);\n\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 1000);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 1000);\n\n      // generate a block with db and ensure we don't somehow apply it\n      PUSH_BLOCK(db2, generate_block(db, database::skip_nothing));\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 1000);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 1000);\n\n      // now the tricky part...\n      // (A) Bob sends 1000 to Alice\n      // (B) Alice sends 2000 to Bob\n      // (C) Alice sends 500 to Bob\n      //\n      // We push AB, then receive a block containing C.\n      // we need to apply the block, then invalidate B in the cache.\n      // AB results in Alice having 0, Bob having 2000.\n      // C results in Alice having 500, Bob having 1500.\n      //\n      // This needs to occur while switching to a fork.\n      //\n\n      signed_transaction tx_a = generate_xfer_tx( bob_id, alice_id, 1000, 2 );\n      signed_transaction tx_b = generate_xfer_tx( alice_id, bob_id, 2000, 10 );\n      signed_transaction tx_c = generate_xfer_tx( alice_id, bob_id,  500, 10 );\n\n      generate_block( db, database::skip_nothing );\n\n      PUSH_TX( db, tx_a );\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 2000);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value,    0);\n\n      PUSH_TX( db, tx_b );\n      PUSH_TX( db2, tx_c );\n\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 0);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 2000);\n\n      BOOST_CHECK_EQUAL(db2.get_balance(alice_id, asset_id_type()).amount.value, 500);\n      BOOST_CHECK_EQUAL(db2.get_balance(  bob_id, asset_id_type()).amount.value, 1500);\n\n      // generate enough blocks on db2 to cause db to switch forks\n      generate_and_send(2);\n\n      // db should invalidate B, but still be applying A, so the states don't agree\n\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 1500);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 500);\n\n      BOOST_CHECK_EQUAL(db2.get_balance(alice_id, asset_id_type()).amount.value, 500);\n      BOOST_CHECK_EQUAL(db2.get_balance(  bob_id, asset_id_type()).amount.value, 1500);\n\n      // This will cause A to expire in db\n      generate_and_send(1);\n\n      BOOST_CHECK_EQUAL(db.get_balance(alice_id, asset_id_type()).amount.value, 500);\n      BOOST_CHECK_EQUAL(db.get_balance(  bob_id, asset_id_type()).amount.value, 1500);\n\n      BOOST_CHECK_EQUAL(db2.get_balance(alice_id, asset_id_type()).amount.value, 500);\n      BOOST_CHECK_EQUAL(db2.get_balance(  bob_id, asset_id_type()).amount.value, 1500);\n\n      // Make sure we can generate and accept a plain old empty block on top of all this!\n      generate_and_send(1);\n   }\n   catch (fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( genesis_reserve_ids )\n{\n   try\n   {\n      fc::time_point_sec now( GRAPHENE_TESTING_GENESIS_TIMESTAMP );\n      fc::temp_directory data_dir( graphene::utilities::temp_directory_path() );\n\n      uint32_t num_special_accounts = 100;\n      uint32_t num_special_assets = 30;\n\n      database db;\n      db.open( data_dir.path(), [&]() -> genesis_state_type\n      {\n         genesis_state_type genesis_state = make_genesis();\n         genesis_state_type::initial_asset_type usd;\n\n         usd.symbol = \"USD\";\n         usd.issuer_name = \"init0\";\n         usd.description = \"federally floated\";\n         usd.precision = 4;\n         usd.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n         usd.accumulated_fees = 0;\n         usd.is_bitasset = true;\n         \n         genesis_state.immutable_parameters.num_special_accounts = num_special_accounts;\n         genesis_state.immutable_parameters.num_special_assets = num_special_assets;\n         genesis_state.initial_assets.push_back( usd );\n\n         return genesis_state;\n      }, \"TEST\" );\n\n      const auto& acct_idx = db.get_index_type<account_index>().indices().get<by_name>();\n      auto acct_itr = acct_idx.find(\"init0\");\n      BOOST_REQUIRE( acct_itr != acct_idx.end() );\n      BOOST_CHECK( acct_itr->id == account_id_type( num_special_accounts ) );\n      \n      const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_symbol>();\n      auto asset_itr = asset_idx.find(\"USD\");\n      BOOST_REQUIRE( asset_itr != asset_idx.end() );\n      BOOST_CHECK( asset_itr->id == asset_id_type( num_special_assets ) );\n   }\n   catch (fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( miss_some_blocks, database_fixture )\n{ try {\n   // Witnesses scheduled incorrectly in genesis block - reschedule\n   generate_blocks( witness_schedule_id_type()(db).current_shuffled_witnesses.size() );\n   generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n   std::vector<witness_id_type> witnesses = witness_schedule_id_type()(db).current_shuffled_witnesses;\n   BOOST_CHECK_EQUAL( INITIAL_WITNESS_COUNT, witnesses.size() );\n   // database_fixture constructor calls generate_block once, signed by witnesses[0]\n   generate_block(); // witnesses[1]\n   generate_block(); // witnesses[2]\n   for( const auto& id : witnesses )\n      BOOST_CHECK_EQUAL( 0, id(db).total_missed );\n   // generate_blocks generates another block *now* (witnesses[3])\n   // and one at now+9 blocks (witnesses[12%9])\n   generate_blocks( db.head_block_time() + db.get_global_properties().parameters.block_interval * 9, true );\n   // i. e. 7 blocks are missed in between by witness[4..11%9]\n   for( uint32_t i = 0; i < witnesses.size(); i++ )\n      BOOST_CHECK_EQUAL( (i+6) % 9 < 2 ? 0 : 1, witnesses[i](db).total_missed );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_FIXTURE_TEST_CASE( miss_many_blocks, database_fixture )\n{\n   try\n   {\n      // Witnesses scheduled incorrectly in genesis block - reschedule\n      generate_blocks( witness_schedule_id_type()(db).current_shuffled_witnesses.size() );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      auto get_misses = []( database& db ) {\n         std::map< witness_id_type, uint32_t > misses;\n         for( const auto& witness_id : witness_schedule_id_type()(db).current_shuffled_witnesses )\n            misses[witness_id] = witness_id(db).total_missed;\n         return misses;\n      };\n      generate_block();\n      generate_block();\n      generate_block();\n      auto missed_before = get_misses( db );\n      // miss 9 maintenance intervals\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time + \n                       db.get_global_properties().parameters.maintenance_interval * 9, true );\n      generate_block();\n      generate_block();\n      generate_block();\n      auto missed_after = get_misses( db );\n      BOOST_CHECK_EQUAL( missed_before.size(), missed_after.size() );\n      for( const auto& miss : missed_before )\n      {\n          const auto& after = missed_after.find( miss.first );\n          BOOST_REQUIRE( after != missed_after.end() );\n          BOOST_CHECK_EQUAL( miss.second, after->second );\n      }\n   }\n   catch (fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture )\n{\n   try\n   {\n      const asset_object& core = asset_id_type()(db);\n      uint32_t skip_flags =\n          database::skip_transaction_dupe_check\n        | database::skip_witness_signature\n        | database::skip_transaction_signatures\n        ;\n\n      // Sam is the creator of accounts\n      private_key_type committee_key = init_account_priv_key;\n      private_key_type sam_key = generate_private_key(\"sam\");\n\n      //\n      // A = old key set\n      // B = new key set\n      //\n      // we measure how many times we test following four cases:\n      //\n      //                                     A-B        B-A\n      // alice     case_count[0]   A == B    empty      empty\n      // bob       case_count[1]   A  < B    empty      nonempty\n      // charlie   case_count[2]   B  < A    nonempty   empty\n      // dan       case_count[3]   A nc B    nonempty   nonempty\n      //\n      // and assert that all four cases were tested at least once\n      //\n      account_object sam_account_object = create_account( \"sam\", sam_key );\n\n      // upgrade sam to LTM\n      upgrade_to_lifetime_member(sam_account_object.get_id());\n\n      //Get a sane head block time\n      generate_block( skip_flags );\n\n      db.modify(db.get_global_properties(), [](global_property_object& p) {\n         p.parameters.committee_proposal_review_period = fc::hours(1).to_seconds();\n      });\n\n      transaction tx;\n      processed_transaction ptx;\n\n      account_object committee_account_object = committee_account(db);\n      // transfer from committee account to Sam account\n      transfer(committee_account_object, sam_account_object, core.amount(100000));\n\n      const int num_keys = 5;\n      vector< private_key_type > numbered_private_keys;\n      vector< vector< public_key_type > > numbered_key_id;\n      numbered_private_keys.reserve( num_keys );\n      numbered_key_id.push_back( vector<public_key_type>() );\n      numbered_key_id.push_back( vector<public_key_type>() );\n\n      for( int i=0; i<num_keys; i++ )\n      {\n         private_key_type privkey = generate_private_key(std::string(\"key_\") + std::to_string(i));\n         public_key_type pubkey = privkey.get_public_key();\n         address addr( pubkey );\n\n         numbered_private_keys.push_back( privkey );\n         numbered_key_id[0].push_back( pubkey );\n         //numbered_key_id[1].push_back( addr );\n      }\n\n      // each element of possible_key_sched is a list of exactly num_keys\n      // indices into numbered_key_id[use_address].  they are defined\n      // by repeating selected elements of\n      // numbered_private_keys given by a different selector.\n      vector< vector< int > > possible_key_sched;\n      const int num_key_sched = (1 << num_keys)-1;\n      possible_key_sched.reserve( num_key_sched );\n\n      for( int s=1; s<=num_key_sched; s++ )\n      {\n         vector< int > v;\n         int i = 0;\n         v.reserve( num_keys );\n         while( v.size() < num_keys )\n         {\n            if( s & (1 << i) )\n               v.push_back( i );\n            i++;\n            if( i >= num_keys )\n               i = 0;\n         }\n         possible_key_sched.push_back( v );\n      }\n\n      // we can only undo in blocks\n      generate_block( skip_flags );\n\n      std::cout << \"update_account_keys:  this test will take a few minutes...\\n\";\n\n      // Originally we had a loop here to go from use_address=0 to 1\n      // Live chain do not allow this so it had to be removed: https://github.com/bitshares/bitshares-core/issues/565\n      vector< public_key_type > key_ids = numbered_key_id[ 0 ];\n      for( int num_owner_keys=1; num_owner_keys<=2; num_owner_keys++ )\n      {\n         for( int num_active_keys=1; num_active_keys<=2; num_active_keys++ )\n         {\n            std::cout << 0 << num_owner_keys << num_active_keys << \"\\n\";\n            for( const vector< int >& key_sched_before : possible_key_sched )\n            {\n               auto it = key_sched_before.begin();\n               vector< const private_key_type* > owner_privkey;\n               vector< const public_key_type* > owner_keyid;\n               owner_privkey.reserve( num_owner_keys );\n\n               trx.clear();\n               account_create_operation create_op;\n               create_op.name = \"alice\";\n\n               for( int owner_index=0; owner_index<num_owner_keys; owner_index++ )\n               {\n                  int i = *(it++);\n                  create_op.owner.key_auths[ key_ids[ i ] ] = 1;\n                  owner_privkey.push_back( &numbered_private_keys[i] );\n                  owner_keyid.push_back( &key_ids[ i ] );\n               }\n               // size() < num_owner_keys is possible when some keys are duplicates\n               create_op.owner.weight_threshold = create_op.owner.key_auths.size();\n\n               for( int active_index=0; active_index<num_active_keys; active_index++ )\n                  create_op.active.key_auths[ key_ids[ *(it++) ] ] = 1;\n               // size() < num_active_keys is possible when some keys are duplicates\n               create_op.active.weight_threshold = create_op.active.key_auths.size();\n\n               create_op.options.memo_key = key_ids[ *(it++) ] ;\n               create_op.registrar = sam_account_object.id;\n               trx.operations.push_back( create_op );\n               // trx.sign( sam_key );\n\n               processed_transaction ptx_create = PUSH_TX( db, trx,\n                  database::skip_transaction_dupe_check |\n                  database::skip_transaction_signatures\n               );\n               account_id_type alice_account_id {\n                  ptx_create.operation_results[0]\n                  .get< object_id_type >() };\n\n               generate_block( skip_flags );\n               for( const vector< int >& key_sched_after : possible_key_sched )\n               {\n                  auto it = key_sched_after.begin();\n\n                  trx.clear();\n                  account_update_operation update_op;\n                  update_op.account = alice_account_id;\n                  update_op.owner = authority();\n                  update_op.active = authority();\n                  update_op.new_options = create_op.options;\n\n                  for( int owner_index=0; owner_index<num_owner_keys; owner_index++ )\n                     update_op.owner->key_auths[ key_ids[ *(it++) ] ] = 1;\n                  // size() < num_owner_keys is possible when some keys are duplicates\n                  update_op.owner->weight_threshold = update_op.owner->key_auths.size();\n                  for( int active_index=0; active_index<num_active_keys; active_index++ )\n                     update_op.active->key_auths[ key_ids[ *(it++) ] ] = 1;\n                  // size() < num_active_keys is possible when some keys are duplicates\n                  update_op.active->weight_threshold = update_op.active->key_auths.size();\n                  FC_ASSERT( update_op.new_options.valid() );\n                  update_op.new_options->memo_key = key_ids[ *(it++) ] ;\n\n                  trx.operations.push_back( update_op );\n                  for( int i=0; i<int(create_op.owner.weight_threshold); i++)\n                  {\n                     sign( trx, *owner_privkey[i] );\n                     if( i < int(create_op.owner.weight_threshold-1) )\n                     {\n                        GRAPHENE_REQUIRE_THROW(PUSH_TX(db, trx), fc::exception);\n                     }\n                     else\n                     {\n                        PUSH_TX( db, trx,\n                           database::skip_transaction_dupe_check |\n                           database::skip_transaction_signatures );\n                     }\n                  }\n                  generate_block( skip_flags );\n\n                  db.pop_block();\n               }\n               db.pop_block();\n            }\n         }\n      }\n   }\n   catch( const fc::exception& e )\n   {\n      edump( (e.to_detail_string()) );\n      throw;\n   }\n}\n\n// The next test is commented out as it will fail in current bitshares implementaton\n// where \"witnesses should never sign 2 consecutive blocks\" is not enforced.\n// https://github.com/bitshares/bitshares-core/issues/565\n// Leaving it here to use it if we implement.later\n\n/**\n *  To have a secure random number we need to ensure that the same\n *  witness does not get to produce two blocks in a row.  There is\n *  always a chance that the last witness of one round will be the\n *  first witness of the next round.\n *\n *  This means that when we shuffle witness we need to make sure\n *  that there is at least N/2 witness between consecutive turns\n *  of the same witness.    This means that durring the random\n *  shuffle we need to restrict the placement of witness to maintain\n *  this invariant.\n *\n *  This test checks the requirement using Monte Carlo approach\n *  (produce lots of blocks and check the invariant holds).\n */\n/*\nBOOST_FIXTURE_TEST_CASE( witness_order_mc_test, database_fixture )\n{\n   try {\n      size_t num_witnesses = db.get_global_properties().active_witnesses.size();\n      size_t dmin = num_witnesses >> 1;\n\n      vector< witness_id_type > cur_round;\n      vector< witness_id_type > full_schedule;\n      // if we make the maximum witness count testable,\n      // we'll need to enlarge this.\n      std::bitset< 0x40 > witness_seen;\n      size_t total_blocks = 1000000;\n\n      cur_round.reserve( num_witnesses );\n      full_schedule.reserve( total_blocks );\n      cur_round.push_back( db.get_dynamic_global_properties().current_witness );\n\n      // we assert so the test doesn't continue, which would\n      // corrupt memory\n      assert( num_witnesses <= witness_seen.size() );\n\n      while( full_schedule.size() < total_blocks )\n      {\n         if( (db.head_block_num() & 0x3FFF) == 0 )\n         {\n             wdump( (db.head_block_num()) );\n         }\n         witness_id_type wid = db.get_scheduled_witness( 1 );\n         full_schedule.push_back( wid );\n         cur_round.push_back( wid );\n         if( cur_round.size() == num_witnesses )\n         {\n            // check that the current round contains exactly 1 copy\n            // of each witness\n            witness_seen.reset();\n            for( const witness_id_type& w : cur_round )\n            {\n               uint64_t inst = w.instance.value;\n               BOOST_CHECK( !witness_seen.test( inst ) );\n               assert( !witness_seen.test( inst ) );\n               witness_seen.set( inst );\n            }\n            cur_round.clear();\n         }\n         generate_block();\n      }\n\n      for( size_t i=0,m=full_schedule.size(); i<m; i++ )\n      {\n         for( size_t j=i+1,n=std::min( m, i+dmin ); j<n; j++ )\n         {\n            BOOST_CHECK( full_schedule[i] != full_schedule[j] );\n            assert( full_schedule[i] != full_schedule[j] );\n         }\n      }\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n*/\n\nBOOST_FIXTURE_TEST_CASE( tapos_rollover, database_fixture )\n{\n   try\n   {\n      ACTORS((alice)(bob));\n\n      BOOST_TEST_MESSAGE( \"Give Alice some money\" );\n      transfer(committee_account, alice_id, asset(10000));\n      generate_block();\n\n      BOOST_TEST_MESSAGE( \"Generate up to block 0xFF00\" );\n      generate_blocks( 0xFF00 );\n      signed_transaction xfer_tx;\n\n      BOOST_TEST_MESSAGE( \"Transfer money at/about 0xFF00\" );\n      transfer_operation xfer_op;\n      xfer_op.from = alice_id;\n      xfer_op.to = bob_id;\n      xfer_op.amount = asset(1000);\n\n      xfer_tx.operations.push_back( xfer_op );\n      xfer_tx.set_expiration( db.head_block_time() + fc::seconds( 0x1000 * db.get_global_properties().parameters.block_interval ) );\n      xfer_tx.set_reference_block( db.head_block_id() );\n\n      sign( xfer_tx, alice_private_key );\n      PUSH_TX( db, xfer_tx, 0 );\n      generate_block();\n\n      BOOST_TEST_MESSAGE( \"Sign new tx's\" );\n      xfer_tx.set_expiration( db.head_block_time() + fc::seconds( 0x1000 * db.get_global_properties().parameters.block_interval ) );\n      xfer_tx.set_reference_block( db.head_block_id() );\n      xfer_tx.clear_signatures();\n      sign( xfer_tx, alice_private_key );\n\n      BOOST_TEST_MESSAGE( \"Generate up to block 0x10010\" );\n      generate_blocks( 0x110 );\n\n      BOOST_TEST_MESSAGE( \"Transfer at/about block 0x10010 using reference block at/about 0xFF00\" );\n      PUSH_TX( db, xfer_tx, 0 );\n      generate_block();\n   }\n   catch (fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_FIXTURE_TEST_CASE( temp_account_balance, database_fixture )\n{ try {\n   ACTORS( (alice) );\n   fund( alice );\n   create_user_issued_asset( \"UIA\" );\n\n   generate_block();\n   set_expiration( db, trx );\n\n   transfer_operation top;\n   top.amount = asset( 1000 );\n   top.from = alice_id;\n   top.to   = GRAPHENE_TEMP_ACCOUNT;\n   trx.operations.push_back( top );\n\n   limit_order_create_operation loc;\n   loc.amount_to_sell = top.amount;\n   loc.expiration = db.head_block_time() + 1;\n   loc.seller = GRAPHENE_TEMP_ACCOUNT;\n   loc.min_to_receive = asset( 1000, asset_id_type(1) );\n   trx.operations.push_back( loc );\n   sign( trx, alice_private_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n\n   generate_block();\n   generate_block();\n   generate_block();\n\n   top.to = GRAPHENE_COMMITTEE_ACCOUNT;\n   trx.operations.push_back( top );\n   set_expiration( db, trx );\n   trx.clear_signatures();\n   sign( trx, alice_private_key );\n   PUSH_TX( db, trx );\n\n   BOOST_CHECK( get_balance( GRAPHENE_TEMP_ACCOUNT, asset_id_type() ) > 0 );\n} FC_LOG_AND_RETHROW() }\n\n///\n/// This test case tries to\n/// * generate blocks when there are too many pending transactions,\n/// * push blocks that are too large.\n/// If we add some logging in signed_transaction::get_signature_keys(), we can see if the code will extract public key(s)\n/// from signature(s) of same transactions multiple times.\n/// See https://github.com/bitshares/bitshares-core/pull/1251\n///\nBOOST_FIXTURE_TEST_CASE( block_size_test, database_fixture )\n{\n   try\n   {\n      ACTORS((alice)(bob));\n\n      const fc::ecc::private_key& key = generate_private_key(\"null_key\");\n      BOOST_TEST_MESSAGE( \"Give Alice some money\" );\n      transfer(committee_account, alice_id, asset(10000000));\n      generate_block();\n\n      const size_t default_block_header_size = fc::raw::pack_size( signed_block_header() );\n      const auto& gpo = db.get_global_properties();\n      const auto block_interval = gpo.parameters.block_interval;\n      idump( (db.head_block_num())(default_block_header_size)(gpo.parameters.maximum_block_size) );\n\n      BOOST_TEST_MESSAGE( \"Start\" );\n      // Note: a signed transaction with a transfer operation inside is at least 102 bytes;\n      //       after processed, it become 103 bytes;\n      //       an empty block is 112 bytes;\n      //       a block with a transfer is 215 bytes;\n      //       a block with 2 transfers is 318 bytes.\n      uint32_t large_block_count = 0;\n      for( uint64_t i = 90; i <= 230; ++i )\n      {\n         if( i > 120 && i < 200 ) // skip some\n            i = 200;\n\n         // Temporarily disable undo db and change max block size\n         db._undo_db.disable();\n         db.modify( gpo, [i,&default_block_header_size](global_property_object& p) {\n            p.parameters.maximum_block_size = default_block_header_size + i;\n         });\n         db._undo_db.enable();\n         idump( (i)(gpo.parameters.maximum_block_size) );\n\n         // push a transaction\n         signed_transaction xfer_tx;\n         transfer_operation xfer_op;\n         xfer_op.from = alice_id;\n         xfer_op.to = bob_id;\n         xfer_op.amount = asset(i);\n         xfer_tx.operations.push_back( xfer_op );\n         xfer_tx.set_expiration( db.head_block_time() + fc::seconds( 0x1000 * block_interval ) );\n         xfer_tx.set_reference_block( db.head_block_id() );\n         sign( xfer_tx, alice_private_key );\n         auto processed_tx = PUSH_TX( db, xfer_tx, database::skip_nothing );\n\n         // sign a temporary block\n         signed_block maybe_large_block;\n         maybe_large_block.transactions.push_back(processed_tx);\n         maybe_large_block.previous = db.head_block_id();\n         maybe_large_block.timestamp = db.get_slot_time(1);\n         maybe_large_block.transaction_merkle_root = maybe_large_block.calculate_merkle_root();\n         maybe_large_block.witness = db.get_scheduled_witness(1);\n         maybe_large_block.sign(key);\n         auto maybe_large_block_size = fc::raw::pack_size(maybe_large_block);\n         idump( (maybe_large_block_size) );\n\n         // should fail to push if it's too large\n         if( maybe_large_block_size > gpo.parameters.maximum_block_size )\n         {\n            ++large_block_count;\n            BOOST_CHECK_THROW( db.push_block(maybe_large_block), fc::exception );\n         }\n\n         // generate a block normally\n         auto good_block = db.generate_block( db.get_slot_time(1), db.get_scheduled_witness(1), key, database::skip_nothing );\n         idump( (fc::raw::pack_size(good_block)) );\n      }\n      // make sure we have tested at least once pushing a large block\n      BOOST_CHECK_GT( large_block_count, 0u );\n   }\n   catch( fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/bsip48_75_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bsip48_75_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( bsip48_75_hardfork_protection_test )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_1270_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION;\n\n      uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set\n      uint16_t uiaflag = ~(bitmask ^ uiamask); // high bits are set\n\n      vector<operation> ops;\n\n      // Testing asset_create_operation\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMCOIN\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100;\n      acop.common_options.flags = uiaflag;\n      acop.common_options.issuer_permissions = uiamask;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n\n      {\n         auto& op = trx.operations.front().get<asset_create_operation>();\n\n         // Unable to set new permission bits\n         op.common_options.issuer_permissions = ( uiamask | lock_max_supply );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.common_options.issuer_permissions = ( uiamask | disable_new_supply );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.bitasset_opts = bitasset_options();\n         op.bitasset_opts->minimum_feeds = 3;\n         op.common_options.flags = bitflag;\n\n         op.common_options.issuer_permissions = ( bitmask | disable_mcr_update );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.common_options.issuer_permissions = ( bitmask | disable_icr_update );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.common_options.issuer_permissions = ( bitmask | disable_mssr_update );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.common_options.issuer_permissions = bitmask;\n\n         // Unable to set new extensions in bitasset options\n         op.bitasset_opts->extensions.value.maintenance_collateral_ratio = 1500;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.bitasset_opts->extensions.value.maintenance_collateral_ratio = {};\n\n         op.bitasset_opts->extensions.value.maximum_short_squeeze_ratio = 1500;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.bitasset_opts->extensions.value.maximum_short_squeeze_ratio = {};\n\n         acop = op;\n      }\n\n      // Able to create asset without new data\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& samcoin = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type samcoin_id = samcoin.get_id();\n\n      BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 );\n      BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 );\n\n      // Unable to propose the invalid operations\n      for( const operation& op : ops )\n         BOOST_CHECK_THROW( propose( op ), fc::exception );\n      ops.clear();\n      // Able to propose the good operation\n      propose( acop );\n\n      // Testing asset_update_operation\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = samcoin_id;\n      auop.new_options = samcoin_id(db).options;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      {\n         auto& op = trx.operations.front().get<asset_update_operation>();\n         op.new_options.market_fee_percent = 200;\n         op.new_options.flags &= ~witness_fed_asset;\n\n         // Unable to set new permission bits\n         op.new_options.issuer_permissions = ( bitmask | lock_max_supply );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.new_options.issuer_permissions = ( bitmask | disable_new_supply );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.new_options.issuer_permissions = ( bitmask | disable_mcr_update );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.new_options.issuer_permissions = ( bitmask | disable_icr_update );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.new_options.issuer_permissions = ( bitmask | disable_mssr_update );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n\n         op.new_options.issuer_permissions = bitmask;\n\n         // Unable to set new extensions\n         op.extensions.value.new_precision = 8;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.extensions.value.new_precision = {};\n\n         op.extensions.value.skip_core_exchange_rate = true;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.extensions.value.skip_core_exchange_rate = {};\n\n         auop = op;\n      }\n\n      // Able to update asset without new data\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 );\n\n      // Unable to propose the invalid operations\n      for( const operation& op : ops )\n         BOOST_CHECK_THROW( propose( op ), fc::exception );\n      ops.clear();\n      // Able to propose the good operation\n      propose( auop );\n\n\n      // Testing asset_update_bitasset_operation\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = samcoin_id;\n      aubop.new_options = samcoin_id(db).bitasset_data(db).options;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n\n      {\n         auto& op = trx.operations.front().get<asset_update_bitasset_operation>();\n         op.new_options.minimum_feeds = 1;\n\n         // Unable to set new extensions\n         op.new_options.extensions.value.maintenance_collateral_ratio = 1500;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.new_options.extensions.value.maintenance_collateral_ratio = {};\n\n         op.new_options.extensions.value.maximum_short_squeeze_ratio = 1500;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.new_options.extensions.value.maximum_short_squeeze_ratio = {};\n\n         aubop = op;\n      }\n\n      // Able to update bitasset without new data\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 );\n\n      // Unable to propose the invalid operations\n      for( const operation& op : ops )\n         BOOST_CHECK_THROW( propose( op ), fc::exception );\n      ops.clear();\n      // Able to propose the good operation\n      propose( aubop );\n\n      // Testing asset_publish_feed_operation\n      update_feed_producers( samcoin, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,samcoin_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,samcoin_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n\n      asset_publish_feed_operation apfop;\n      apfop.publisher = feeder_id;\n      apfop.asset_id = samcoin_id;\n      apfop.feed = f;\n\n      trx.operations.clear();\n      trx.operations.push_back( apfop );\n\n      {\n         auto& op = trx.operations.front().get<asset_publish_feed_operation>();\n\n         // Unable to set new extensions\n         op.extensions.value.initial_collateral_ratio = 1500;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.extensions.value.initial_collateral_ratio = {};\n\n         apfop = op;\n      }\n\n      // Able to publish feed without new data\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).current_feed.initial_collateral_ratio,\n                         f.maintenance_collateral_ratio );\n\n      // Unable to propose the invalid operations\n      for( const operation& op : ops )\n         BOOST_CHECK_THROW( propose( op ), fc::exception );\n      ops.clear();\n      // Able to propose the good operation\n      propose( apfop );\n\n      // Check what we have now\n      idump( (samcoin) );\n      idump( (samcoin.bitasset_data(db)) );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( prediction_market_global_settle_permission )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_1270_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // create a prediction market\n      const asset_object& pm = create_prediction_market( \"PDM\", sam_id );\n      asset_id_type pm_id = pm.get_id();\n\n      BOOST_CHECK( pm_id(db).can_global_settle() );\n\n      // disable global_settle permission\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n      auop.new_options.issuer_permissions &= ~global_settle;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !pm_id(db).can_global_settle() );\n\n      // create some supply\n      borrow( sam, asset(100, pm_id), asset(100) );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // try to enable global_settle again, should fail\n      auop.new_options.issuer_permissions |= global_settle;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      BOOST_CHECK( !pm_id(db).can_global_settle() );\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 );\n      BOOST_CHECK( !pm_id(db).can_global_settle() );\n\n      // try to update the asset without enabling global_settle permission, should fail\n      auop.new_options.issuer_permissions &= ~global_settle;\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK( !pm_id(db).can_global_settle() );\n\n      // try to enable global_settle again, should succeed\n      auop.new_options.issuer_permissions |= global_settle;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( pm_id(db).can_global_settle() );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( update_max_supply )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_1270_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      // create a UIA\n      const asset_object& uia = create_user_issued_asset( \"UIATEST\", sam, charge_market_fee );\n      asset_id_type uia_id = uia.get_id();\n\n      // issue some to Sam\n      issue_uia( sam_id, uia.amount( GRAPHENE_MAX_SHARE_SUPPLY - 100 ) );\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // update max supply to a smaller number\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.new_options.max_supply -= 101;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply < current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 101 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value + 1 );\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n\n      // able to set max supply to be equal to current supply\n      auop.new_options.max_supply += 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply == current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // no longer able to set max supply to a number smaller than current supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.max_supply += 1;\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply == current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // increase max supply again\n      auop.new_options.max_supply += 2;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // decrease max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // update flag to disable updating of max supply\n      auop.new_options.flags |= lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // unable to update max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.max_supply += 1;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // update flag to enable updating of max supply\n      auop.new_options.flags &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // able to update max supply\n      auop.new_options.max_supply += 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // update flag to disable updating of max supply\n      auop.new_options.flags |= lock_max_supply;\n      // update permission to disable updating of lock_max_supply flag\n      auop.new_options.issuer_permissions |= lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // Able to propose the operation\n      propose( auop );\n\n      // unable to reinstall the permission\n      auop.new_options.issuer_permissions &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.issuer_permissions |= lock_max_supply;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // unable to update max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.max_supply += 1;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // unable to enable the lock_max_supply flag\n      auop.new_options.flags &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.flags |= lock_max_supply;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n\n      // able to update other parameters\n      auto old_market_fee_percent = auop.new_options.market_fee_percent;\n      BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, old_market_fee_percent );\n\n      auop.new_options.market_fee_percent = 120u;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, 120u );\n\n      // reserve all supply\n      reserve_asset( sam_id, uia_id(db).amount( GRAPHENE_MAX_SHARE_SUPPLY - 100 ) );\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // still unable to update max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.max_supply += 1;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // still unable to enable the lock_max_supply flag\n      auop.new_options.flags &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.flags |= lock_max_supply;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // able to reinstall the permission and do it\n      auop.new_options.issuer_permissions &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // still unable to update max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.max_supply += 1;\n\n      BOOST_CHECK( !uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // now able to enable the lock_max_supply flag\n      auop.new_options.flags &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // update max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // issue some\n      issue_uia( sam_id, uia_id(db).amount( 100 ) );\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // update permission to disable updating of lock_max_supply flag\n      auop.new_options.issuer_permissions |= lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // still can update max supply\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // unable to reinstall the permission\n      auop.new_options.issuer_permissions &= ~lock_max_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.issuer_permissions |= lock_max_supply;\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // update max supply\n      auop.new_options.max_supply -= 1;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_update_max_supply() );\n      // max_supply > current_supply\n      BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( disable_new_supply_uia )\n{\n   try {\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      // create a UIA\n      const asset_object& uia = create_user_issued_asset( \"UIATEST\", sam, charge_market_fee );\n      asset_id_type uia_id = uia.get_id();\n\n      BOOST_CHECK( uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // issue some to Sam\n      issue_uia( sam_id, uia_id(db).amount( 100 ) );\n\n      BOOST_CHECK( uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // prepare to update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n\n      // update flag to disable creation of new supply\n      auop.new_options.flags |= disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // unable to issue more coins\n      BOOST_CHECK_THROW( issue_uia( sam_id, uia_id(db).amount( 100 ) ), fc::exception );\n\n      BOOST_CHECK( !uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // update flag to enable creation of new supply\n      auop.new_options.flags &= ~disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // issue some to Sam\n      issue_uia( sam_id, uia_id(db).amount( 100 ) );\n\n      BOOST_CHECK( uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // update flag to disable creation of new supply\n      auop.new_options.flags |= disable_new_supply;\n      // update permission to disable updating of disable_new_supply flag\n      auop.new_options.issuer_permissions |= disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // Able to propose the operation\n      propose( auop );\n\n      // unable to reinstall the permission\n      auop.new_options.issuer_permissions &= ~disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.issuer_permissions |= disable_new_supply;\n\n      BOOST_CHECK( !uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // unable to issue more coins\n      BOOST_CHECK_THROW( issue_uia( sam_id, uia_id(db).amount( 100 ) ), fc::exception );\n\n      BOOST_CHECK( !uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // unable to enable the disable_new_supply flag\n      auop.new_options.flags &= ~disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.flags |= disable_new_supply;\n\n      BOOST_CHECK( !uia_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( disable_new_supply_pm )\n{\n   try {\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      fund( sam, asset(10000) );\n\n      // create a PM\n      const asset_object& pm = create_prediction_market( \"PDM\", sam_id );\n      asset_id_type pm_id = pm.get_id();\n\n      BOOST_CHECK( pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // Sam borrow some\n      borrow( sam, asset(100, pm_id), asset(100) );\n\n      BOOST_CHECK( pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // prepare to update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n\n      // update flag to disable creation of new supply\n      auop.new_options.flags |= disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // unable to borrow more\n      BOOST_CHECK_THROW( borrow( sam, asset(100, pm_id), asset(100) ), fc::exception );\n\n      BOOST_CHECK( !pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // update flag to enable creation of new supply\n      auop.new_options.flags &= ~disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // Sam borrow some\n      borrow( sam, asset(100, pm_id), asset(100) );\n\n      BOOST_CHECK( pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // update flag to disable creation of new supply\n      auop.new_options.flags |= disable_new_supply;\n      // update permission to disable updating of disable_new_supply flag\n      auop.new_options.issuer_permissions |= disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // Able to propose the operation\n      propose( auop );\n\n      // unable to reinstall the permission\n      auop.new_options.issuer_permissions &= ~disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.issuer_permissions |= disable_new_supply;\n\n      BOOST_CHECK( !pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // unable to borrow more coins\n      BOOST_CHECK_THROW( borrow( sam, asset(100, pm_id), asset(100) ), fc::exception );\n\n      BOOST_CHECK( !pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      // unable to enable the disable_new_supply flag\n      auop.new_options.flags &= ~disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.flags |= disable_new_supply;\n\n      BOOST_CHECK( !pm_id(db).can_create_new_supply() );\n      BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( skip_core_exchange_rate )\n{\n   try {\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      // create a UIA\n      const asset_object& uia = create_user_issued_asset( \"UIATEST\", sam, charge_market_fee );\n      asset_id_type uia_id = uia.get_id();\n\n      BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(1, uia_id), asset(1)) );\n\n      // prepare to update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n\n      // update CER\n      auop.new_options.core_exchange_rate = price(asset(2, uia_id), asset(1));\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // CER changed\n      BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(2, uia_id), asset(1)) );\n\n      // save value for later check\n      auto old_market_fee_percent = auop.new_options.market_fee_percent;\n      BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, old_market_fee_percent );\n\n      // set skip_core_exchange_rate to false, should fail\n      auop.new_options.core_exchange_rate = price(asset(3, uia_id), asset(1));\n      auop.extensions.value.skip_core_exchange_rate = false;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // unable to propose either\n      BOOST_CHECK_THROW( propose( auop ), fc::exception );\n\n      // CER didn't change\n      BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(2, uia_id), asset(1)) );\n\n      // skip updating CER\n      auop.extensions.value.skip_core_exchange_rate = true;\n      auop.new_options.market_fee_percent = 120u;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // CER didn't change\n      BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(2, uia_id), asset(1)) );\n      // market_fee_percent changed\n      BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, 120u );\n\n      // Able to propose the operation\n      propose( auop );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( invalid_flags_in_asset )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_1270_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION;\n\n      uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set\n      uint16_t uiaflag = ~(bitmask ^ uiamask); // high bits are set\n\n      // Able to create UIA with invalid flags\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMCOIN\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100;\n      acop.common_options.flags = uiaflag;\n      acop.common_options.issuer_permissions = uiamask;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& samcoin = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type samcoin_id = samcoin.get_id();\n\n      // There are invalid bits in flags\n      BOOST_CHECK( samcoin_id(db).options.flags & ~UIA_VALID_FLAGS_MASK );\n\n      // Able to create MPA with invalid flags\n      asset_create_operation acop2 = acop;\n      acop2.symbol = \"SAMBIT\";\n      acop2.bitasset_opts = bitasset_options();\n      acop2.common_options.flags = bitflag;\n      acop2.common_options.issuer_permissions = bitmask;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop2 );\n\n      ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& sambit = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type sambit_id = sambit.get_id();\n\n      // There are invalid bits in flags\n      BOOST_CHECK( sambit_id(db).options.flags & ~VALID_FLAGS_MASK );\n\n      // Unable to correct the invalid flags of the UIA\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = samcoin_id;\n      auop.new_options = samcoin_id(db).options;\n      auop.new_options.flags = 0;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unable to correct the invalid flags of the MPA\n      asset_update_operation auop2;\n      auop2.issuer = sam_id;\n      auop2.asset_to_update = sambit_id;\n      auop2.new_options = sambit_id(db).options;\n      auop2.new_options.flags = 0;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop2 );\n\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      if( hf2467 ) // Note: tests hf core-2281 too, assumes hf core-2281 and hf core-2467 occur at the same time\n      {\n         auto mi = db.get_global_properties().parameters.maintenance_interval;\n         generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      }\n      set_expiration( db, trx );\n\n      // take a look at flags of UIA\n      BOOST_CHECK( samcoin_id(db).options.flags != UIA_VALID_FLAGS_MASK );\n\n      // Try to update UIA but leave some invalid flags, should fail\n      auop.new_options = samcoin_id(db).options;\n      for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 )\n      {\n         idump( (bit) );\n         auop.new_options.flags = UIA_VALID_FLAGS_MASK | bit;\n         if( auop.new_options.flags == UIA_VALID_FLAGS_MASK )\n            continue;\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         // Unable to propose either if the bit is not a valid bit for MPA\n         if( !(bit & VALID_FLAGS_MASK) )\n            BOOST_CHECK_THROW( propose( auop ), fc::exception );\n      }\n\n      // Unset the invalid bits in flags, should succeed\n      auop.new_options.flags = UIA_VALID_FLAGS_MASK;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin_id(db).options.flags, UIA_VALID_FLAGS_MASK );\n\n      // Able to propose too\n      propose( auop );\n\n      // take a look at flags of MPA\n      uint16_t valid_bitflag = VALID_FLAGS_MASK & ~committee_fed_asset;\n      if( !hf2467 ) // Note: actually it is for hf core-2281\n         valid_bitflag = valid_bitflag & ~disable_collateral_bidding;\n      BOOST_CHECK( sambit_id(db).options.flags != valid_bitflag );\n\n      // Try to update MPA but leave some invalid flags, should fail\n      auop2.new_options = sambit_id(db).options;\n      for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 )\n      {\n         idump( (bit) );\n         auop2.new_options.flags = valid_bitflag | bit;\n         if( auop2.new_options.flags == valid_bitflag )\n            continue;\n         trx.operations.clear();\n         trx.operations.push_back( auop2 );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         // Unable to propose either\n         BOOST_CHECK_THROW( propose( auop2 ), fc::exception );\n      }\n\n      // Unset the invalid bits in flags, should succeed\n      auop2.new_options.flags = valid_bitflag;\n      trx.operations.clear();\n      trx.operations.push_back( auop2 );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( sambit_id(db).options.flags, valid_bitflag );\n\n      // Able to propose too\n      propose( auop2 );\n\n      // Unable to create a new UIA with an unknown bit in flags\n      acop.symbol = \"NEWSAMCOIN\";\n      // With all possible bits in permissions set to 1\n      acop2.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;\n      for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 )\n      {\n         acop.common_options.flags = UIA_VALID_FLAGS_MASK | bit;\n         if( acop.common_options.flags == UIA_VALID_FLAGS_MASK )\n            continue;\n         trx.operations.clear();\n         trx.operations.push_back( acop );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         // Unable to propose either\n         BOOST_CHECK_THROW( propose( acop ), fc::exception );\n      }\n\n      // Able to create a new UIA with a valid flags field\n      acop.common_options.flags = UIA_VALID_FLAGS_MASK;\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& newsamcoin = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type newsamcoin_id = newsamcoin.get_id();\n\n      BOOST_CHECK_EQUAL( newsamcoin_id(db).options.flags, UIA_VALID_FLAGS_MASK );\n\n      // Able to propose too\n      propose( acop );\n\n      // Unable to create a new MPA with an unknown bit in flags\n      acop2.symbol = \"NEWSAMBIT\";\n      // With all possible bits in permissions set to 1\n      // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time\n      acop2.common_options.issuer_permissions = hf2467 ? ASSET_ISSUER_PERMISSION_MASK\n                  : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update & ~disable_collateral_bidding );\n      for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 )\n      {\n         acop2.common_options.flags = valid_bitflag | bit;\n         if( acop2.common_options.flags == valid_bitflag )\n            continue;\n         trx.operations.clear();\n         trx.operations.push_back( acop2 );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         // Unable to propose either\n         BOOST_CHECK_THROW( propose( acop2 ), fc::exception );\n      }\n\n      // Able to create a new MPA with a valid flags field\n      acop2.common_options.flags = valid_bitflag;\n      trx.operations.clear();\n      trx.operations.push_back( acop2 );\n      ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& newsambit = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type newsambit_id = newsambit.get_id();\n\n      BOOST_CHECK_EQUAL( newsambit_id(db).options.flags, valid_bitflag );\n\n      BOOST_CHECK( !newsambit_id(db).can_owner_update_icr() );\n      BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() );\n      // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time\n      if( hf2467 )\n      {\n         BOOST_CHECK( !newsambit_id(db).can_owner_update_bsrm() );\n         BOOST_CHECK( !newsambit_id(db).can_bid_collateral() );\n      }\n      else\n      {\n         BOOST_CHECK( newsambit_id(db).can_owner_update_bsrm() );\n         BOOST_CHECK( newsambit_id(db).can_bid_collateral() );\n      }\n\n      // Able to propose too\n      propose( acop2 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( invalid_flags_in_asset_after_hf2467 )\n{\n   // Note: tests hf core-2281 too, assumes hf core-2281 and hf core-2467 occur at the same time\n   hf2467 = true;\n   INVOKE( invalid_flags_in_asset );\n}\n\nBOOST_AUTO_TEST_CASE( update_asset_precision )\n{\n   try {\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      // create a prediction market\n      const asset_object& pm = create_prediction_market( \"PDM\", sam_id );\n      asset_id_type pm_id = pm.get_id();\n\n      BOOST_CHECK_EQUAL( pm_id(db).precision, 5 );\n\n      // prepare to update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n\n      // Unable to update precision of a PM\n      auop.extensions.value.new_precision = 4;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK_EQUAL( pm_id(db).precision, 5 );\n\n      // Able to propose the operation\n      propose( auop );\n\n      // create a UIA\n      const asset_object& uia = create_user_issued_asset( \"UIATEST\", sam, charge_market_fee );\n      asset_id_type uia_id = uia.get_id();\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 2 );\n\n      // try to set new precision to be the same as the old precision, will fail\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.extensions.value.new_precision = 2;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 2 );\n\n      // try to set new precision to a number which is too big, will fail\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.extensions.value.new_precision = 13;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( auop ), fc::exception );\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 2 );\n\n      // update precision to a valid number, should succeed\n      auop.extensions.value.new_precision = 3;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 3 );\n\n      // create some supply\n      issue_uia( sam_id, asset( 100, uia_id ) );\n\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 );\n\n      // try to update precision, will fail\n      auop.extensions.value.new_precision = 4;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 3 );\n\n      // destroy all supply\n      reserve_asset( sam_id, asset( 100, uia_id ) );\n\n      BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 );\n\n      // update precision, should succeed\n      auop.extensions.value.new_precision = 4;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 4 );\n\n      // create a MPA which is backed by the UIA\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 10, charge_market_fee, 3, uia_id );\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).options.short_backing_asset == uia_id );\n\n      // try to update precision of the UIA, will fail\n      auop.extensions.value.new_precision = 3;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK_EQUAL( uia_id(db).precision, 4 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( asset_owner_permissions_update_icr_mcr_mssr )\n{\n   try {\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      auto current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, f.maintenance_collateral_ratio );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  f.maximum_short_squeeze_ratio );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     feed_icr );\n\n      // disable owner's permission to update icr\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = mpa_id;\n      auop.new_options = mpa_id(db).options;\n      auop.new_options.issuer_permissions |= disable_icr_update;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can not update icr\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1950;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.initial_collateral_ratio.reset();\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      // disable owner's permission to update mcr\n      auop.new_options.issuer_permissions &= ~disable_icr_update;\n      auop.new_options.issuer_permissions |= disable_mcr_update;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can not update mcr\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1650;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.maintenance_collateral_ratio.reset();\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      // disable owner's permission to update mssr\n      auop.new_options.issuer_permissions &= ~disable_mcr_update;\n      auop.new_options.issuer_permissions |= disable_mssr_update;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can not update mssr\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1150;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio.reset();\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      // enable owner's permission to update mssr\n      auop.new_options.issuer_permissions &= ~disable_mssr_update;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can update the ratios\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1950;\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1650;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1150;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1650 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1150 );\n\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1650 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1150 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     1950 );\n\n      // check the ratios' valid range\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1000;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.initial_collateral_ratio = 32001;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1950;\n\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1000;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 32001;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1650;\n\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1000;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 32001;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1150;\n\n      // Sam borrow some\n      borrow( sam, asset(1000, mpa_id), asset(2000) );\n\n      // disable owner's permission to update icr\n      auop.new_options.issuer_permissions |= disable_icr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can not update icr\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1960;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      aubop.new_options.extensions.value.initial_collateral_ratio.reset();\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // able to update other ratios\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1950;\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1600;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1100;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1100 );\n\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1100 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     1950 );\n\n      // unable to enable the permission to update icr\n      auop.new_options.issuer_permissions &= ~disable_icr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.issuer_permissions |= disable_icr_update;\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // disable owner's permission to update mcr\n      auop.new_options.issuer_permissions |= disable_mcr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can not update mcr\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1660;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      aubop.new_options.extensions.value.maintenance_collateral_ratio.reset();\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // able to update other params that still has permission E.G. mssr\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1950;\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1600;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1010;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1010 );\n\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1010 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     1950 );\n\n      // unable to enable the permission to update mcr\n      auop.new_options.issuer_permissions &= ~disable_mcr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options.issuer_permissions |= disable_mcr_update;\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( mpa_id(db).can_owner_update_mssr() );\n\n      // disable owner's permission to update mssr\n      auop.new_options.issuer_permissions |= disable_mssr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mssr() );\n\n      // check that owner can not update mssr\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1020;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio.reset();\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // able to update other params that still has permission E.G. force_settlement_delay_sec\n      aubop.new_options.extensions.value.initial_collateral_ratio = 1950;\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 1600;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1010;\n      aubop.new_options.force_settlement_delay_sec += 1;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec,\n                           aubop.new_options.force_settlement_delay_sec );\n\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() );\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() );\n\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1010 );\n\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1010 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     1950 );\n\n      // unable to enable the permission to update mssr\n      auop.new_options.issuer_permissions &= ~disable_mssr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_icr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() );\n      BOOST_CHECK( !mpa_id(db).can_owner_update_mssr() );\n\n      // publish a new feed\n      f.settlement_price = price( asset(2,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(3,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1830;\n      f.maximum_short_squeeze_ratio = 1230;\n\n      feed_icr = 1930;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // the values set by the asset owner still take effect\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1010 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     1950 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( asset_owner_update_mcr_mssr )\n{\n   try {\n\n      // advance to bsip48/75 hard fork\n      generate_blocks( HARDFORK_BSIP_48_75_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( seller, asset(init_amount) );\n\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n      asset_id_type core_id = asset_id_type();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      auto current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, f.maintenance_collateral_ratio );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  f.maximum_short_squeeze_ratio );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     feed_icr );\n\n      // borrower borrows some and sends to seller\n      const call_order_object* call_ptr = borrow( borrower_id, asset(1000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id { call_ptr->id };\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 1000 );\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount );\n\n      transfer( borrower_id, seller_id, asset(1000, mpa_id) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 1000 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount );\n\n      // seller places orders\n      const limit_order_object* order1_ptr = create_sell_order( seller, asset(100, mpa_id), asset(105) );\n      BOOST_REQUIRE( order1_ptr );\n      limit_order_id_type order1_id = order1_ptr->get_id();\n      BOOST_CHECK_EQUAL( order1_id(db).for_sale.value, 100 );\n      BOOST_CHECK_EQUAL( order1_id(db).amount_to_receive().amount.value, 105 );\n\n      const limit_order_object* order2_ptr = create_sell_order( seller, asset(100, mpa_id), asset(115) );\n      BOOST_REQUIRE( order2_ptr );\n      limit_order_id_type order2_id = order2_ptr->get_id();\n      BOOST_CHECK_EQUAL( order2_id(db).for_sale.value, 100 );\n      BOOST_CHECK_EQUAL( order2_id(db).amount_to_receive().amount.value, 115 );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 800 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount );\n\n      // asset owner updates MCR and MSSR\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.maintenance_collateral_ratio = 3000;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1100;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 3000 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1100 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     feed_icr );\n\n      // borrower should get margin called\n      BOOST_REQUIRE( db.find( call_id ));\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 900 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1895 );\n\n      // limit order1 should be filled\n      BOOST_CHECK( !db.find( order1_id ));\n\n      // limit order2 should not change due to MSSR\n      BOOST_REQUIRE( db.find( order2_id ));\n      BOOST_CHECK_EQUAL( order2_id(db).for_sale.value, 100 );\n      BOOST_CHECK_EQUAL( order2_id(db).amount_to_receive().amount.value, 115 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 800 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount + 105 );\n\n      // asset owner updates MSSR\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1200;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      current_feed = mpa_id(db).bitasset_data(db).current_feed;\n      BOOST_CHECK( current_feed.settlement_price   == f.settlement_price );\n      BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate );\n      BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 3000 );\n      BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio,  1200 );\n      BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio,     feed_icr );\n\n      // borrower should get margin called\n      BOOST_REQUIRE( db.find( call_id ));\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1780 );\n\n      // limit order2 should be filled\n      BOOST_CHECK( !db.find( order2_id ));\n\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 800 );\n      BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount + 105 + 115 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n\n"
  },
  {
    "path": "tests/tests/bsip85_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bsip85_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( hardfork_time_test )\n{ try {\n\n   {\n      // The maker fee discount percent is 0 by default\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 );\n\n      // Try to set new committee parameter before hardfork\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time() );\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation cmuop;\n      cmuop.new_parameters.extensions.value.maker_fee_discount_percent = 1;\n      cop.proposed_ops.emplace_back( cmuop );\n      trx.operations.push_back( cop );\n\n      // It should fail\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      trx.clear();\n\n      // The percent should still be 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 );\n   }\n\n   // Pass the hardfork\n   generate_blocks( HARDFORK_BSIP_85_TIME );\n   set_expiration( db, trx );\n\n   {\n      // The maker fee discount percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 );\n\n      // Try to set new committee parameter after hardfork\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time() );\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation cmuop;\n      cmuop.new_parameters.extensions.value.maker_fee_discount_percent = 10001; // 100.01%\n      cop.proposed_ops.emplace_back(cmuop);\n      trx.operations.push_back(cop);\n\n      // Should fail since the value is too big\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // The maker fee discount percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 );\n\n      trx.operations.clear();\n      cop.proposed_ops.clear();\n      cmuop.new_parameters.extensions.value.maker_fee_discount_percent = 1123; // 11.23%\n      cop.proposed_ops.emplace_back(cmuop);\n      trx.operations.push_back(cop);\n\n      // Should succeed\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      trx.operations.clear();\n      proposal_id_type prop_id { ptx.operation_results[0].get<object_id_type>() };\n\n      // The maker fee discount percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 );\n\n      // Approve the proposal\n      proposal_update_operation uop;\n      uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      uop.active_approvals_to_add = { get_account(\"init0\").get_id(), get_account(\"init1\").get_id(),\n                                      get_account(\"init2\").get_id(), get_account(\"init3\").get_id(),\n                                      get_account(\"init4\").get_id(), get_account(\"init5\").get_id(),\n                                      get_account(\"init6\").get_id(), get_account(\"init7\").get_id() };\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n\n      // The maker fee discount percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 );\n\n      generate_blocks( prop_id( db ).expiration_time + 5 );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n      // The maker fee discount percent should have changed\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 1123 );\n\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( bsip85_maker_fee_discount_test )\n{\n   try\n   {\n      ACTORS((alice)(bob)(izzy));\n\n      int64_t alice_b0 = 1000000, bob_b0 = 1000000;\n      int64_t pool_0 = 1000000, accum_0 = 0;\n\n      transfer( account_id_type(), alice_id, asset(alice_b0) );\n      transfer( account_id_type(), bob_id, asset(bob_b0) );\n\n      asset_id_type core_id = asset_id_type();\n      int64_t cer_core_amount = 1801;\n      int64_t cer_usd_amount = 31;\n      price tmp_cer( asset( cer_core_amount ), asset( cer_usd_amount, asset_id_type(1) ) );\n      const auto& usd_obj = create_user_issued_asset( \"IZZYUSD\", izzy_id(db), charge_market_fee, tmp_cer );\n      asset_id_type usd_id = usd_obj.get_id();\n      issue_uia( alice_id, asset( alice_b0, usd_id ) );\n      issue_uia( bob_id, asset( bob_b0, usd_id ) );\n\n      fund_fee_pool( committee_account( db ), usd_obj, pool_0 );\n\n      // If pay fee in CORE\n      int64_t order_create_fee = 547;\n      int64_t order_maker_refund = 61; // 547 * 11.23% = 61.4281\n\n      // If pay fee in USD\n      int64_t usd_create_fee = order_create_fee * cer_usd_amount / cer_core_amount;\n      if( usd_create_fee * cer_core_amount != order_create_fee * cer_usd_amount ) usd_create_fee += 1;\n      int64_t usd_maker_refund = usd_create_fee * 1123 / 10000;\n      // amount paid by fee pool\n      int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount;\n      int64_t core_maker_refund = usd_maker_refund == 0 ? 0 : core_create_fee * 1123 / 10000;\n\n      fee_parameters::flat_set_type new_fees;\n      limit_order_create_operation::fee_params_t create_fee_params;\n      create_fee_params.fee = order_create_fee;\n      new_fees.insert( create_fee_params );\n\n      // Pass BSIP 85 HF time\n      // Note: no test case for the behavior before the HF since it's covered by other test cases\n      INVOKE( hardfork_time_test );\n      set_expiration( db, trx );\n\n      // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation\n      // so we have to do it every time we stop generating/popping blocks and start doing tx's\n      enable_fees();\n      change_fees( new_fees );\n\n      {\n         // prepare params\n         time_point_sec max_exp = time_point_sec::maximum();\n         price cer = usd_id( db ).options.core_exchange_rate;\n         const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n\n         // balance data\n         int64_t alice_bc = alice_b0, bob_bc = bob_b0; // core balance\n         int64_t alice_bu = alice_b0, bob_bu = bob_b0; // usd balance\n         int64_t pool_b = pool_0, accum_b = accum_0;\n\n         // Check order fill\n         BOOST_TEST_MESSAGE( \"Creating ao1, then be filled by bo1\" );\n         // pays fee in core\n         const limit_order_object* ao1 = create_sell_order( alice_id, asset(1000), asset(200, usd_id) );\n         const limit_order_id_type ao1id = ao1->get_id();\n         // pays fee in usd\n         const limit_order_object* bo1 = create_sell_order(   bob_id, asset(200, usd_id), asset(1000), max_exp, cer );\n\n         BOOST_CHECK( db.find( ao1id ) == nullptr );\n         BOOST_CHECK( bo1 == nullptr );\n\n         // data after order created\n         alice_bc -= 1000; // amount for sale\n         alice_bc -= order_create_fee; // fee\n         bob_bu -= 200; // amount for sale\n         bob_bu -= usd_create_fee; // fee\n         pool_b -= core_create_fee; // fee pool\n         accum_b += 0;\n\n         // data after order filled\n         alice_bu += 200; // bob pays\n         alice_bc += order_maker_refund; // maker fee refund\n         bob_bc += 1000; // alice pays\n         accum_b += usd_create_fee; // bo1 paid fee, was taker, no refund\n         pool_b += 0; // no change\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Check partial fill\n         BOOST_TEST_MESSAGE( \"Creating ao2, then be partially filled by bo2\" );\n         // pays fee in usd\n         const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer );\n         const limit_order_id_type ao2id = ao2->get_id();\n         // pays fee in core\n         const limit_order_object* bo2 = create_sell_order(   bob_id, asset(100, usd_id), asset(500) );\n\n         BOOST_CHECK( db.find( ao2id ) != nullptr );\n         BOOST_CHECK( bo2 == nullptr );\n\n         // data after order created\n         alice_bc -= 1000; // amount to sell\n         alice_bu -= usd_create_fee; // fee\n         pool_b -= core_create_fee; // fee pool\n         accum_b += 0;\n         bob_bc -= order_create_fee; // fee\n         bob_bu -= 100; // amount to sell\n\n         // data after order filled\n         alice_bu += 100; // bob pays\n         alice_bu += usd_maker_refund; // maker fee refund\n         bob_bc += 500;\n         accum_b += usd_create_fee - usd_maker_refund; // ao2 paid fee deduct maker refund\n         pool_b += core_maker_refund; // ao2 maker refund\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n\n"
  },
  {
    "path": "tests/tests/bsip86_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bsip86_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( hardfork_time_test )\n{ try {\n\n   {\n      // The network fee percent is 0 by default\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 0 );\n\n      // Try to set new committee parameter before hardfork\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time() );\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation cmuop;\n      cmuop.new_parameters.extensions.value.market_fee_network_percent = 1;\n      cop.proposed_ops.emplace_back( cmuop );\n      trx.operations.push_back( cop );\n\n      // It should fail\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      trx.clear();\n\n      // The percent should still be 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 0 );\n   }\n\n   // Pass the hardfork\n   generate_blocks( HARDFORK_BSIP_86_TIME );\n   set_expiration( db, trx );\n\n   {\n      // The network fee percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 0 );\n\n      // Try to set new committee parameter after hardfork\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time() );\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation cmuop;\n      cmuop.new_parameters.extensions.value.market_fee_network_percent = 3001; // 30.01%\n      cop.proposed_ops.emplace_back(cmuop);\n      trx.operations.push_back(cop);\n\n      // Should fail since the value is too big\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // The network fee percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 0 );\n\n      trx.operations.clear();\n      cop.proposed_ops.clear();\n      cmuop.new_parameters.extensions.value.market_fee_network_percent = 1123; // 11.23%\n      cop.proposed_ops.emplace_back(cmuop);\n      trx.operations.push_back(cop);\n\n      // Should succeed\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      trx.operations.clear();\n      proposal_id_type prop_id { ptx.operation_results[0].get<object_id_type>() };\n\n      // The network fee percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 0 );\n\n      // Approve the proposal\n      proposal_update_operation uop;\n      uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      uop.active_approvals_to_add = { get_account(\"init0\").get_id(), get_account(\"init1\").get_id(),\n                                      get_account(\"init2\").get_id(), get_account(\"init3\").get_id(),\n                                      get_account(\"init4\").get_id(), get_account(\"init5\").get_id(),\n                                      get_account(\"init6\").get_id(), get_account(\"init7\").get_id() };\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n\n      // The network fee percent is still 0\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 0 );\n\n      generate_blocks( prop_id( db ).expiration_time + 5 );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n      // The network fee percent should have changed\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_market_fee_network_percent(), 1123 );\n\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( fee_sharing_test )\n{ try {\n   ACTORS((alice)(bob));\n\n   uint16_t market_fee_percent = 100; // 1%\n   price cer(asset(1, asset_id_type(1)), asset(1));\n\n   const asset_object& alicecoin = create_user_issued_asset( \"ALICECOIN\", alice_id(db), charge_market_fee,\n                                                             cer, 4, market_fee_percent );\n   const asset_object& aliceusd  = create_user_issued_asset( \"ALICEUSD\",  alice_id(db), 0 );\n\n   asset_id_type alicecoin_id = alicecoin.get_id();\n   asset_id_type aliceusd_id = aliceusd.get_id();\n\n   // prepare users' balance\n   issue_uia( alice, aliceusd.amount( 20000000 ) );\n   issue_uia( bob, alicecoin.amount( 10000000 ) );\n   transfer( account_id_type(), alice_id, asset(10000000) );\n   transfer( account_id_type(), bob_id, asset(10000000) );\n\n   // match and fill orders\n   create_sell_order( alice_id, aliceusd_id(db).amount(200000), alicecoin_id(db).amount(100000) );\n   create_sell_order( bob_id, alicecoin_id(db).amount(100000), aliceusd_id(db).amount(200000) );\n\n   // check fee sharing\n   BOOST_CHECK_EQUAL( get_market_fee_reward( account_id_type(), alicecoin_id ), 0 );\n   BOOST_CHECK_EQUAL( get_market_fee_reward( account_id_type(), aliceusd_id ), 0 );\n\n   // check issuer fees\n   BOOST_CHECK_EQUAL( alicecoin_id(db).dynamic_data(db).accumulated_fees.value, 1000 );\n   BOOST_CHECK_EQUAL( aliceusd_id(db).dynamic_data(db).accumulated_fees.value, 0 );\n\n   // pass the hard fork\n   INVOKE( hardfork_time_test );\n   set_expiration( db, trx );\n\n   // match and fill orders again\n   create_sell_order( alice_id, aliceusd_id(db).amount(200000), alicecoin_id(db).amount(100000) );\n   create_sell_order( bob_id, alicecoin_id(db).amount(100000), aliceusd_id(db).amount(200000) );\n\n   // check fee sharing\n   BOOST_CHECK_EQUAL( get_market_fee_reward( account_id_type(), alicecoin_id ), 112 ); // 1000*11.23%\n   BOOST_CHECK_EQUAL( get_market_fee_reward( account_id_type(), aliceusd_id ), 0 );\n\n   // check issuer fees\n   BOOST_CHECK_EQUAL( alicecoin_id(db).dynamic_data(db).accumulated_fees.value, 1888 ); // 1000+1000-112\n   BOOST_CHECK_EQUAL( aliceusd_id(db).dynamic_data(db).accumulated_fees.value, 0 );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/bsrm_basic_tests.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture )\n\n/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork\nBOOST_AUTO_TEST_CASE( bsrm_hardfork_protection_test )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time\n      uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update & ~disable_collateral_bidding;\n      uint16_t new_bitmask1 = ASSET_ISSUER_PERMISSION_MASK;\n      uint16_t new_bitmask2 = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update;\n      uint16_t new_bitmask3 = ASSET_ISSUER_PERMISSION_MASK & ~disable_collateral_bidding;\n\n      uint16_t old_bitflag = VALID_FLAGS_MASK & ~committee_fed_asset & ~disable_collateral_bidding;\n\n      vector<operation> ops;\n\n      // Testing asset_create_operation\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMCOIN\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100;\n      acop.common_options.flags = old_bitflag;\n      acop.common_options.issuer_permissions = old_bitmask;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 3;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n\n      {\n         auto& op = trx.operations.front().get<asset_create_operation>();\n\n         // Unable to set new permission bit\n         op.common_options.issuer_permissions = new_bitmask1;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.common_options.issuer_permissions = new_bitmask2;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.common_options.issuer_permissions = new_bitmask3;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.common_options.issuer_permissions = old_bitmask;\n\n         // Unable to set new extensions in bitasset options\n         op.bitasset_opts->extensions.value.black_swan_response_method = 0;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.bitasset_opts->extensions.value.black_swan_response_method = {};\n\n         acop = op;\n      }\n\n      // Able to create asset without new data\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& samcoin = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type samcoin_id = samcoin.get_id();\n\n      BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 );\n      BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 );\n\n      // Able to propose the good operation\n      propose( acop );\n\n      // Testing asset_update_operation\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = samcoin_id;\n      auop.new_options = samcoin_id(db).options;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      {\n         auto& op = trx.operations.front().get<asset_update_operation>();\n         op.new_options.market_fee_percent = 200;\n\n         // Unable to set new permission bit\n         op.new_options.issuer_permissions = new_bitmask1;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.new_options.issuer_permissions = new_bitmask2;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.new_options.issuer_permissions = new_bitmask3;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.new_options.issuer_permissions = old_bitmask;\n\n         auop = op;\n      }\n\n      // Able to update asset without new data\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 );\n\n      // Able to propose the good operation\n      propose( auop );\n\n      // Testing asset_update_bitasset_operation\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = samcoin_id;\n      aubop.new_options = samcoin_id(db).bitasset_data(db).options;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n\n      {\n         auto& op = trx.operations.front().get<asset_update_bitasset_operation>();\n         op.new_options.minimum_feeds = 1;\n\n         // Unable to set new extensions\n         op.new_options.extensions.value.black_swan_response_method = 1;\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n         ops.push_back( op );\n         op.new_options.extensions.value.black_swan_response_method = {};\n\n         aubop = op;\n      }\n\n      // Able to update bitasset without new data\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 );\n\n      // Able to propose the good operation\n      propose( aubop );\n\n      // Unable to propose the invalid operations\n      for( const operation& op : ops )\n         BOOST_CHECK_THROW( propose( op ), fc::exception );\n\n      // Check what we have now\n      idump( (samcoin) );\n      idump( (samcoin.bitasset_data(db)) );\n\n      generate_block();\n\n      // Advance to core-2467 hard fork\n      // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      // Now able to propose the operations that was invalid\n      for( const operation& op : ops )\n         propose( op );\n\n      generate_block();\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests scenarios about setting non-UIA issuer permission bits on an UIA\nBOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time\n      uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update & ~disable_collateral_bidding;\n      uint16_t new_bitmask1 = ASSET_ISSUER_PERMISSION_MASK;\n      uint16_t new_bitmask2 = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update;\n      uint16_t new_bitmask3 = ASSET_ISSUER_PERMISSION_MASK & ~disable_collateral_bidding;\n      uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK;\n\n      uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply\n\n      vector<operation> ops;\n\n      asset_id_type samcoin_id = create_user_issued_asset( \"SAMCOIN\", sam_id(db), uiaflag ).get_id();\n\n      // Testing asset_update_operation\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = samcoin_id;\n      auop.new_options = samcoin_id(db).options;\n      auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle;\n\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      // Able to update asset with non-UIA issuer permission bits\n      PUSH_TX(db, trx, ~0);\n\n      // Able to propose too\n      propose( auop );\n\n      // Issue some coin\n      issue_uia( sam_id, asset( 1, samcoin_id ) );\n\n      // Unable to unset the non-UIA \"disable\" issuer permission bits\n      auto perms = samcoin_id(db).options.issuer_permissions;\n\n      auop.new_options.issuer_permissions = perms & ~disable_icr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = perms & ~disable_mcr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = perms & ~disable_mssr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Advance to core-2467 hard fork\n      // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      // Still able to propose\n      auop.new_options.issuer_permissions = new_bitmask1;\n      propose( auop );\n      auop.new_options.issuer_permissions = new_bitmask2;\n      propose( auop );\n      auop.new_options.issuer_permissions = new_bitmask3;\n      propose( auop );\n\n      // But no longer able to update directly\n      auop.new_options.issuer_permissions = uiamask | witness_fed_asset;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | committee_fed_asset;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_icr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_mcr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_mssr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unset the non-UIA bits in issuer permissions, should succeed\n      auop.new_options.issuer_permissions = uiamask;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask );\n\n      // Burn all supply\n      reserve_asset( sam_id, asset( 1, samcoin_id ) );\n\n      BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 );\n\n      // Still unable to set the non-UIA bits in issuer permissions\n      auop.new_options.issuer_permissions = uiamask | witness_fed_asset;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | committee_fed_asset;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_icr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_mcr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_mssr_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      auop.new_options.issuer_permissions = uiamask | disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      generate_block();\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions\nBOOST_AUTO_TEST_CASE( bsrm_asset_permissions_flags_extensions_test )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // Unable to create a PM with the disable_bsrm_update bit in flags\n      BOOST_CHECK_THROW( create_prediction_market( \"TESTPM\", sam_id, 0, disable_bsrm_update ), fc::exception );\n\n      // Unable to create a MPA with the disable_bsrm_update bit in flags\n      BOOST_CHECK_THROW( create_bitasset( \"TESTBIT\", sam_id, 0, disable_bsrm_update ), fc::exception );\n\n      // Unable to create a UIA with the disable_bsrm_update bit in flags\n      BOOST_CHECK_THROW( create_user_issued_asset( \"TESTUIA\", sam_id(db), disable_bsrm_update ), fc::exception );\n\n      // create a PM with a zero market_fee_percent\n      const asset_object& pm = create_prediction_market( \"TESTPM\", sam_id, 0, charge_market_fee );\n      asset_id_type pm_id = pm.get_id();\n\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      // create a UIA with a zero market_fee_percent\n      const asset_object& uia = create_user_issued_asset( \"TESTUIA\", sam_id(db), charge_market_fee );\n      asset_id_type uia_id = uia.get_id();\n\n      // Prepare for asset update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n\n      // Unable to set disable_bsrm_update bit in flags for PM\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n      auop.new_options.flags |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( auop ), fc::exception );\n\n      // Unable to set disable_bsrm_update bit in flags for MPA\n      auop.asset_to_update = mpa_id;\n      auop.new_options = mpa_id(db).options;\n      auop.new_options.flags |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( auop ), fc::exception );\n\n      // Unable to set disable_bsrm_update bit in flags for UIA\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.new_options.flags |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( auop ), fc::exception );\n\n      // Unable to set disable_bsrm_update bit in issuer_permissions for PM\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n      auop.new_options.issuer_permissions |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // But able to propose\n      propose( auop );\n\n      // Unable to set disable_bsrm_update bit in issuer_permissions for UIA\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.new_options.issuer_permissions |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // But able to propose\n      propose( auop );\n\n      // Unable to create a UIA with disable_bsrm_update permission bit\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMCOIN\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100;\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( acop ), fc::exception );\n\n      // Able to create UIA without disable_bsrm_update permission bit\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      PUSH_TX(db, trx, ~0);\n\n      // Unable to create a PM with disable_bsrm_update permission bit\n      acop.symbol = \"SAMPM\";\n      acop.precision = asset_id_type()(db).precision;\n      acop.is_prediction_market = true;\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update;\n      acop.bitasset_opts = bitasset_options();\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( acop ), fc::exception );\n\n      // Unable to create a PM with BSRM in extensions\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = 0;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( acop ), fc::exception );\n\n      // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle;\n      acop.bitasset_opts->extensions.value.black_swan_response_method.reset();\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      PUSH_TX(db, trx, ~0);\n\n      // Unable to update PM to set BSRM\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = pm_id;\n      aubop.new_options = pm_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.black_swan_response_method = 1;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Able to propose\n      propose( aubop );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests whether asset owner has permission to update bsrm\nBOOST_AUTO_TEST_CASE( bsrm_asset_owner_permissions_update_bsrm )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() );\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // Prepare for asset update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = mpa_id;\n      auop.new_options = mpa_id(db).options;\n\n      // disable owner's permission to update bsrm\n      auop.new_options.issuer_permissions |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() );\n\n      // check that owner can not update bsrm\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n\n      aubop.new_options.extensions.value.black_swan_response_method = 1;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.black_swan_response_method.reset();\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() );\n\n      // enable owner's permission to update bsrm\n      auop.new_options.issuer_permissions &= ~disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() );\n\n      // check that owner can update bsrm\n      aubop.new_options.extensions.value.black_swan_response_method = 1;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() );\n\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u );\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // check bsrm' valid range\n      aubop.new_options.extensions.value.black_swan_response_method = 4;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      aubop.new_options.extensions.value.black_swan_response_method = 1;\n\n      // Sam borrow some\n      borrow( sam, asset(1000, mpa_id), asset(2000) );\n\n      // disable owner's permission to update bsrm\n      auop.new_options.issuer_permissions |= disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() );\n\n      // check that owner can not update bsrm\n      aubop.new_options.extensions.value.black_swan_response_method = 0;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      aubop.new_options.extensions.value.black_swan_response_method.reset();\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      aubop.new_options.extensions.value.black_swan_response_method = 1;\n\n      // able to update other params that still has permission E.G. force_settlement_delay_sec\n      aubop.new_options.force_settlement_delay_sec += 1;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec,\n                           aubop.new_options.force_settlement_delay_sec );\n\n      BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() );\n\n      BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u );\n\n      // unable to enable the permission to update bsrm\n      auop.new_options.issuer_permissions &= ~disable_bsrm_update;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests closing debt position when there is no sufficient price feeds\nBOOST_AUTO_TEST_CASE( close_debt_position_when_no_feed )\n{\n   try {\n\n      // Advance to a time before core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( borrower, asset(init_amount) );\n\n      // Create asset\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // update price feed publisher list so that there is no valid feed\n      update_feed_producers( mpa_id, { sam_id } );\n\n      // no sufficient price feeds\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // Unable to close debt position\n      BOOST_CHECK_THROW( cover( borrower, asset(100000, mpa_id), asset(2000) ), fc::exception );\n      BOOST_CHECK( db.find( call_id ) );\n\n      // Go beyond the hard fork time\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      // Still no sufficient price feeds\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // The debt position is there\n      BOOST_CHECK( db.find( call_id ) );\n\n      // Able to close debt position\n      cover( borrower_id(db), asset(100000, mpa_id), asset(2000) );\n      BOOST_CHECK( !db.find( call_id ) );\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      // final check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n      BOOST_CHECK( !db.find( call_id ) );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests whether it is able to update BSRM after GS\nBOOST_AUTO_TEST_CASE( update_bsrm_after_gs )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( borrower, asset(init_amount) );\n\n      // Create asset\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      ilog( \"Publish a new feed to trigger GS\" );\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n      BOOST_CHECK( !db.find( call_id ) );\n\n      // Sam tries to update BSRM\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n\n      for( uint16_t i = 1; i <= 3; ++i )\n      {\n         idump( (i) );\n         aubop.new_options.extensions.value.black_swan_response_method = i;\n         trx.operations.clear();\n         trx.operations.push_back( aubop );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      }\n\n      // recheck\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // publish a new feed to revive the MPA\n      ilog( \"Publish a new feed to revive MPA\" );\n      f.settlement_price = price( asset(1000,mpa_id), asset(3) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check - revived\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // Sam tries to update BSRM\n      for( uint8_t i = 1; i <= 3; ++i )\n      {\n         idump( (i) );\n         aubop.new_options = mpa_id(db).bitasset_data(db).options;\n         aubop.new_options.extensions.value.black_swan_response_method = i;\n         trx.operations.clear();\n         trx.operations.push_back( aubop );\n         PUSH_TX(db, trx, ~0);\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(i) );\n\n         if( i != 3 )\n         {\n            aubop.new_options.extensions.value.black_swan_response_method = 0;\n            trx.operations.clear();\n            trx.operations.push_back( aubop );\n            PUSH_TX(db, trx, ~0);\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                            == bsrm_type::global_settlement );\n         }\n      }\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      // final check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(3) );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests whether it is able to update BSRM after individual settlement to fund\nBOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_fund);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_fund );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      ilog( \"Publish a new feed to trigger settlement\" );\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK( db.find( call2_id ) );\n\n      // Sam tries to update BSRM\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n\n      for( uint16_t i = 0; i <= 3; ++i )\n      {\n         if( static_cast<bsrm_type>(i) == bsrm_type::individual_settlement_to_fund )\n            continue;\n         idump( (i) );\n         aubop.new_options.extensions.value.black_swan_response_method = i;\n         trx.operations.clear();\n         trx.operations.push_back( aubop );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      }\n\n      // recheck\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_fund );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // Settle debt\n      ilog( \"Settle\" );\n      force_settle( borrower2, asset(100000,mpa_id) );\n\n      // recheck\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // Sam tries to update BSRM\n      for( uint8_t i = 0; i <= 3; ++i )\n      {\n         if( static_cast<bsrm_type>(i) == bsrm_type::individual_settlement_to_fund )\n            continue;\n         idump( (i) );\n         aubop.new_options = mpa_id(db).bitasset_data(db).options;\n         aubop.new_options.extensions.value.black_swan_response_method = i;\n         trx.operations.clear();\n         trx.operations.push_back( aubop );\n         PUSH_TX(db, trx, ~0);\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(i) );\n         if( i != 3 )\n         {\n            aubop.new_options.extensions.value.black_swan_response_method = bsrm_value;\n            trx.operations.clear();\n            trx.operations.push_back( aubop );\n            PUSH_TX(db, trx, ~0);\n            BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::individual_settlement_to_fund );\n         }\n      }\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      // final check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(3) );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests whether it is able to update BSRM after individual settlement to order\nBOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_order);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_order );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      ilog( \"Publish a new feed to trigger settlement\" );\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( db.find_settled_debt_order(mpa_id) );\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK( db.find( call2_id ) );\n\n      // Sam tries to update BSRM\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n\n      for( uint16_t i = 0; i <= 3; ++i )\n      {\n         if( static_cast<bsrm_type>(i) == bsrm_type::individual_settlement_to_order )\n            continue;\n         idump( (i) );\n         aubop.new_options.extensions.value.black_swan_response_method = i;\n         trx.operations.clear();\n         trx.operations.push_back( aubop );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      }\n\n      // recheck\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_order );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( db.find_settled_debt_order(mpa_id) );\n\n      // Fill the individual settlement order\n      ilog( \"Buy into the individual settlement order\" );\n      const limit_order_object* sell_ptr = create_sell_order( borrower2, asset(100000,mpa_id), asset(1) );\n      BOOST_CHECK( !sell_ptr );\n\n      // recheck\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // Sam tries to update BSRM\n      for( uint8_t i = 0; i <= 3; ++i )\n      {\n         if( static_cast<bsrm_type>(i) == bsrm_type::individual_settlement_to_order )\n            continue;\n         idump( (i) );\n         aubop.new_options = mpa_id(db).bitasset_data(db).options;\n         aubop.new_options.extensions.value.black_swan_response_method = i;\n         trx.operations.clear();\n         trx.operations.push_back( aubop );\n         PUSH_TX(db, trx, ~0);\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(i) );\n         if( i != 2 )\n         {\n            aubop.new_options.extensions.value.black_swan_response_method = bsrm_value;\n            trx.operations.clear();\n            trx.operations.push_back( aubop );\n            PUSH_TX(db, trx, ~0);\n            BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::individual_settlement_to_order );\n         }\n      }\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      // final check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(2) );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests scenarios:\n///   updating BSRM from no_settlement to others when the least collateralized short is actually undercollateralized\nBOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   using bsrm_type = bitasset_options::black_swan_response_type;\n   uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n   // Several passes, update BSRM from no_settlement to different values\n   for( uint8_t i = 0; i <= 3; ++i )\n   {\n      if( i == bsrm_value )\n         continue;\n      idump( (i) );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::no_settlement );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      ilog( \"Publish a new feed so that the least collateralized short is undercollateralized\" );\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n      BOOST_CHECK( db.find( call_id ) );\n      BOOST_CHECK( db.find( call2_id ) );\n\n      // Sam tries to update BSRM\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.black_swan_response_method = i;\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      const auto& check_result = [&]\n      {\n         switch( static_cast<bsrm_type>(i) )\n         {\n         case bsrm_type::global_settlement:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::global_settlement );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( !db.find( call2_id ) );\n            break;\n         case bsrm_type::individual_settlement_to_fund:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::individual_settlement_to_fund );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( db.find( call2_id ) );\n            break;\n         case bsrm_type::individual_settlement_to_order:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::individual_settlement_to_order );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( db.find( call2_id ) );\n            break;\n         default:\n            BOOST_FAIL( \"This should not happen\" );\n            break;\n         }\n      };\n\n      check_result();\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_CAPTURE_AND_RETHROW() }\n\n/// Tests scenarios:\n///   manually trigger global settlement via asset_global_settle_operation on each BSRM type\nBOOST_AUTO_TEST_CASE( manual_gs_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   using bsrm_type = bitasset_options::black_swan_response_type;\n\n   // Several passes, for each BSRM type, before and after core-2591 hf\n   for( uint8_t i = 0; i < 8; ++i )\n   {\n      uint8_t bsrm = i % 4;\n\n      idump( (i)(bsrm) );\n\n      if( 4 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n      ACTORS((sam)(feeder)(borrower)(borrower2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == static_cast<bsrm_type>(bsrm) );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      ilog( \"Publish a new feed so that the least collateralized short is undercollateralized\" );\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      const auto& check_result = [&]\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         switch( static_cast<bsrm_type>(bsrm) )\n         {\n         case bsrm_type::global_settlement:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::global_settlement );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n            // can not globally settle again\n            BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception );\n            break;\n         case bsrm_type::no_settlement:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::no_settlement );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( db.find( call_id ) );\n            BOOST_CHECK( db.find( call2_id ) );\n\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(1250,mpa_id), asset(20) ) );\n            // can not globally settle at real price since the least collateralized short's CR is too low\n            BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception );\n            break;\n         case bsrm_type::individual_settlement_to_fund:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::individual_settlement_to_fund );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( db.find( call2_id ) );\n\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n            // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n            // current feed = 100000:1983 * (1250-11):1000\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(123900,mpa_id), asset(1983) ) );\n            break;\n         case bsrm_type::individual_settlement_to_order:\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                         == bsrm_type::individual_settlement_to_order );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( db.find_settled_debt_order(mpa_id) );\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( db.find( call2_id ) );\n\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n            // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n            BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->for_sale.value, 1983 );\n            BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->amount_to_receive().amount.value, 100000 );\n            break;\n         default:\n            BOOST_FAIL( \"This should not happen\" );\n            break;\n         }\n      };\n\n      check_result();\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // publish a new feed (collateral price rises)\n      f.settlement_price = price( asset(1000,mpa_id), asset(15) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // globally settle\n      if( bsrm_type::no_settlement == static_cast<bsrm_type>(bsrm) )\n         force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(18) ) );\n      else if( bsrm_type::individual_settlement_to_fund == static_cast<bsrm_type>(bsrm)\n               || bsrm_type::individual_settlement_to_order == static_cast<bsrm_type>(bsrm) )\n         force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(22) ) );\n\n      // check\n      const auto& check_result2 = [&]\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method()\n                      == bsrm_type::global_settlement );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK( !db.find( call2_id ) );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n\n         switch( static_cast<bsrm_type>(bsrm) )\n         {\n         case bsrm_type::global_settlement:\n            break;\n         case bsrm_type::no_settlement:\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 3600 ); // 1800 * 2\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            break;\n         case bsrm_type::individual_settlement_to_fund:\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            break;\n         case bsrm_type::individual_settlement_to_order:\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            break;\n         default:\n            BOOST_FAIL( \"This should not happen\" );\n            break;\n         }\n      };\n\n      check_result2();\n\n      ilog( \"Generate a block\" );\n      generate_block();\n\n      check_result2();\n\n      // reset\n      db.pop_block();\n      db.pop_block();\n\n   } // for i\n\n} FC_CAPTURE_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/bsrm_indvd_settlement_tests.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture )\n\n/// Tests individual settlement (to order or fund) : how call orders are being processed when price drops\nBOOST_AUTO_TEST_CASE( individual_settlement_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // multiple passes,\n   // 0 : individual settlement to order, before hf core-2582\n   // 1, 2 : individual settlement to fund, before hf core-2582\n   // 3 : individual settlement to order, after hf core-2582\n   // 4, 5 : individual settlement to fund, after hf core-2582\n   // 6 : individual settlement to order, after hf core-2591\n   // 7, 8 : individual settlement to fund, after hf core-2591\n   for( int i = 0; i < 9; ++ i )\n   {\n      idump( (i) );\n\n      if( 3 == i )\n      {\n         // Advance to core-2582 hard fork\n         generate_blocks(HARDFORK_CORE_2582_TIME);\n         generate_block();\n      }\n      else if( 6 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2)(seller3)(seller4));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n      fund( borrower4, asset(init_amount) );\n      fund( borrower5, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = ( 0 == ( i % 3 ) ) ? static_cast<uint8_t>(bsrm_type::individual_settlement_to_order)\n                                              : static_cast<uint8_t>(bsrm_type::individual_settlement_to_fund);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      if( 0 == ( i % 3 ) )\n         BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                      == bsrm_type::individual_settlement_to_order );\n      else\n         BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                      == bsrm_type::individual_settlement_to_fund );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // 100000 / 2000 = 50\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // 100000 / 2100 = 47.619047619\n      // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // 100000 / 2200 = 45.454545455\n      // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // 100000 / 2500 = 40\n      // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000\n      const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) );\n      BOOST_REQUIRE( call4_ptr );\n      call_order_id_type call4_id = call4_ptr->get_id();\n\n      // 100000 / 2240 = 44.642857143\n      // undercollateralization price = 100000:2240 * 1250:1000 = 100000:1792\n      const call_order_object* call5_ptr = borrow( borrower5, asset(1000000, mpa_id), asset(22400) );\n      BOOST_REQUIRE( call5_ptr );\n      call_order_id_type call5_id = call5_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n      transfer( borrower3, seller, asset(100000,mpa_id) );\n      transfer( borrower4, seller2, asset(50000,mpa_id) );\n      transfer( borrower4, seller3, asset(50000,mpa_id) );\n      transfer( borrower5, seller4, asset(1000000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n      BOOST_CHECK_EQUAL( call5_id(db).debt.value, 1000000 );\n      BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 22400 );\n\n      BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n      BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n      BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 );\n      BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n      BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 300000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 50000 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 50000 );\n      BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 1000000 );\n      BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 0 );\n\n      // seller sells some\n      const limit_order_object* sell_low = create_sell_order( seller, asset(10000,mpa_id), asset(190) );\n      BOOST_REQUIRE( sell_low );\n      limit_order_id_type sell_low_id = sell_low->get_id();\n      BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 10000 );\n\n      // seller sells some\n      const limit_order_object* sell_mid = create_sell_order( seller, asset(100000,mpa_id), asset(2000) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100000 );\n\n      // seller4 sells some\n      const limit_order_object* sell_mid2 = create_sell_order( seller4, asset(20000,mpa_id), asset(439) );\n      BOOST_REQUIRE( sell_mid2 );\n      limit_order_id_type sell_mid2_id = sell_mid2->get_id();\n      BOOST_CHECK_EQUAL( sell_mid2_id(db).for_sale.value, 20000 );\n\n      // seller sells some\n      const limit_order_object* sell_high = create_sell_order( seller, asset(100000,mpa_id), asset(2400) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 );\n\n      // seller2 settles\n      auto result = force_settle( seller2, asset(50000,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find( settle_id ) );\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 50000 );\n\n      // seller3 settles\n      result = force_settle( seller3, asset(10000,mpa_id) );\n      force_settlement_id_type settle2_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find( settle2_id ) );\n      BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 10000 );\n\n      // check\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n      BOOST_CHECK_EQUAL( call5_id(db).debt.value, 1000000 );\n      BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 22400 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // 300000 - 10000 - 100000 - 100000\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 50000 - 50000\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40000 ); // 50000 - 10000\n      BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // 1000000 - 20000\n      BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that call, call2, call3 and call5 are undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1800) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444\n      // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789\n\n      auto check_result = [&]\n      {\n         // sell_low price is 10000/190 = 52.631578947\n         // call is matched with sell_low\n         // call pays price is (10000/190) * (1239/1250)\n         // sell_low is smaller thus fully filled\n         BOOST_CHECK( !db.find( sell_low_id ) );\n         // sell_low gets 190, pays 10000\n         // call gets 10000, pays round_down(190 * 1250/1239) = 191, margin call fee = 1\n         // call is now (100000-10000):(2000-191) = 90000:1809 = 49.751243781 (< 50)\n\n         // sell_mid price is 100000/2000 = 50\n         // call is matched with sell_mid\n         // call pays price is (100000/2000) * (1239/1250)\n         // call is smaller\n         // call gets 90000, pays round_up(90000 * (2000/100000) * (1250/1239)) = 1815\n         // 1815 > 1809, unable to fill\n\n         // call is matched with settle\n         // settle is smaller thus fully filled\n         BOOST_CHECK( !db.find( settle_id ) );\n         // unable to pay at MSSP\n         // call pays at its own price\n         // settle receives round_down(50000 * (1809/90000) * (1239/1250)) = 996\n         // settle pays round_up(996 * (90000/1809) * (1250/1239)) = 49993, refund 7\n         // call receives 49993\n         // call pays round_down(49993 * 1809/90000) = 1004, margin call fee = 1004 - 996 = 8\n         // call is now (90000-49993):(1809-1004) = 40007:805 = 49.698136646 (< 90000:1809)\n\n         // call is matched with sell_mid again\n         // call gets 40007, pays round_up(40007 * (2000/100000) * (1250/1239)) = 807\n         // 807 > 805, unable to fill\n\n         // call is matched with settle2\n         // settle2 is smaller thus fully filled\n         BOOST_CHECK( !db.find( settle2_id ) );\n         // unable to pay at MSSP\n         // call pays at its own price\n         // settle2 receives round_down(10000 * (805/40007) * (1239/1250)) = 199\n         // settle2 pays round_up(199 * (40007/805) * (1250/1239)) = 9978, refund 22\n         // call receives 9978\n         // call pays round_down(9978 * 805/40007) = 200, margin call fee = 200 - 199 = 1\n         // call is now (40007-9978):(805-200) = 30029:605 = 49.634710744 (< 40007:805)\n\n         // call is matched with sell_mid again\n         // call gets 30029, pays round_up(30029 * (2000/100000) * (1250/1239)) = 606\n         // 606 > 605, unable to fill\n\n         // no settle order\n         // call is individually settled\n         BOOST_CHECK( !db.find( call_id ) );\n         // fund gets round_up(605 * 1239/1250) = 600, margin call fee = 605 - 600 = 5\n         // fund debt = 30029\n\n         if( 0 == ( i % 3 ) ) // to order\n         {\n            // call2 is matched with sell_mid\n            // the size is the same, consider call2 as smaller\n            // call2 gets 100000, pays round_up(100000 * (2000/100000) * (1250/1239)) = 2018, margin call fee = 18\n            // 2018 < 2100, able to fill\n            BOOST_CHECK( !db.find( call2_id ) );\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n\n            // sell_mid2 price is 20000/439 = 45.55808656\n            // call pays price is (20000/439) * (1239/1250) = 45.157175399\n\n            // call3 is 100000/2200 = 45.454545455 (>45.157175399), so unable to fill\n            // call3 is individually settled\n            BOOST_CHECK( !db.find( call3_id ) );\n            // fund gets round_up(2200 * 1239/1250) = 2181, margin call fee = 2200 - 2181 = 19\n            // fund debt += 100000\n\n            // call5 is 1000000/22400 = 44.642857143 (<45.157175399)\n            // call5 is matched with sell_mid2\n            // sell_mid2 is smaller thus fully filled\n            BOOST_CHECK( !db.find( sell_mid2_id ) );\n            // sell_mid2 gets 439, pays 20000\n            // call5 gets 20000, pays round_down(439 * 1250/1239) = 442, margin call fee = 3\n            // call5 is now (1000000-20000):(22400-442) = 980000:21958 = 44.63065853 (> MSSP 44.444444444)\n\n            // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match)\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 );\n\n            // call5 is individually settled\n            BOOST_CHECK( !db.find( call5_id ) );\n            // fund gets round_up(21958 * 1239/1250) = 21765, margin call fee = 21958 - 21765 = 193\n            // fund debt += 980000\n\n            // call4 is not undercollateralized\n            BOOST_REQUIRE( db.find( call4_id ) );\n            BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n            // check\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id);\n            BOOST_REQUIRE( settled_debt );\n\n            BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 24546 ); // 600 + 2181 + 21765\n            BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 1110029 ); // 30029 + 100000 + 980000\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2018 ); // refund 82\n            BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 );\n            BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n            BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 );\n            BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22\n            BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 );\n            BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change\n            BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439\n         }\n         else // to fund\n         {\n            // sell_mid price is 100000/2000 = 50\n            // call pays price is (100000/2000) * (1239:1250) = 49.56\n\n            // median feed is 100000/1800 = 55.555555556\n            // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444\n            // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789\n\n            // fund collateral = 600\n            // fund debt = 30029\n            // current feed is capped at (30029:600) * (1239:1000) = 62.009885\n            // call pays price is (30029:600) * (1239:1250) = 49.607908\n            // call match price is 30029/600 = 50.048333333 (> sell_mid.price)\n\n            // call2 will not match with sell_mid\n            // call2 is individually settled\n            BOOST_CHECK( !db.find( call2_id ) );\n            // fund gets round_up(2100 * 1239/1250) = 2082, margin call fee = 2100 - 2082 = 18\n            // fund debt += 100000\n\n            // fund collateral = 600 + 2082 = 2682\n            // fund debt = 30029 + 100000 = 130029\n            // current feed is capped at (130029:2682) * (1239:1000) = 60.069325503\n            // call pays price is (130029:2682) * (1239:1250) = 48.055460403\n            // call match price is 130029/2682 = 48.482102908 (< sell_mid.price)\n\n            // call3 is matched with sell_mid\n            // the size is the same, consider call3 as smaller\n            // call3 gets 100000, pays round_up(100000 * (2000/100000) * (1250/1239)) = 2018, margin call fee = 18\n            // 2018 < 2200, able to fill\n            BOOST_CHECK( !db.find( call3_id ) );\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n\n            // sell_mid2 price is 20000/439 = 45.55808656\n            // call match price is 130029/2682 = 48.482102908 (> sell_mid2.price)\n\n            // call5 will not match with sell_mid2\n            // call5 is individually settled\n            BOOST_CHECK( !db.find( call5_id ) );\n            // fund gets round_up(22400 * 1239/1250) = 22203, margin call fee = 22400 - 22203 = 197\n            // fund debt += 1000000\n\n            // fund collateral = 600 + 2082 + 22203 = 24885\n            // fund debt = 30029 + 100000 + 100000 = 1130029\n            // current feed is capped at (1130029:24885) * (1239:1000) = 56.263047257\n            // call pays price is (1130029:24885) * (1239:1250) = 45.010437806\n            // call match price is 1130029/24885 = 45.410046213 (< sell_mid2.price)\n\n            // call4 is 100000:2500 = 40\n            // will be called if median_feed <= 100000:2500 * 1850:1000 = 74\n            // sell_mid2 is matched with call4\n            // sell_mid2 is smaller thus fully filled\n            BOOST_CHECK( !db.find( sell_mid2_id ) );\n            // sell_mid2 gets 439, pays 20000\n            // call4 gets 20000, pays round_down(439 * 1250/1239) = 442, margin call fee = 3\n            // call4 is now (100000-20000):(2500-442) = 80000:2058 = 38.872691934 (< MSSP 44.444444444)\n            // will be called if median_feed <= 80000:2058 * 1850:1000 = 71.914480078\n\n            // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match)\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 );\n\n            // call4 is not undercollateralized\n            BOOST_REQUIRE( db.find( call4_id ) );\n            BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80000 );\n            BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2058 );\n\n            // check\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 24885 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 1130029 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(1130029*1239,mpa_id), asset(24885*1000) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some\n            BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n            BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 );\n            BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22\n            BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 );\n            BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change\n            BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439\n         }\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      if( ( i >= 3 ) && ( 1 == ( i % 3 ) ) ) // additional tests, only pass after hf core-2582\n      {\n         set_expiration( db, trx );\n\n         // cancel sell_high\n         cancel_limit_order( sell_high_id(db) );\n\n         // publish a new feed so that call4 is undercollateralized\n         f.settlement_price = price( asset(80000,mpa_id), asset(2057) );\n         publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n         auto check_result_1 = [&]\n         {\n            BOOST_CHECK( !db.find( call4_id ) );\n         };\n\n         check_result_1();\n\n         BOOST_TEST_MESSAGE( \"Generate a block again\" );\n         generate_block();\n\n         check_result_1();\n\n         // reset\n         db.pop_block();\n      }\n      else if( ( i >= 3 ) && ( 2 == ( i % 3 ) ) ) // additional tests. NOTE: infinity loop and OOM before hf core-2582\n      {\n         set_expiration( db, trx );\n\n         // median feed is 100000/1800 = 55.555555556\n         // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444\n         // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789\n         // (1 / maintenance collateralization) is 100000/1800/1.85 = 30.03003003\n\n         // current feed is capped at (1130029:24885) * (1239:1000) = 56.263047257\n         // call pays price is (1130029:24885) * (1239:1250) = 45.010437806\n         // call match price is 1130029/24885 = 45.410046213\n         // fake (1 / maintenance collateralization) is (1130029/24885)*(1239/1000)/1.85 = 30.412457977\n\n         // borrower4 adds collateral to call4, and setup target CR\n         BOOST_TEST_MESSAGE( \"Borrower4 adds collateral\" );\n         borrow( borrower4_id(db), asset(0,mpa_id), asset(605), 1000 );\n         // call4 is now 80000:(2058+605) = 80000:2663 = 30.041306797\n         // Its CR is still below required MCR, but above the fake MCR (if calculate with the capped feed)\n\n         // seller4 sells some, this should be matched with call4\n         // due to TCR, both it and call4 won't be fully filled\n         BOOST_TEST_MESSAGE( \"Seller4 sells some\" );\n         const limit_order_object* sell_mid3 = create_sell_order( seller4, asset(20000,mpa_id), asset(439) );\n         BOOST_REQUIRE( sell_mid3 );\n         limit_order_id_type sell_mid3_id = sell_mid3->get_id();\n\n         auto check_result_2 = [&]\n         {\n            BOOST_REQUIRE( db.find( sell_mid3_id ) );\n            BOOST_CHECK_LT( sell_mid3_id(db).for_sale.value, 20000 );\n            BOOST_REQUIRE( db.find( call4_id ) );\n            BOOST_CHECK_LT( call4_id(db).debt.value, 80000 );\n         };\n\n         check_result_2();\n\n         BOOST_TEST_MESSAGE( \"Generate a block again\" );\n         generate_block();\n\n         check_result_2();\n\n         // reset\n         db.pop_block();\n      }\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests individual settlement to fund : if disable_force_settle flag is set,\n/// * able to settle if the fund is not empty,\n/// * and settle order is cancelled when the fund becomes empty\nBOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // two passes,\n   // i == 0 : with valid feed,\n   // i == 1 : no feed\n   for( int i = 0; i < 2; ++ i )\n   {\n      idump( (i) );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_fund);\n\n      // Create asset\n      asset_id_type samcoin_id = create_user_issued_asset( \"SAMCOIN\", sam_id(db), charge_market_fee,\n                                                           price(asset(1, asset_id_type(1)), asset(1)),\n                                                           2, 100 ).get_id(); // fee 1%\n      issue_uia( borrower, asset(init_amount, samcoin_id) );\n      issue_uia( borrower2, asset(init_amount, samcoin_id) );\n\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee | disable_force_settle;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 300;\n      acop.bitasset_opts->short_backing_asset = samcoin_id;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n      acop.bitasset_opts->extensions.value.force_settle_fee_percent = 300;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_fund );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1,samcoin_id) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000, samcoin_id) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2500, samcoin_id) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 0 );\n\n      // Unable to settle when the fund is empty and disable_force_settle is set\n      BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650,samcoin_id) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                   == price( asset(100000*1239,mpa_id), asset(1983*1000,samcoin_id) ) );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n      if( 1 == i ) // let the feed expire\n      {\n         generate_blocks( db.head_block_time() + fc::seconds(350) );\n         set_expiration( db, trx );\n\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n      }\n\n      // seller settles some : allowed when fund is not empty\n      auto result = force_settle( seller_id(db), asset(10000,mpa_id) );\n      auto op_result = result.get<extendable_operation_result>().value;\n      // seller gets round_down(10000*1983/100000) = 198, market fee 1, finally gets 197\n      // seller pays round_up(198*100000/1983) = 9985\n      BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement\n      BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n      BOOST_CHECK( *op_result.paid->begin() == asset( 9985, mpa_id ) );\n      BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n      BOOST_CHECK( *op_result.received->begin() == asset( 197, samcoin_id ) );\n      BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() );\n      BOOST_CHECK( *op_result.fees->begin() == asset( 1, samcoin_id ) );\n      // fund is now (100000-9985):(1983-198) = 90015:1785\n\n      // check\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 );\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      if( 0 == i )\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                      == price( asset(90015*1239,mpa_id), asset(1785*1000,samcoin_id) ) );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      }\n      else if( 1 == i )\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      }\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 190015 ); // 200000 - 9985\n      BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 197 );\n\n      // seller settles more, more than debt in the fund\n      result = force_settle( seller_id(db), asset(150000,mpa_id) );\n      op_result = result.get<extendable_operation_result>().value;\n\n      auto check_result = [&]\n      {\n         // seller gets 99041\n         // seller pays 1964\n         BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement\n         BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n         BOOST_CHECK( *op_result.paid->begin() == asset( 90015, mpa_id ) );\n         BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n         BOOST_CHECK( *op_result.received->begin() == asset( 1768, samcoin_id ) );\n         BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() );\n         BOOST_CHECK( *op_result.fees->begin() == asset( 17, samcoin_id ) );\n         // fund is now empty\n\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n         if( 0 == i )\n         {\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n         }\n         else if( 1 == i )\n         {\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n         }\n\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); // 200000 - 9985 - 90015\n         BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 1965 ); // 197 + 1768\n\n         // Unable to settle when the fund is empty and disable_force_settle is set\n         BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception );\n\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests individual settlement to fund : if there is no sufficient price feeds,\n/// * before core-2587 hard fork, cannot settle an amount more than the fund,\n/// * after core-2587 hard fork, can settle an amount more than the fund: only pay from the fund, no settle order.\nBOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_no_feed )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   {\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_fund);\n\n      // Create asset\n      asset_id_type samcoin_id = create_user_issued_asset( \"SAMCOIN\", sam_id(db), charge_market_fee,\n                                                           price(asset(1, asset_id_type(1)), asset(1)),\n                                                           2, 100 ).get_id(); // fee 1%\n      issue_uia( borrower, asset(init_amount, samcoin_id) );\n      issue_uia( borrower2, asset(init_amount, samcoin_id) );\n\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 300;\n      acop.bitasset_opts->short_backing_asset = samcoin_id;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n      acop.bitasset_opts->extensions.value.force_settle_fee_percent = 300;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_fund );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1,samcoin_id) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000, samcoin_id) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2500, samcoin_id) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650,samcoin_id) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                   == price( asset(100000*1239,mpa_id), asset(1983*1000,samcoin_id) ) );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n      // let the feed expire\n      {\n         generate_blocks( db.head_block_time() + fc::seconds(350) );\n         set_expiration( db, trx );\n\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n      }\n\n      // Before core-2587 hard fork, unable to settle more than the fund when no feed\n      BOOST_CHECK_THROW( force_settle( seller, asset(100001,mpa_id) ), fc::exception );\n\n      // Advance to core-2587 hard fork\n      generate_blocks( HARDFORK_CORE_2587_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      // able to settle more than the fund\n      auto result = force_settle( seller_id(db), asset(100001,mpa_id) );\n      auto op_result = result.get<extendable_operation_result>().value;\n\n      auto check_result = [&]\n      {\n         // seller gets 1983, market fee 19, finally gets 1964\n         // seller pays 100000\n         BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement\n         BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n         BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) );\n         BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n         BOOST_CHECK( *op_result.received->begin() == asset( 1964, samcoin_id ) );\n         BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() );\n         BOOST_CHECK( *op_result.fees->begin() == asset( 19, samcoin_id ) );\n         // fund is now empty\n\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); // 200000 - 100000\n         BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 1964 );\n\n         // Unable to settle when the fund is empty and no feed\n         BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception );\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n   }\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests individual settlement to fund : settles when price drops, and how taker orders would match after that\nBOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // multiple passes,\n   // i == 0 : settle more than the amount of debt in fund\n   // i == 1 : settle exactly the amount of debt in fund, before hf core-2582\n   // i == 2 : settle exactly the amount of debt in fund, after hf core-2582\n   for( int i = 0; i < 3; ++ i )\n   {\n      idump( (i) );\n\n      if( 2 == i )\n      {\n         // Advance to core-2582 hard fork\n         generate_blocks(HARDFORK_CORE_2582_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n      fund( borrower4, asset(init_amount) );\n      fund( borrower5, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_fund);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_fund );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000\n      const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) );\n      BOOST_REQUIRE( call4_ptr );\n      call_order_id_type call4_id = call4_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n      transfer( borrower3, seller2, asset(100000,mpa_id) );\n      transfer( borrower4, seller2, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                   == price( asset(100000*1239,mpa_id), asset(1983*1000) ) );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      // call pays price  (MSSP) = 100000:1983 * 1239:1250 = 49.984871407\n      // call match price (MCOP) = 100000:1983 = 50.428643469\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      // borrower5 is unable to borrow if CR <= real ICR\n      // for median_feed: 1650 * 1.9 = 3135\n      // for current_feed: 1983 * 1.9 / 1.239 = 3040.9\n      BOOST_CHECK_THROW( borrow( borrower5, asset(100000, mpa_id), asset(3135) ), fc::exception );\n      const call_order_object* call5_ptr = borrow( borrower5, asset(100000, mpa_id), asset(3136) );\n      BOOST_REQUIRE( call5_ptr );\n      call_order_id_type call5_id = call5_ptr->get_id();\n\n      BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 3136 );\n\n      // seller sells some\n      const limit_order_object* limit_ptr = create_sell_order( seller, asset(80000,mpa_id), asset(100) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n\n      // call2 is partially filled\n      // limit order gets round_down(80000*(1983/100000)) = 1586\n      // limit order pays round_up(1586*(100000/1983)) = 79980\n      // call2 gets 79980\n      // call2 pays round_down(79980*(1983/100000)*(1250/1239)) = 1600, margin call fee = 14\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); // 100000 - 79980\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); // 2100 - 1600\n      // 20020 / 500 = 40.04\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      // 100000 / 2200 = 45.454545455\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 120020 ); // 200000 - 79980\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // seller sells more, this order is below MCOP so will not be matched right now\n      limit_ptr = create_sell_order( seller, asset(100000,mpa_id), asset(2000) );\n      // the limit order is not filled\n      BOOST_REQUIRE( limit_ptr );\n      limit_order_id_type limit_id = limit_ptr->get_id();\n\n      BOOST_CHECK_EQUAL( limit_ptr->for_sale.value, 100000 );\n\n      // unable to settle too little amount\n      BOOST_CHECK_THROW( force_settle( seller2, asset(50,mpa_id) ), fc::exception );\n\n      // seller2 settles\n      share_type amount_to_settle = ( 0 == i ? 150000 : 100000 );\n      if( 1 == i ) // it will fail\n      {\n         BOOST_REQUIRE_THROW( force_settle( seller2, asset(amount_to_settle, mpa_id) ), fc::exception );\n         generate_block();\n         db.pop_block();\n         continue;\n      }\n      auto result = force_settle( seller2, asset(amount_to_settle, mpa_id) );\n      auto op_result = result.get<extendable_operation_result>().value;\n\n      auto check_result = [&]\n      {\n         // seller2 gets 1983\n         // seller2 pays 100000\n         force_settlement_id_type settle_id;\n         if( 0 == i )\n         {\n            BOOST_REQUIRE( op_result.new_objects.valid() ); // force settlement order created\n            settle_id = *op_result.new_objects->begin();\n         }\n         else if ( 2 == i )\n            BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n         BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n         BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) );\n         BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n         BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) );\n         // fund is now empty\n\n         // check\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n         // the individual settlement fund is now empty, so the price feed is no longer capped\n         // call3 is the least collateralized short, matched with the limit order, both filled\n         BOOST_CHECK( !db.find(call3_id) );\n         BOOST_CHECK( !db.find(limit_id) );\n         // same size, consider call3 as smaller\n         // call3 match price 100000:2000\n         // call3 gets 100000, pays round_up(2000 * 1250/1239) = 2018, margin call fee 18\n\n         if( 0 == i )\n         {\n            // settle order is matched with call2\n            // call2 is smaller\n            // call2 gets 20020, pays round_up(20020 * (1650/100000) * (1250/1000)) = 413\n            // settle order gets round_up(20020 * (1650/100000) * (1239/1000)) = 410, margin call fee = 3\n\n            // settle order is matched with call4\n            // settle order is smaller\n            BOOST_CHECK( !db.find(settle_id) );\n            // settle order gets round_down((50000-20020) * (1650/100000) * (1239/1000)) = 612\n            // settle order pays round_up(612 * (100000/1650) * (1000/1239)) = 29937\n            // call4 gets 29937\n            // call4 pays round_down(29937 * (1650/100000) * (1250/1000)) = 617, margin call fee = 5\n            // call4 is now (100000-29937):(2500-617) = 70063:1883\n            BOOST_CHECK_EQUAL( call4_id(db).debt.value, 70063 );\n            BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 1883 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 3586 ); // 1586 + 2000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 50043 ); // 200000 - 100000 - 20020 - 29937\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 3005 ); // 1983 + 410 + 612\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2013 ); // refund some\n            BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some\n            BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n            BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 3136 );\n         }\n         else if ( 2 == i )\n         {\n            // no change to other call orders\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 3586 ); // 1586 + 2000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 ); // 200000 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1983 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some\n            BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n            BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 3136 );\n         }\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests individual settlement to fund:\n/// * Before hf core-2591, forced-settlements are filled at individual settlement fund price\n/// * After hf core-2591, forced-settlements are filled at margin call order price (MCOP)\nBOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_price_test )\n{ try {\n\n   // Advance to a recent hard fork\n   generate_blocks(HARDFORK_CORE_2582_TIME);\n   generate_block();\n\n   // multiple passes,\n   // i == 0 : before hf core-2591, settle less than the amount of debt in fund\n   // i == 1 : before hf core-2591, settle exactly the amount of debt in fund\n   // i == 2 : before hf core-2591, settle more than the amount of debt in fund\n   // i == 3 : after hf core-2591, settle less than the amount of debt in fund\n   // i == 4 : after hf core-2591, settle exactly the amount of debt in fund\n   // i == 5 : after hf core-2591, settle more than the amount of debt in fund\n   for( int i = 0; i < 6; ++ i )\n   {\n      idump( (i) );\n\n      if( 3 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n      fund( borrower4, asset(init_amount) );\n      fund( borrower5, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_fund);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_fund );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000\n      const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) );\n      BOOST_REQUIRE( call4_ptr );\n      call_order_id_type call4_id = call4_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n      transfer( borrower3, seller2, asset(100000,mpa_id) );\n      transfer( borrower4, seller2, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n      BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 17 );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                   == price( asset(100000*1239,mpa_id), asset(1983*1000) ) );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      // call pays price  (MSSP) = 100000:1983 * 1239:1250 = 49.984871407\n      // call match price (MCOP) = 100000:1983 = 50.428643469\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      // borrower5 is unable to borrow if CR <= real ICR\n      // for median_feed: 1650 * 1.9 = 3135\n      // for current_feed: 1983 * 1.9 / 1.239 = 3040.9\n      BOOST_CHECK_THROW( borrow( borrower5, asset(100000, mpa_id), asset(3135) ), fc::exception );\n      const call_order_object* call5_ptr = borrow( borrower5, asset(100000, mpa_id), asset(3136) );\n      BOOST_REQUIRE( call5_ptr );\n      call_order_id_type call5_id = call5_ptr->get_id();\n\n      BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 3136 );\n\n      // seller sells some\n      const limit_order_object* limit_ptr = create_sell_order( seller, asset(80000,mpa_id), asset(100) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n\n      // call2 is partially filled\n      // limit order gets round_down(80000*(1983/100000)) = 1586\n      // limit order pays round_up(1586*(100000/1983)) = 79980\n      // call2 gets 79980\n      // call2 pays round_down(79980*(1983/100000)*(1250/1239)) = 1600, margin call fee = 14\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); // 100000 - 79980\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); // 2100 - 1600\n      // 20020 / 500 = 40.04\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      // 100000 / 2200 = 45.454545455\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 120020 ); // 200000 - 79980\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 31 ); // 17 + 14\n\n      // seller sells more, this order is below MCOP so will not be matched right now\n      limit_ptr = create_sell_order( seller, asset(100000,mpa_id), asset(2000) );\n      // the limit order is not filled\n      BOOST_REQUIRE( limit_ptr );\n      limit_order_id_type limit_id = limit_ptr->get_id();\n\n      BOOST_CHECK_EQUAL( limit_ptr->for_sale.value, 100000 );\n\n      // unable to settle too little amount\n      BOOST_CHECK_THROW( force_settle( seller2, asset(50,mpa_id) ), fc::exception );\n\n      // publish a new feed so that current_feed is no longer capped\n      f.settlement_price = price( asset(100000,mpa_id), asset(1450) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price  (MSSP) = 100000:1450 * 1000:1250 = 10000000:181250 = 55.172413793\n      // call match price (MCOP) = 100000:1450 * 1000:1239 = 10000000:179655 = 55.662241518\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n      const auto& get_amount_to_settle = [&]() {\n         switch(i) {\n         case 0:\n         case 3:\n            return 90000;\n         case 1:\n         case 4:\n            return 100000;\n         case 2:\n         case 5:\n         default:\n            return 110000;\n         }\n      };\n\n      // seller2 settles\n      share_type amount_to_settle = get_amount_to_settle();\n      auto result = force_settle( seller2, asset(amount_to_settle, mpa_id) );\n      auto op_result = result.get<extendable_operation_result>().value;\n\n      auto check_result = [&]\n      {\n         force_settlement_id_type settle_id;\n         if( 0 == i )\n         {\n            BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n            // receives = round_down(90000 * 1983 / 100000) = 1784\n            // pays = round_up(1784 * 100000 / 1983) = 89965\n            // settlement fund = 1983 - 1784 = 199\n            // settlement debt = 100000 - 89965 = 10035\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 89965, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 1784 ) );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 199 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 10035 );\n\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            // 20020 / 500 = 40.04\n            BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n            // 100000 / 2200 = 45.454545455\n\n            BOOST_REQUIRE( db.find(limit_id) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 31 ); // 17 + 14\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 110035 ); // 200000 - 89965\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1784 );\n         }\n         else if( 1 == i )\n         {\n            BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            // 20020 / 500 = 40.04\n            BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n            // 100000 / 2200 = 45.454545455\n\n            BOOST_REQUIRE( db.find(limit_id) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 31 ); // 17 + 14\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 ); // 200000 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1983 );\n         }\n         else if( 2 == i )\n         {\n            // force settlement order created\n            BOOST_REQUIRE( op_result.new_objects.valid() && 1U == op_result.new_objects->size() );\n            settle_id = *op_result.new_objects->begin();\n\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n            // settle order is matched with call3\n            // settle order is smaller\n            BOOST_CHECK( !db.find(settle_id) );\n            // settle order gets round_down((110000-100000) * (1450/100000) * (1239/1000)) = 179\n            // settle order pays round_up(179 * (100000/1450) * (1000/1239)) = 9964\n            // call3 gets 9964\n            // call3 pays round_down(9964 * (1450/100000) * (1250/1000)) = 180, margin call fee = 1\n            // call3 is now (100000-9964):(2200-180) = 90036:2020\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            // 20020 / 500 = 40.04\n            BOOST_CHECK_EQUAL( call3_id(db).debt.value, 90036 );\n            BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2020 );\n            // 90036 / 2020 = 44.572277228\n\n            BOOST_REQUIRE( db.find(limit_id) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 32 ); // 17 + 14 + 1\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 90036 ); // 200000 - 100000 - 9964\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 2162 ); // 1983 + 179\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 );\n            BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n            BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 3136 );\n         }\n         else if( 3 == i )\n         {\n            BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n            // settlement fund pays = round_down(90000 * 1983 / 100000) = 1784\n            // seller2 pays = round_up(1784 * 100000 / 1983) = 89965\n            // settlement fund = 1983 - 1784 = 199\n            // settlement debt = 100000 - 89965 = 10035\n            // seller2 would receive = round_up(89965 * 179655 / 10000000 ) = 1617 (<1784, so ok)\n            // collateral fee = 1784 - 1617 = 167\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 89965, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 1617 ) );\n            BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() );\n            BOOST_CHECK( *op_result.fees->begin() == asset( 167 ) );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 199 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 10035 );\n\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            // 20020 / 500 = 40.04\n            BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n            // 100000 / 2200 = 45.454545455\n\n            BOOST_REQUIRE( db.find(limit_id) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 198 ); // 17 + 14 + 167\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 110035 ); // 200000 - 89965\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1617 );\n         }\n         else if( 4 == i )\n         {\n            BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n            // settlement fund pays = 1983\n            // seller2 pays = 100000\n            // settlement fund = 0\n            // settlement debt = 0\n            // seller2 would receive = round_up(100000 * 179655 / 10000000 ) = 1797 (<1983, so ok)\n            // collateral fee = 1983 - 1797 = 186\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 1797 ) );\n            BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() );\n            BOOST_CHECK( *op_result.fees->begin() == asset( 186 ) );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            // 20020 / 500 = 40.04\n            BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n            // 100000 / 2200 = 45.454545455\n\n            BOOST_REQUIRE( db.find(limit_id) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 217 ); // 17 + 14 + 186\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 ); // 200000 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1797 );\n         }\n         else if( 5 == i )\n         {\n            // force settlement order created\n            BOOST_REQUIRE( op_result.new_objects.valid() && 1U == op_result.new_objects->size() );\n            settle_id = *op_result.new_objects->begin();\n\n            // settlement fund pays = 1983\n            // seller2 pays = 100000\n            // settlement fund = 0\n            // settlement debt = 0\n            // seller2 would receive = round_up(100000 * 179655 / 10000000 ) = 1797 (<1983, so ok)\n            // collateral fee = 1983 - 1797 = 186\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 1797 ) );\n            BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() );\n            BOOST_CHECK( *op_result.fees->begin() == asset( 186 ) );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 );\n\n            // settle order is matched with call3\n            // settle order is smaller\n            BOOST_CHECK( !db.find(settle_id) );\n            // settle order gets round_down((110000-100000) * (1450/100000) * (1239/1000)) = 179\n            // settle order pays round_up(179 * (100000/1450) * (1000/1239)) = 9964\n            // call3 gets 9964\n            // call3 pays round_down(9964 * (1450/100000) * (1250/1000)) = 180, margin call fee = 1\n            // call3 is now (100000-9964):(2200-180) = 90036:2020\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 );\n            // 20020 / 500 = 40.04\n            BOOST_CHECK_EQUAL( call3_id(db).debt.value, 90036 );\n            BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2020 );\n            // 90036 / 2020 = 44.572277228\n\n            BOOST_REQUIRE( db.find(limit_id) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 218 ); // 17 + 14 + 186 + 1\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 90036 ); // 200000 - 100000 - 9964\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1976 ); // 1797 + 179\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 );\n            BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 );\n            BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 3136 );\n         }\n\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests individual settlement to order : settles when price drops, and the settled-debt order is matched as maker\n/// * Before hf core-2591, the settled-debt order is filled at its own price (collateral amount / debt amount)\n/// * After hf core-2591, the settled-debt order is filled at margin call order price (MCOP)\nBOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_matching_as_maker_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   // multiple passes,\n   // i == 0 : before hf core-2591\n   // i == 1 : after hf core-2591\n   for( int i = 0; i < 2; ++i )\n   {\n      idump( (i) );\n\n      if( 1 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n      fund( borrower4, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_order);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_order );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000\n      const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) );\n      BOOST_REQUIRE( call4_ptr );\n      call_order_id_type call4_id = call4_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n      transfer( borrower3, seller, asset(100000,mpa_id) );\n      transfer( borrower4, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 400000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 );\n      BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) );\n      // order match price = 100000 / 1983 = 50.428643469\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 );\n\n      // seller sells some\n      const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n\n      // call2 is partially filled\n      // limit order gets round_down(10000*(1650/100000)*(1239/1000)) = 204\n      // limit order pays round_up(204*(100000/1650)*(1000/1239)) = 9979\n      // call2 gets 9979\n      // call2 pays round_down(9979*(1650/100000)*(1250/1000)) = 205, margin call fee = 1\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90021 ); // 100000 - 9979\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1895 ); // 2100 - 205\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      // no change to the settled-debt order\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 );\n      BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 390021 ); // 400000 - 9979\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 204 );\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 18 ); // 17 + 1\n\n      // publish a new feed so that 2 other debt positions are undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1800) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444\n      // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK( !db.find( call2_id ) );\n      BOOST_CHECK( !db.find( call3_id ) );\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      // call2: margin call fee deducted = round_down(1895*11/1250) = 16,\n      // fund receives 1895 - 16 = 1879\n      // call3: margin call fee deducted = round_down(2200*11/1250) = 19,\n      // fund receives 2200 - 19 = 2181\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // 1983 + 1879 + 2181\n      BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // 100000 + 90021 + 100000\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n      BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) );\n      // order match price = 290021 / 6043 = 47.992884329\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 53 ); // 17 + 1 + 16 + 19\n\n      // borrower buys at higher price\n      const limit_order_object* buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) );\n      BOOST_CHECK( buy_high );\n      limit_order_id_type buy_high_id = buy_high->get_id();\n\n      // seller sells some, this will match buy_high,\n      // and when it matches call4, it will be cancelled since it is too small\n      limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n      // buy_high is filled\n      BOOST_CHECK( !db.find( buy_high_id ) );\n\n      BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n      // no change to the settled-debt order\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 );\n      BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n      BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389921 ); // 400000 - 9979 - 100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 214 ); // 204 + 10\n\n      // publish a new feed so that\n      // * before hf core-2591, the settled debt order is in the front of the order book\n      // * after hf core-2591, the settled debt order is updated to be behind the margin call orders\n      f.settlement_price = price( asset(100000,mpa_id), asset(1600) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1600 * 1000:1250 = 100000:2000 = 50\n      // call match price = 100000:1600 * 1000:1239 = 100000:1982.4 = 50.443906376\n\n      if( 0 == i )\n      {\n         // no change to the settled-debt order\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 );\n         BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n         BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) );\n      }\n      else if( 1 == i )\n      {\n         // the settled-debt order is updated\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5750 ); // round_up(290021 * 19824 / 1000000)\n         BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290052 ); //round_down(5750*1000000/19824)\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n         BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) );\n      }\n\n      // borrower buys at higher price\n      buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) );\n      BOOST_CHECK( buy_high );\n      buy_high_id = buy_high->get_id();\n\n      // seller sells some, this will match buy_high, then\n      // * before hf core-2591, when it matches the settled debt, it will be cancelled since it is too small\n      // * after hf core-2591, when it matches a call order, it will be cancelled since it is too small\n      limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n      // buy_high is filled\n      BOOST_CHECK( !db.find( buy_high_id ) );\n\n      if( 0 == i )\n      {\n         // no change to the settled-debt order\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 );\n         BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n         BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) );\n      }\n      else if( 1 == i )\n      {\n         // no change to the settled-debt order\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5750 );\n         BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290052 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n         BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) );\n      }\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389821 ); // 400000 - 9979 - 100 - 100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 224 ); // 204 + 10 + 10\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 53 ); // 17 + 1 + 16 + 19\n\n      // seller sells some\n      limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n\n      if( 0 == i )\n      {\n         // the settled debt is partially filled\n         // limit order receives = round_down(10000*6043/290021) = 208\n         // settled debt receives = round_up(208*290021/6043) = 9983\n\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK( !db.find( call2_id ) );\n         BOOST_CHECK( !db.find( call3_id ) );\n         BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 );\n\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5835 ); // 6043 - 208\n         BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 280038 ); // 290021 - 9983\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 280038 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 5835 );\n         BOOST_CHECK( settled_debt->sell_price == asset(5835)/asset(280038,mpa_id) );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379838 ); // 400000 - 9979 - 100 - 100 - 9983\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 432 ); // 204 + 10 + 10 + 208\n\n         BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 53 ); // no change\n      }\n      else if( 1 == i )\n      {\n         // call4 is partially filled\n         // limit order gets round_down(10000*(1600/100000)*(1239/1000)) = 198\n         // limit order pays round_up(198*(100000/1600)*(1000/1239)) = 9988\n         // call4 gets 9988\n         // call4 pays round_down(9988*(1600/100000)*(1250/1000)) = 199, margin call fee = 1\n\n         BOOST_CHECK( !db.find( call_id ) );\n         BOOST_CHECK( !db.find( call2_id ) );\n         BOOST_CHECK( !db.find( call3_id ) );\n         BOOST_CHECK_EQUAL( call4_id(db).debt.value, 90012 ); // 100000 - 9988\n         BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2301 ); // 2500 - 199\n\n         // no change to the settled-debt order\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5750 );\n         BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290052 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 );\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 );\n         BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379833 ); // 400000 - 9979 - 100 - 100 - 9988\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 422 ); // 204 + 10 + 10 + 198\n\n         BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 54 ); // 53 + 1\n      }\n\n      // seller sells some\n      limit_ptr = create_sell_order( seller, asset(300000,mpa_id), asset(3000) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n\n      auto check_result = [&]\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n\n         if( 0 == i )\n         {\n            // the settled debt is fully filled\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n            // limit order reminder = 300000 - 280038 = 19962\n            // call4 is partially filled\n            // limit order gets round_down(19962*(1600/100000)*(1239/1000)) = 395\n            // limit order pays round_up(395*(100000/1600)*(1000/1239)) = 19926\n            // call4 gets 19926\n            // call4 pays round_down(19926*(1600/100000)*(1250/1000)) = 398, margin call fee = 3\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79874 ); // 400000 - 9979 - 100 - 100 - 9983\n                                                                          // - 280038 - 19926\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6662 ); // 204 + 10 + 10 + 208 + 5835 + 395\n\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( !db.find( call2_id ) );\n            BOOST_CHECK( !db.find( call3_id ) );\n            BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80074 ); // 100000 - 19926\n            BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2102 ); // 2500 - 398\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 56 ); // 53 + 3\n         }\n         else if( 1 == i )\n         {\n            // call4 is fully filled\n            BOOST_CHECK( !db.find( call_id ) );\n            BOOST_CHECK( !db.find( call2_id ) );\n            BOOST_CHECK( !db.find( call3_id ) );\n            BOOST_CHECK( !db.find( call4_id ) );\n            // call4 gets 90012\n            // limit order gets round_up(90012*(1600/100000)*(1239/1000)) = 1785\n            // call4 pays round_up(90012*(1600/100000)*(1250/1000)) = 1801, margin call fee = 1801 - 1785 = 16\n\n            // limit order reminder = 300000 - 90012 = 209988\n            // the settled debt is partially filled\n            // limit order receives = round_down(209988*19824/1000000) = 4162\n            // settled debt receives = round_up(4162*1000000/19824) = 209948\n            // settled debt pays = round_down(209948*6043/290021) = 4374, collateral fee = 4374 - 4162 = 212\n\n            settled_debt = db.find_settled_debt_order(mpa_id);\n            BOOST_REQUIRE( settled_debt );\n            BOOST_CHECK( settled_debt->is_settled_debt );\n            BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1588 ); // round_up( 80073 * 19824 / 1000000 )\n            BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 80104 ); //rnd_down(1588*1000000/19824)\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 80073 ); // 290021\n                                                                                                       // - 209948\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1669 ); // 6043 - 4374\n            BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79873 ); // 400000 - 9979 - 100 - 100 - 9988\n                                                                          // - 90012 - 209948\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6369 ); // 204 + 10 + 10 + 198 + 1785 + 4162\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 282 ); // 54 + 16 + 212\n         }\n\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      if( 1 == i )\n      {\n\n         // undercollateralization price = 100000:5000 * 1250:1000 = 100000:4000\n         const call_order_object* call5_ptr = borrow( borrower4_id(db), asset(100000, mpa_id), asset(5000) );\n         BOOST_REQUIRE( call5_ptr );\n         call_order_id_type call5_id = call5_ptr->get_id();\n\n         BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 5000 );\n\n         transfer( borrower4_id(db), seller_id(db), asset(100000,mpa_id) );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 179873 ); // 79873 + 100000\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6369 ); // no change\n\n         // seller sells some\n         limit_ptr = create_sell_order( seller_id(db), asset(100000,mpa_id), asset(1000) );\n         // the limit order is partially filled\n         BOOST_REQUIRE( limit_ptr );\n         limit_order_id_type limit_id = limit_ptr->get_id();\n\n         auto check_result_1 = [&]\n         {\n            // the settled-debt order is fully filled\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n            // settled debt receives = 80073\n            // limit order receives = round_up(80073*19824/1000000) = 1588\n            // settled debt pays = 1669, collateral fee = 1669 - 1588 = 81\n\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 19927 ); // 100000 - 80073\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79873 ); // 179873 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 7957 ); // 6369 + 1588\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 363 ); // 282 + 81\n         };\n\n         check_result_1();\n\n         BOOST_TEST_MESSAGE( \"Generate a new block\" );\n         generate_block();\n\n         check_result_1();\n\n         // reset\n         db.pop_block();\n      }\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests individual settlement to order :\n///   after hf core-2591, the settled-debt order is matched as taker when price feed is updated\nBOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_matching_as_taker_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   // multiple passes,\n   // i == 0 : before hf core-2591\n   // i >= 1 : after hf core-2591\n   for( int i = 0; i < 6; ++i )\n   {\n      idump( (i) );\n\n      if( 1 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_order);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_order );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 );\n      BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) );\n      // order match price = 100000 / 1983 = 50.428643469\n\n      BOOST_CHECK( !db.find( call_id ) );\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 );\n\n      // seller sells some\n      const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) );\n      // the limit order is filled\n      BOOST_CHECK( !limit_ptr );\n\n      // the settled debt is partially filled\n      // limit order receives = round_down(10000*1983/100000) = 198\n      // settled debt receives = round_up(198*100000/1983) = 9985\n      // settled debt pays = 198, collateral fee = 0\n\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1785 ); // 1983 - 198\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); // 100000 - 9985\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 );\n      if( 0 == i )\n         BOOST_CHECK( settled_debt->sell_price == asset(1785)/asset(90015,mpa_id) );\n      else\n         BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90015 ); // 100000 - 9985\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 198 );\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 );\n\n      // publish a new feed (collateral price rises)\n      f.settlement_price = price( asset(200,mpa_id), asset(1) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 200:1 * 1000:1250 = 200000:1250 = 160\n      // call match price = 200:1 * 1000:1239 = 200000:1239 = 161.420500404\n\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      if( 0 == i )\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1785 );\n      else\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 558 ); // round_up( 90015 * 1239 / 200000 )\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 );\n      if( 0 == i )\n         BOOST_CHECK( settled_debt->sell_price == asset(1785)/asset(90015,mpa_id) );\n      else\n         BOOST_CHECK( settled_debt->sell_price == asset(1239)/asset(200000,mpa_id) );\n\n      // seller sells some\n      limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(150) );\n      if( 0 == i )\n      {\n         // the limit order is filled\n         BOOST_CHECK( !limit_ptr );\n\n         // the settled debt is partially filled\n         // limit order receives = round_down(10000*1785/90015) = 198\n         // settled debt receives = round_up(198*90015/1785) = 9985\n         // settled debt pays = 198, collateral fee = 0\n         settled_debt = db.find_settled_debt_order(mpa_id);\n         BOOST_REQUIRE( settled_debt );\n         BOOST_CHECK( settled_debt->is_settled_debt );\n         BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1587 ); // 1983 - 198 - 198\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 80030 ); // 100000 - 9985\n                                                                                                    // - 9985\n         BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1587 );\n         BOOST_CHECK( settled_debt->sell_price == asset(1587)/asset(80030,mpa_id) );\n\n         BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 );\n\n         BOOST_TEST_MESSAGE( \"Generate a block\" );\n         generate_block();\n\n         // reset\n         db.pop_block();\n         // this branch ends here\n         continue;\n      }\n\n      // the limit order is not filled\n      BOOST_REQUIRE( limit_ptr );\n      limit_order_id_type limit_id = limit_ptr->get_id();\n\n      BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 10000 );\n\n      // the settled-debt order is unchanged\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n      BOOST_CHECK( settled_debt->is_settled_debt );\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 558 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 );\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 );\n      BOOST_CHECK( settled_debt->sell_price == asset(1239)/asset(200000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 198 );\n\n      BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 );\n\n      call_order_id_type call2_id;\n      limit_order_id_type limit2_id;\n      if( 1 == i )\n      {\n         // do nothing here so that there is no call order exists\n         // so the settled-debt order will match the limit order on the next price feed update\n      }\n      if( 2 == i )\n      {\n         // create a small call order that will go undercollateralized on the next price feed update\n         // so the settled-debt order after merged the new call order will still be well collateralized\n         // and will match the limit order\n         // undercollateralization price = 10000:100 * 1250:1000 = 100000:800\n         const call_order_object* call2_ptr = borrow( borrower2, asset(10000, mpa_id), asset(100) );\n         BOOST_REQUIRE( call2_ptr );\n         call2_id = call2_ptr->get_id();\n\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 10000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 100 );\n      }\n      else if( 3 == i )\n      {\n         // create a huge call order that will go undercollateralized on the next price feed update\n         // so the settled-debt order after merged the new call order will be undercollateralized too\n         // and will not match the limit order\n         // undercollateralization price = 1000000:10000 * 1250:1000 = 100000:800\n         const call_order_object* call2_ptr = borrow( borrower2, asset(1000000, mpa_id), asset(10000) );\n         BOOST_REQUIRE( call2_ptr );\n         call2_id = call2_ptr->get_id();\n\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 10000 );\n      }\n      else if( 4 == i )\n      {\n         // create a big call order that will be margin called on the next price feed update\n         // so the settled-debt order will have no limit order to match with\n         // undercollateralization price = 100000:2400 * 1250:1000 = 100000:1920\n         const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2400) );\n         BOOST_REQUIRE( call2_ptr );\n         call2_id = call2_ptr->get_id();\n\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2400 );\n      }\n      else if( 5 == i )\n      {\n         // create a big call order that will not be margin called on the next price feed update\n         // so the settled-debt order will match the limit order\n         // undercollateralization price = 100000:5000 * 1250:1000 = 100000:4000\n         const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(5000) );\n         BOOST_REQUIRE( call2_ptr );\n         call2_id = call2_ptr->get_id();\n\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 5000 );\n\n         // Transfer funds to sellers\n         transfer( borrower2, seller2, asset(100000,mpa_id) );\n\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 );\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n         // seller2 sells some\n         const limit_order_object* limit2_ptr = create_sell_order( seller2, asset(100000,mpa_id), asset(1550) );\n         BOOST_REQUIRE( limit2_ptr );\n         limit2_id = limit2_ptr->get_id();\n\n         BOOST_CHECK_EQUAL( limit2_id(db).for_sale.value, 100000 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 100000 - 100000\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n      }\n\n      // publish a new feed (collateral price drops)\n      f.settlement_price = price( asset(100000,mpa_id), asset(1350) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price  (MSSP) = 100000:1350 * 1000:1250 = 100000:1687.5 = 59.259259259\n      // call match price (MCOP) = 100000:1350 * 1000:1239 = 100000:1672.65 = 59.78537052\n\n      auto check_result = [&]\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n\n         // the settled-debt order was:\n         // settled_debt_amount = 90015\n         // settled_collateral_amount = 1785\n\n         // the limit order was selling 10000 MPA for 150 CORE\n\n         if( 1 == i )\n         {\n            // the settled-debt order is matched with the limit order\n            // the limit order is fully filled\n            BOOST_CHECK( !db.find( limit_id ) );\n\n            // the settled-debt order is partially filled, match price is 10000:150\n            // limit order receives = 150\n            // settled debt receives = 10000\n            // settled debt pays = round_down(10000*1785/90015) = 198, collateral fee = 198 - 150 = 48\n\n            settled_debt = db.find_settled_debt_order(mpa_id);\n            BOOST_REQUIRE( settled_debt );\n            BOOST_CHECK( settled_debt->is_settled_debt );\n            BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1339 ); // round_up( 80015 * 167265 / 10000000 )\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 80015 ); //90015 - 10000\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1587 ); // 1785 - 198\n            BOOST_CHECK( settled_debt->sell_price == asset(167265)/asset(10000000,mpa_id) );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 65 ); // 17 + 48\n         }\n         else if( 2 == i )\n         {\n            // call2 is individually settled\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // margin call fee deducted = round_down(100*11/1250) = 0,\n            // fund receives 100, collateral = 1785 + 100 = 1885\n            // fund debt = 90015 + 10000 = 100015\n            // fund price = 100015 / 2785 = 53.058355438 < MCOP 59.78537052\n\n            // the settled-debt order is matched with the limit order\n            // the limit order is fully filled\n            BOOST_CHECK( !db.find( limit_id ) );\n\n            // the settled-debt order is partially filled, match price is 10000:150\n            // limit order receives = 150\n            // settled debt receives = 10000\n            // settled debt pays = round_down(10000*1885/100015) = 188, collateral fee = 188 - 150 = 38\n\n            settled_debt = db.find_settled_debt_order(mpa_id);\n            BOOST_REQUIRE( settled_debt );\n            BOOST_CHECK( settled_debt->is_settled_debt );\n            BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1506 ); // round_up( 90015 * 167265 / 10000000 )\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); //90015 - 10000\n                                                                                                       // + 10000\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1697 ); // 1785 + 100\n                                                                                                      // - 188\n            BOOST_CHECK( settled_debt->sell_price == asset(167265)/asset(10000000,mpa_id) );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 55 ); // 17 + 38\n         }\n         else if( 3 == i )\n         {\n            // call2 is individually settled\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // margin call fee deducted = round_down(10000*11/1250) = 88,\n            // fund receives 10000 - 88 = 9912, collateral = 1785 + 9912 = 11697\n            // fund debt = 90015 + 1000000 = 1090015\n            // fund price = 1090015 / 11697 = 93.187569462 > MCOP 59.78537052\n\n            // the settled-debt order can't be matched with the limit order\n\n            BOOST_REQUIRE( db.find( limit_id ) );\n            BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 10000 ); // no change\n\n            settled_debt = db.find_settled_debt_order(mpa_id);\n            BOOST_REQUIRE( settled_debt );\n            BOOST_CHECK( settled_debt->is_settled_debt );\n            BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 11697 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 1090015 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 11697 );\n            BOOST_CHECK( settled_debt->sell_price == asset(11697)/asset(1090015,mpa_id) );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // no change\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 198 ); // no change\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 105 ); // 17 + 88\n         }\n         else if( 4 == i )\n         {\n            // call2 is margin called, matched with the limit order\n            // the limit order is fully filled\n            BOOST_CHECK( !db.find( limit_id ) );\n\n            // call2 is partially filled\n            // limit order receives = 150\n            // call2 receives = 10000\n            // margin call fee = round_down(150*11/1250) = 1\n            // call2 pays 150 + 1 = 151\n\n            BOOST_REQUIRE( db.find( call2_id ) );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90000 ); // 100000 - 10000\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2249 ); // 2400 - 151\n\n            // the settled-debt order is not matched\n\n            settled_debt = db.find_settled_debt_order(mpa_id);\n            BOOST_REQUIRE( settled_debt );\n            BOOST_CHECK( settled_debt->is_settled_debt );\n            BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1506 ); // round_up( 90015 * 167265 / 10000000 )\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 );\n            BOOST_CHECK( settled_debt->sell_price == asset(167265)/asset(10000000,mpa_id) );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 18 ); // 17 + 1\n         }\n         else if( 5 == i )\n         {\n            // call2 is unchanged\n            BOOST_REQUIRE( db.find( call2_id ) );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 5000 );\n\n            // the settled-debt order is matched with the limit order\n            // the limit order is fully filled\n            BOOST_CHECK( !db.find( limit_id ) );\n\n            // the settled-debt order is partially filled, match price is 10000:150\n            // limit order receives = 150\n            // settled debt receives = 10000, settled_debt = 90015 - 10000 = 80015\n            // settled debt pays = round_down(10000*1785/90015) = 198, collateral fee = 198 - 150 = 48\n            // settled_collateral = 1785 - 198 = 1587\n\n            // then, the settled-debt order is matched with limit2\n            // the settled-debt order is fully filled, match price is 10000:155\n            // settled debt receives = 80015\n            // limit2 receives = round_up(80015*155/10000) = 1241\n            // settled debt pays = 1587, collateral fee = 1587 - 1241 = 346\n\n            BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n            BOOST_CHECK_EQUAL( limit2_id(db).for_sale.value, 19985 ); // 100000 - 80015\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 100000 - 100000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1241 );\n\n            BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 411 ); // 17 + 48 + 346\n         }\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests a scenario that force settlements get cancelled on expiration when there is no debt position\n/// due to individual settlement to order\nBOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position )\n{ try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::individual_settlement_to_order);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 86400;\n      acop.bitasset_opts->force_settlement_delay_sec = 600;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::individual_settlement_to_order );\n\n      acop.symbol = \"SAMMPA2\";\n      acop.bitasset_opts->force_settlement_delay_sec = 60000;\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa2 = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa2_id = mpa2.get_id();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n      update_feed_producers( mpa2_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      price_feed f2;\n      f2.settlement_price = price( asset(100,mpa2_id), asset(1) );\n      f2.core_exchange_rate = price( asset(100,mpa2_id), asset(1) );\n      f2.maintenance_collateral_ratio = 1850;\n      f2.maximum_short_squeeze_ratio = 1250;\n\n      publish_feed( mpa2_id, feeder_id, f2, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680\n      const call_order_object* call2_ptr = borrow( borrower, asset(100000, mpa2_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower, seller, asset(100000,mpa2_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(100000,mpa_id), asset(1650) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n      // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485\n      // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n\n      // call: margin call fee deducted = round_down(2000*11/1250) = 17,\n      // fund receives 2000 - 17 = 1983\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 );\n      BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 );\n      // order match price = 100000 / 1983 = 50.428643469\n\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(11100,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      result = force_settle( seller, asset(11100,mpa2_id) );\n      force_settlement_id_type settle2_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find(settle2_id) );\n\n      BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // let the first settle order expire\n      generate_blocks( db.head_block_time() + fc::seconds(600) );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      settled_debt = db.find_settled_debt_order(mpa_id);\n      BOOST_REQUIRE( settled_debt );\n\n      BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); // no change\n      BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 );\n\n      // the first settle order is cancelled\n      BOOST_REQUIRE( !db.find(settle_id) );\n\n      // no change to the second settle order\n      BOOST_REQUIRE( db.find(settle2_id) );\n      BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/bsrm_no_settlement_tests.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture )\n\n/// Tests margin calls when BSRM is no_settlement and call order is maker\nBOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrowers borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(10,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed\n      // 1000 * (2000/1250) * 1.9 = 3040\n      // 1000 * (22/10) * 1.9 = 4180\n      BOOST_CHECK_THROW( borrow( borrower3, asset(1000, mpa_id), asset(4180) ), fc::exception );\n      // borrower3 create debt position right above ICR\n      const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // borrower adjust debt position to right at MSSR\n      // 1000 * (22/10) * 1.25 = 2750\n      borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR<MSSR\n      borrow( borrower, asset(0, mpa_id), asset(749) );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Sam update MSSR and MCFR\n      // note: borrower's position is undercollateralized again due to the mssr change\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300;\n      aubop.new_options.extensions.value.margin_call_fee_ratio = 1;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(1000,mpa_id) );\n      transfer( borrower2, seller, asset(1000,mpa_id) );\n      transfer( borrower3, seller, asset(500,mpa_id) );\n      transfer( borrower3, seller2, asset(500,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later\n      const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n\n      // seller2 sells more, this order won't be filled in the beginning either\n      const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n\n      // seller2 sells more, this order won't be filled\n      const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) );\n      BOOST_REQUIRE( sell_highest );\n      limit_order_id_type sell_highest_id = sell_highest->get_id();\n      BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // 500 - 100 - 100 - 100\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // seller sells some, this order will be filled\n      const limit_order_object* sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) );\n      BOOST_CHECK( !sell_low );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300)\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price\n                   == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100)\n                                                                    // 13:10 * 889:1867\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller sells more\n      sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) );\n      BOOST_CHECK( !sell_low );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000\n      // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300)\n      // 232 + 1866 + 305\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 );\n      // now feed price is 13:10 * (1000-111):(2750-111*275/100)\n      //                 = 13:10 * 889:2445 = 11557:24450\n      // call order match price is 1300:1299 * 889:2445 = 0.363879089\n      // sell_mid's price is 100/210 = 0.47619048\n\n      // sell_mid got filled too\n      BOOST_CHECK( !db.find( sell_mid_id ) );\n\n      // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price\n      // call pays round_down(210*1300/1299) = 210, fee = 0\n      // now feed price is 13:10 * (889-100):(2445-210)\n      //                 = 13:10 * 789:2235 = 10257:22350\n      // call order match price is 1300:1299 * 789:2235 = 0.353291897\n      // sell_high's price is 100/275 = 0.363636364\n\n      // sell_high got filled too\n      BOOST_CHECK( !db.find( sell_high_id ) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 );\n      // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price\n      // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275\n      // call pays round_down(275*1300/1299) = 275, fee = 0\n      // now feed price is 13:10 * (789-100):(2235-275)\n      //                 = 13:10 * 689:1960 = 8957:19600 (>10/22)\n      // call order match price is 1300:1299 * 689:1960 = 0.351801229\n      // sell_highest's price is 100/285 = 0.350877193\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price\n                   == price( asset(8957,mpa_id), asset(19600) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1960 );\n      BOOST_CHECK( !db.find(call2_id) );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller sells more\n      sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) );\n      BOOST_REQUIRE( sell_low );\n      limit_order_id_type sell_low_id = sell_low->get_id();\n\n      auto final_check = [&]\n      {\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 311 );\n         BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000\n         // 2403 + round_up(689*(19600/8957)*(1299/1000))\n         // 2403 + 1959\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 );\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change\n\n         // check\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n         BOOST_CHECK( !db.find(call_id) );\n         BOOST_CHECK( !db.find(call2_id) );\n         BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n         BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n      };\n\n      final_check();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      final_check();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests margin calls when BSRM is no_settlement and call order is maker and taker limit order is too small to fill\nBOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrowers borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed\n      // 100000 * (2000/125000) * 1.9 = 3040\n      // 100000 * (22/1000) * 1.9 = 4180\n      BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception );\n      // borrower3 create debt position right above ICR\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // borrower adjust debt position to right at MSSR\n      // 100000 * (22/1000) * 1.25 = 2750\n      borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR<MSSR\n      borrow( borrower, asset(0, mpa_id), asset(749) );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Sam update MSSR and MCFR\n      // note: borrower's position is undercollateralized again due to the mssr change\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300;\n      aubop.new_options.extensions.value.margin_call_fee_ratio = 1;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n      transfer( borrower3, seller, asset(50000,mpa_id) );\n      transfer( borrower3, seller2, asset(50000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later\n      const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 );\n\n      // seller2 sells more, this order won't be filled in the beginning either\n      const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n\n      // seller2 sells more, this order won't be filled\n      const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) );\n      BOOST_REQUIRE( sell_highest );\n      limit_order_id_type sell_highest_id = sell_highest->get_id();\n      BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 250000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // seller sells some, this order will be filled\n      const limit_order_object* sell_low = create_sell_order( seller, asset(11100,mpa_id), asset(210) );\n      BOOST_CHECK( !sell_low );\n\n      // call2 pays price = 210/10000\n      // call2 match price = (210/10000) * (1299/1300) = 27279/1300000\n      // sell_low receives = round_down(11100 * 27279/1300000)) = 232\n      // sell_low pays = round_up(232 * 1300000/27279) = 11057, the rest is cancelled\n      // call2 receives = 11057\n      // call2 pays = round_down(11057 * 210/10000) = 232, margin call fee = 0\n      // now feed price = 13:10 * (100000-11057):(2100-232) = 13:10 * 88943:1868 = 1156259:18680\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 238943 ); // 250000 - 11057\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price\n                   == price( asset(1156259,mpa_id), asset(18680) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 88943 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1868 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller sells more\n      sell_low = create_sell_order( seller, asset(100000,mpa_id), asset(100) );\n\n      // call2 is fully filled\n      BOOST_CHECK( !db.find(call2_id) );\n      // call2 pays 1868\n      // call2 pays price = 1868/88943\n      // call2 match price = (1868/88943) * (1299/1300) = 2426532/115625900\n      // sell_low pays 88943\n      // sell_low receives = round_up(88943 * 2426532/115625900)) = 1867\n      // sell_low reminder = 100000-88943 = 11057\n\n      // sell_low is fully filled\n      BOOST_CHECK( !sell_low );\n      // call pays price = 275/10000\n      // call match price = (275/10000) * (1299/1300) = 357225/13000000\n      // sell_low receives = round_down(11057 * 357225/13000000)) = 303\n      // sell_low pays = round_up(303 * 13000000/357225) = 11027, the rest is cancelled\n\n      auto final_check = [&]\n      {\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 138973 ); // 250000 - 11057 - 88943 - 11027\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2402 ); // 232 + 1867 + 303\n         // call receives = 11027\n         // call pays = round_down(11027 * 275/10000) = 303, margin call fee = 0\n         // now feed price = 13:10 * (100000-11027):(2750-303) = 13:10 * 88973:2447 = 1156649:24470\n         // call order match price is 1300:1299 * 88973:2447 = 36.38802348\n         // sell_mid's price is 10000/210 = 47.619047619\n\n         // sell_mid got filled too\n         BOOST_CHECK( !db.find( sell_mid_id ) );\n\n         // sell_mid was selling 10000 MPA for 210 CORE as maker, matched at its price\n         // call pays round_down(210*1300/1299) = 210, fee = 0\n         // call receives\n         // now feed price is 13:10 * (88973-10000):(2447-210)\n         //                 = 13:10 * 78973:2237 = 1026649:22370\n         // call order match price is 1300:1299 * 78973:2237 = 35.330261612\n         // sell_high's price is 10000/275 = 36.363636364\n\n         // sell_high got filled too\n         BOOST_CHECK( !db.find( sell_high_id ) );\n\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 );\n         // sell_mid was selling 10000 MPA for 210 CORE as maker, matched at its price\n         // sell_high was selling 10000 MPA for 275 CORE as maker, matched at its price\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275\n         // call pays round_down(275*1300/1299) = 275, fee = 0\n         // now feed price is 13:10 * (78973-10000):(2237-275)\n         //                 = 13:10 * 68973:1962 = 896649:19620 (>1000/22)\n         // call order match price is 1300:1299 * 68973:1962 = 35.181496941\n         // sell_highest's price is 10000/285 = 35.087719298, does not match\n\n         // check\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                      == price( asset(896649,mpa_id), asset(19620) ) );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n         BOOST_CHECK_EQUAL( call_id(db).debt.value, 68973 );\n         BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1962 );\n         BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n         BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n      };\n\n      final_check();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      final_check();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests force settlements when BSRM is no_settlement and call order is maker\nBOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrowers borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(10,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed\n      // 1000 * (2000/1250) * 1.9 = 3040\n      // 1000 * (22/10) * 1.9 = 4180\n      BOOST_CHECK_THROW( borrow( borrower3, asset(1000, mpa_id), asset(4180) ), fc::exception );\n      // borrower3 create debt position right above ICR\n      const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // borrower adjust debt position to right at MSSR\n      // 1000 * (22/10) * 1.25 = 2750\n      borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR<MSSR\n      borrow( borrower, asset(0, mpa_id), asset(749) );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Sam update MSSR and MCFR\n      // note: borrower's position is undercollateralized again due to the mssr change\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300;\n      aubop.new_options.extensions.value.margin_call_fee_ratio = 1;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(1000,mpa_id) );\n      transfer( borrower2, seller, asset(1000,mpa_id) );\n      transfer( borrower3, seller, asset(500,mpa_id) );\n      transfer( borrower3, seller2, asset(500,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later\n      const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n\n      // seller2 sells more, this order won't be filled in the beginning either\n      const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n\n      // seller2 sells more, this order won't be filled\n      const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) );\n      BOOST_REQUIRE( sell_highest );\n      limit_order_id_type sell_highest_id = sell_highest->get_id();\n      BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // 500 - 100 - 100 - 100\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(111,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK( !db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300)\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price\n                   == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100)\n                                                                    // 13:10 * 889:1867\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller settles some more\n      result = force_settle( seller, asset(1000,mpa_id) );\n      settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n      BOOST_CHECK( !db.find(settle_id) );\n\n      // call2 is filled by settle order\n      BOOST_CHECK( !db.find(call2_id) );\n      // now feed price is 13:10 * 1000:2750 = 26:55 (>10/22)\n      // call order match price is 1300:1299 * 1000:2750 = 0.363916299\n      // sell_mid's price is 100/210 = 0.047619048\n\n      // then seller2's sell_mid got filled by call\n      BOOST_CHECK( !db.find( sell_mid_id ) );\n\n      // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price\n      // call pays round_down(210*1300/1299) = 210, fee = 0\n      // now feed price is 13:10 * (1000-100):(2750-210)\n      //                 = 13:10 * 900:2540 = 11700:25400 (>10/22)\n      // call order match price is 1300:1299 * 900:2540 = 0.32732629\n      // sell_high's price is 100/275 = 0.363636364\n\n      // then sell_high got filled by call\n      BOOST_CHECK( !db.find( sell_high_id ) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 );\n      // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price\n      // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275\n      // call pays round_down(275*1300/1299) = 275, fee = 0\n      // now feed price is 13:10 * (1000-100-100):(2750-210-275)\n      //                 = 13:10 * 800:2265 = 10400:22650 = 208:453 (>10/22)\n      // call order match price is 1300:1299 * 800:2265 = 0.353472785\n      // sell_highest's price is 100/285 = 0.350877193\n\n      // then the settle order got filled by call\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000\n      // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(22650/10400)*(1299/1000)\n      // 232 + 1866 + 314\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2412 );\n      // now feed price is 13:10 * (800-111):(2265-111*(22650/10400)*(13/10))\n      //                 = 13:10 * 689:1951 = 8957:19510 (>10/22)\n      // call order match price is 1300:1299 * 689:1951 = 0.353424094\n      // sell_highest's price is 100/285 = 0.350877193\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price\n                   == price( asset(8957,mpa_id), asset(19510) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1951 );\n      BOOST_CHECK( !db.find(call2_id) );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller settles more\n      result = force_settle( seller, asset(1000,mpa_id) );\n      settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n\n      auto final_check = [&]\n      {\n         BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 311 );\n\n         BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000\n         // 2412 + round_up(689*(19510/8957)*(1299/1000))\n         // 2412 + 1950\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 );\n\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 );\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change\n\n         // check\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n         BOOST_CHECK( !db.find(call_id) );\n         BOOST_CHECK( !db.find(call2_id) );\n         BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n         BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n      };\n\n      final_check();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      final_check();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests force settlements when BSRM is no_settlement and call order is maker and settle order is too small to fill\nBOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test )\n{\n   try {\n\n      // Advance to core-2467 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrowers borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed\n      // 100000 * (2000/125000) * 1.9 = 3040\n      // 100000 * (22/1000) * 1.9 = 4180\n      BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception );\n      // borrower3 create debt position right above ICR\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // borrower adjust debt position to right at MSSR\n      // 100000 * (22/1000) * 1.25 = 2750\n      borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR<MSSR\n      borrow( borrower, asset(0, mpa_id), asset(749) );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Sam update MSSR and MCFR\n      // note: borrower's position is undercollateralized again due to the mssr change\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300;\n      aubop.new_options.extensions.value.margin_call_fee_ratio = 1;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n      transfer( borrower2, seller, asset(100000,mpa_id) );\n      transfer( borrower3, seller, asset(50000,mpa_id) );\n      transfer( borrower3, seller2, asset(50000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later\n      const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 );\n\n      // seller2 sells more, this order won't be filled in the beginning either\n      const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n\n      // seller2 sells more, this order won't be filled\n      const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) );\n      BOOST_REQUIRE( sell_highest );\n      limit_order_id_type sell_highest_id = sell_highest->get_id();\n      BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 250000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(11100,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n\n      auto final_check = [&]\n      {\n         BOOST_CHECK( !db.find(settle_id) );\n\n         // call2 pays price = 210/10000\n         // call2 match price = (210/10000) * (1299/1300) = 27279/1300000\n         // sell_low receives = round_down(11100 * 27279/1300000)) = 232\n         // sell_low pays = round_up(232 * 1300000/27279) = 11057, the rest is cancelled\n         // call2 receives = 11057\n         // call2 pays = round_down(11057 * 210/10000) = 232, margin call fee = 0\n         // now feed price = 13:10 * (100000-11057):(2100-232) = 13:10 * 88943:1868 = 1156259:18680\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 238943 ); // 250000 - 11057\n         BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 );\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 );\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n         // check\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                      == price( asset(1156259,mpa_id), asset(18680) ) );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n         BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n         BOOST_CHECK_EQUAL( call2_id(db).debt.value, 88943 );\n         BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1868 );\n         BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n         BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 );\n         BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n         BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n      };\n\n      final_check();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      final_check();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests margin calls and force settlements when BSRM is no_settlement and call order is taker\nBOOST_AUTO_TEST_CASE( no_settlement_taker_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // Several passes, with different limit orders and/or settle orders\n   for( int i = 0; i <= 20; ++ i )\n   {\n      idump( (i) );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; // 1.1%\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrowers borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2750) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // 1000 * (22/10) * 1.9 = 4180\n      const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(1000,mpa_id) );\n      transfer( borrower2, seller, asset(1000,mpa_id) );\n      transfer( borrower3, seller, asset(500,mpa_id) );\n      transfer( borrower3, seller2, asset(500,mpa_id) );\n\n      int64_t expected_seller_balance_mpa = 2500;\n      int64_t expected_seller2_balance_mpa = 500;\n\n      // seller2 sells some\n      const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) );\n      BOOST_REQUIRE( sell_highest );\n      limit_order_id_type sell_highest_id = sell_highest->get_id();\n      BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n      expected_seller2_balance_mpa -= 100;\n\n      // seller2 sells more\n      const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n      expected_seller2_balance_mpa -= 100;\n\n      // seller2 sells more, due to MCFR, this order won't be filled if no order is selling lower\n      const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n      expected_seller2_balance_mpa -= 100;\n\n      // seller sells\n      const limit_order_object* sell_low = nullptr;\n      limit_order_id_type sell_low_id;\n      force_settlement_id_type settle_id;\n      force_settlement_id_type settle2_id;\n      if( 0 == i )\n      {\n         // Nothing to do here\n      }\n      else if( 1 == i )\n      {\n         sell_low = create_sell_order( seller, asset(111,mpa_id), asset(230) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 111 );\n         expected_seller_balance_mpa -= 111;\n      }\n      else if( 2 == i )\n      {\n         sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 111 );\n         expected_seller_balance_mpa -= 111;\n      }\n      else if( 3 == i )\n      {\n         sell_low = create_sell_order( seller, asset(900,mpa_id), asset(1870) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 900 );\n         expected_seller_balance_mpa -= 900;\n      }\n      else if( 4 == i )\n      {\n         sell_low = create_sell_order( seller, asset(920,mpa_id), asset(1870) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 920 );\n         expected_seller_balance_mpa -= 920;\n      }\n      else if( 5 == i )\n      {\n         sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(1870) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1000 );\n         expected_seller_balance_mpa -= 1000;\n      }\n      else if( 6 == i )\n      {\n         sell_low = create_sell_order( seller, asset(1050,mpa_id), asset(1870) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1050 );\n         expected_seller_balance_mpa -= 1050;\n      }\n      else if( 7 == i )\n      {\n         sell_low = create_sell_order( seller, asset(1800,mpa_id), asset(1870*2) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1800 );\n         expected_seller_balance_mpa -= 1800;\n      }\n      else if( 8 == i )\n      {\n         sell_low = create_sell_order( seller, asset(2000,mpa_id), asset(1870) );\n         BOOST_REQUIRE( sell_low );\n         sell_low_id = sell_low->get_id();\n         BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 2000 );\n         expected_seller_balance_mpa -= 2000;\n      }\n      else if( 9 == i )\n      {\n         auto result = force_settle( seller, asset(111,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 111;\n      }\n      else if( 10 == i )\n      {\n         auto result = force_settle( seller, asset(990,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 990;\n      }\n      else if( 11 == i )\n      {\n         auto result = force_settle( seller, asset(995,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 995;\n      }\n      else if( 12 == i )\n      {\n         auto result = force_settle( seller, asset(1000,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 1000;\n      }\n      else if( 13 == i )\n      {\n         auto result = force_settle( seller, asset(1050,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 1050;\n      }\n      else if( 14 == i )\n      {\n         auto result = force_settle( seller, asset(1750,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 1750;\n      }\n      else if( 15 == i )\n      {\n         auto result = force_settle( seller, asset(1800,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 1800;\n      }\n      else if( 16 == i )\n      {\n         auto result = force_settle( seller, asset(2000,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         expected_seller_balance_mpa -= 2000;\n      }\n      else if( 17 == i )\n      {\n         auto result = force_settle( seller, asset(492,mpa_id) );\n         settle_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle_id) );\n         result = force_settle( seller, asset(503,mpa_id) );\n         settle2_id = *result.get<extendable_operation_result>().value.new_objects->begin();\n         BOOST_REQUIRE( db.find(settle2_id) );\n         expected_seller_balance_mpa -= 995;\n      }\n      else\n      {\n         BOOST_TEST_MESSAGE( \"No more test cases so far\" );\n         break;\n      }\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // publish a new feed so that borrower's and borrower2's debt positions become undercollateralized\n      f.settlement_price = price( asset(10,mpa_id), asset(22) );\n      f.maximum_short_squeeze_ratio = 1300;\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check result\n      auto check_result = [&]\n      {\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa );\n         BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), expected_seller2_balance_mpa );\n         BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 );\n         BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n         if( 0 == i ) // no order is filled\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_mid price = 100:210 = 0.476190476\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(13,mpa_id), asset(21) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 1 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 111:230 = 0.482608696\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low receives 230\n            // call2 pays round_down(230*1300/1289) = 231, margin call fee = 1\n            // now feed price is 13:10 * (1000-111):(2100-231)\n            //                 = 13:10 * 889:1869 = 11557:18690 = 0.61835206 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 889:1869 = 0.479714554\n            // sell_mid price = 100:210 = 0.476190476\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(11557,mpa_id), asset(18690) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1869 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 230 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 2 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 111:210 = 0.504545455\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low receives 210\n            // call2 pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-111):(2100-211)\n            //                 = 13:10 * 889:1889 = 11557:18890 = 0.611805188 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 889:1889 = 0.474635522\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid receives 210\n            // call2 pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-111-100):(2100-211-211)\n            //                 = 13:10 * 789:1678 = 10257:16780 = 0.611263409 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 789:1678 = 0.474215212\n            // sell_high price = 100:275 = 0.363636364\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10257,mpa_id), asset(16780) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 789 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1678 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 210 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 );\n         }\n         else if( 3 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 900:1870 = 0.481283422\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low receives 1870\n            // call2 pays round_down(1870*1300/1289) = 1885, margin call fee = 15\n            // now feed price is 13:10 * (1000-900):(2100-1885)\n            //                 = 13:10 * 100:215 = 130:215 = 0.604651163 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 100:215 = 0.469085464\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid receives 210\n            // call2 pays round_up(210*1300/1289) = 212, margin call fee = 2\n            // call2 is fully filled, freed collateral = 215 - 212 = 3\n            BOOST_CHECK( !db.find( call2_id ) );\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_high price = 100:275 = 0.363636364\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(130,mpa_id), asset(275) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 3 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 );\n         }\n         else if( 4 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 920:1870 = 0.49197861\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low receives 1870\n            // call2 pays round_down(1870*1300/1289) = 1885, margin call fee = 15\n            // now feed price is 13:10 * (1000-920):(2100-1885)\n            //                 = 13:10 * 80:215 = 104:215 = 0.48372093 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 80:215 = 0.375268371\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is partially filled\n            // sell_mid pays 80, receives 80 * 210/100 = 168\n            // call2 pays round_up(80*(210/100)*(1300/1289)) = 170, margin call fee = 2\n            // call2 is fully filled, freed collateral = 215-170 = 45\n            BOOST_CHECK( !db.find( call2_id ) );\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 20, receives 20 * 210/100 = 42\n            // call pays round_down(20*(210/100)*(1300/1289)) = 42, margin call fee = 0\n            // now feed price is 13:10 * (1000-20):(2750-42)\n            //                 = 13:10 * 980:2708 = 1274:2708 = 0.470457903 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 980:2708 = 0.364978978\n            // sell_high price = 100:275 = 0.363636364\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(1274,mpa_id), asset(2708) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 980 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2708 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 45 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 );\n         }\n         else if( 5 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 1000:1870 = 0.534759358\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low receives 1870\n            // call2 pays round_up(1870*1300/1289) = 1886, margin call fee = 16\n            // call2 is fully filled, freed collateral = 2100-1886 = 214\n            BOOST_CHECK( !db.find( call2_id ) );\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-100):(2750-211)\n            //                 = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 900:2539 = 0.357495223\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price is 13:10 * (1000-100-100):(2750-211-277)\n            //                 = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 800:2262 = 0.356687444\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(1040,mpa_id), asset(2262) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 214 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 6 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 1050:1870 = 0.561497326\n            // sell_low is partially filled\n            // sell_low pays 1000, receives round_up(1000 * 1870/1050) = 1781\n            // call2 pays round_up(1000*(1870/1050)*(1300/1289)) = 1797, margin call fee = 16\n            // call2 is fully filled, freed collateral = 2100-1797 = 303\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_low price = 1050:1870 = 0.561497326\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low pays 50, receives round_down(50*1870/1050) = 89\n            // call pays round_down(50*(1870/1050)*(1300/1289)) = 89, margin call fee = 0\n            // now feed price is 13:10 * (1000-50):(2750-89)\n            //                 = 13:10 * 950:2661 = 1235:2661 = 0.464111236 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 950:2661 = 0.360055265\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price from call is 13:10 * (1000-50-100):(2750-89-211)\n            //                 = 13:10 * 850:2450 = 1105:2450 = 0.451020408 (< 10:22 = 0.454545455)\n            // so feed price is 10:22\n            // call match price is 1000:1289 * 10:22 = 0.352634177\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price from call is 13:10 * (1000-50-100-100):(2750-89-211-277)\n            //                 = 13:10 * 750:2173 = 975:2173 = 0.448688449 (< 10:22 = 0.454545455)\n            // so feed price is 10:22\n            // call match price is 1000:1289 * 10:22 = 0.352634177\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10,mpa_id), asset(22) ) );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 750 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2173 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 303 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 7 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 0.480254165\n            // sell_low price = 900:1870 = 0.481283422\n            // sell_low is partially filled\n            // sell_low pays 1000, receives round_up(1000 * 1870/900) = 2078\n            // call2 pays round_up(1000*(1870/900)*(1300/1289)) = 2096, margin call fee = 18\n            // call2 is fully filled, freed collateral = 2100-2096 = 4\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_low price = 900:1870 = 0.481283422\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n            // sell_low pays 800, receives round_down(800*1870/900) = 1662\n            // call pays round_down(800*(1870/900)*(1300/1289)) = 1676, margin call fee = 14\n            // now call's debt is 1000-800=200, collateral is 2750-1676=1074\n            //     CR = 1074/200 / (22/10) = 2.44 > 1.85, out of margin call territory\n            // so feed price is 10:22\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10,mpa_id), asset(22) ) );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 200 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1074 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 4 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870*2 ); // 2078+1662\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 8 == i )\n         {\n            // sell_low would match call2 and call\n\n            // sell_low pays 1000, receives round_up(1000 * 1870/2000) = 935\n            // call2 pays round_up(1000*(1870/2000)*(1300/1289)) = 943, margin call fee = 8\n            // call2 is fully filled, freed collateral = 2100-943 = 1157\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // sell_low is fully filled\n            BOOST_CHECK( !db.find( sell_low_id ) );\n\n            // sell_low pays 1000, receives round_up(1000 * 1870/2000) = 935\n            // call pays round_up(1000*(1870/2000)*(1300/1289)) = 943, margin call fee = 8\n            // call is fully filled, freed collateral = 2750-943 = 1807\n            BOOST_CHECK( !db.find( call_id ) );\n\n            // feed price is 10:22\n\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10,mpa_id), asset(22) ) );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 1157 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 + 1807 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 9 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_down(111*27069/13000) = 231\n            // call2 pays round_down(111*2100/1000) = 233, margin call fee = 2\n            // now feed price is 13:10 * (1000-111):(2100-233)\n            //                 = 13:10 * 889:1867 = 11557:18670 = 0.619014462 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 889:1867 = 0.480228442\n            // sell_mid price = 100:210 = 0.476190476\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(11557,mpa_id), asset(18670) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 231 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 10 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_down(990*27069/13000) = 2061\n            // call2 pays round_down(990*2100/1000) = 2079, margin call fee = 18\n            // now feed price is 13:10 * (1000-990):(2100-2079)\n            //                 = 13:10 * 10:21 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 10:21 = 0.480254165\n            // sell_mid price = 100:210 = 0.476190476\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(13,mpa_id), asset(21) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 10 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 21 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2061 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 11 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_down(995*27069/13000) = 2071\n            // call2 pays round_down(995*2100/1000) = 2089, margin call fee = 18\n            // now feed price is 13:10 * (1000-995):(2100-2089)\n            //                 = 13:10 * 5:11 = 13:22 = 0.590909091 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 5:11 = 0.45842443\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is partially filled\n            // sell_mid pays 5, receives round_up(5 * 21/10) = 11\n            // call2 pays round_up(5*(21/10)*(1300/1289)) = 11, margin call fee = 0\n            // call2 is fully filled, freed collateral = 11-11 = 0\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 95, receives round_down(95*210/100) = 199\n            // call pays round_down(95*(210/100)*(1300/1289)) = 201, margin call fee = 2\n            // now feed price from call is 13:10 * (1000-95):(2750-201)\n            //                 = 13:10 * 905:2549 = 11765:25490 = 0.46155355 (> 10:22 = 0.454545455)\n            // so feed price is 11765:25490\n            // call match price is 1300:1289 * 905:2549 = 0.358071024\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price from call is 13:10 * (1000-95-100):(2750-201-277)\n            //                 = 13:10 * 805:2272 = 10465:22720 = 0.460607394 (> 10:22 = 0.454545455)\n            // so feed price is 10465:22720\n            // call match price is 1300:1289 * 805:2272 = 0.357337001\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10465,mpa_id), asset(22720) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 805 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2272 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2071 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 11+199+275\n         }\n         else if( 12 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_up(1000*27069/13000) = 2083\n            // call2 pays 2100, margin call fee = 17\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-100):(2750-211)\n            //                 = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 900:2539 = 0.357495223\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price is 13:10 * (1000-100-100):(2750-211-277)\n            //                 = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 800:2262 = 0.356687444\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(1040,mpa_id), asset(2262) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2083 );\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 13 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // call2 is fully filled\n            BOOST_CHECK( !db.find( call2_id ) );\n            // settle order receives round_up(1000*27069/13000) = 2083\n            // call2 pays 2100, margin call fee = 17\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call pays price = 1000:2750\n            // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-100):(2750-211)\n            //                 = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 900:2539 = 0.357495223\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price is 13:10 * (1000-100-100):(2750-211-277)\n            //                 = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444\n            // sell_highest price = 100:285 = 0.350877193, does not match\n\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_down(50*2915718/1040000) = 140\n            // call pays round_down(50*2262/800) = 141, margin call fee = 1\n            // now feed price is 13:10 * (800-50):(2262-141)\n            //                 = 13:10 * 750:2121 = 975:2121 = 0.459688826 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 750:2121 = 0.35662438\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(975,mpa_id), asset(2121) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 750 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2121 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2223 ); // 2083 + 140\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 14 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // call2 is fully filled\n            BOOST_CHECK( !db.find( call2_id ) );\n            // settle order receives round_up(1000*27069/13000) = 2083\n            // call2 pays 2100, margin call fee = 17\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call pays price = 1000:2750\n            // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-100):(2750-211)\n            //                 = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 900:2539 = 0.357495223\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price is 13:10 * (1000-100-100):(2750-211-277)\n            //                 = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444\n            // sell_highest price = 100:285 = 0.350877193, does not match\n\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_down(750*2915718/1040000) = 2102\n            // call pays round_down(750*2262/800) = 2120, margin call fee = 18\n            // now feed price is 13:10 * (800-750):(2262-2120)\n            //                 = 13:10 * 50:142 = 65:142 = 0.457746479 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 50:142 = 0.355117517\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(65,mpa_id), asset(142) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 50 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 142 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4185 ); // 2083 + 2102\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 15 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // call2 is fully filled\n            BOOST_CHECK( !db.find( call2_id ) );\n            // settle order receives round_up(1000*27069/13000) = 2083\n            // call2 pays 2100, margin call fee = 17\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call pays price = 1000:2750\n            // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-100):(2750-211)\n            //                 = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 900:2539 = 0.357495223\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price is 13:10 * (1000-100-100):(2750-211-277)\n            //                 = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444\n            // sell_highest price = 100:285 = 0.350877193, does not match\n\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_up(800*2915718/1040000) = 2243\n            // call pays 2262, margin call fee = 19\n            // call is fully filled\n            BOOST_CHECK( !db.find( call_id ) );\n            // now feed price is 10:22, no margin call\n\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10,mpa_id), asset(22) ) );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4326 ); // 2083 + 2243\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 16 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // call2 is fully filled\n            BOOST_CHECK( !db.find( call2_id ) );\n            // settle order receives round_up(1000*27069/13000) = 2083\n            // call2 pays 2100, margin call fee = 17\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call pays price = 1000:2750\n            // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 100, receives 210\n            // call pays round_down(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (1000-100):(2750-211)\n            //                 = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 900:2539 = 0.357495223\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price is 13:10 * (1000-100-100):(2750-211-277)\n            //                 = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444\n            // sell_highest price = 100:285 = 0.350877193, does not match\n\n            // call is fully filled\n            BOOST_CHECK( !db.find( call_id ) );\n            // settle order receives round_up(800*2915718/1040000) = 2243\n            // call pays 2262, margin call fee = 19\n            BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 200 );\n\n            // now feed price is 10:22, no margin call\n\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10,mpa_id), asset(22) ) );\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4326 ); // 2083 + 2243\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275\n         }\n         else if( 17 == i )\n         {\n            // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455)\n            // call2 pays price = 1000:2100\n            //       match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165\n            // settle order is fully filled\n            BOOST_CHECK( !db.find( settle_id ) );\n            // settle order receives round_down(492*27069/13000) = 1024\n            // call2 pays round_down(492*2100/1000) = 1033, margin call fee = 9\n            // now feed price is 13:10 * (1000-492):(2100-1033)\n            //                 = 13:10 * 508:1067 = 6604:10670 = 0.618931584 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 508:1067 = 660400:1375363 = 0.480164146\n            // sell_mid price = 100:210 = 0.476190476 does not match\n            // settle2 is fully filled\n            BOOST_CHECK( !db.find( settle2_id ) );\n            // settle2 receives round_down(503*(1375363/660400)) = 1047\n            // call2 pays round_down(503*1067/508) = 1056, margin call fee = 9\n            // now feed price is 13:10 * (508-503):(1067-1056)\n            //                 = 13:10 * 5:11 = 13:22 = 0.590909091 (> 10:22 = 0.454545455)\n            // call2 match price is 1300:1289 * 5:11 = 0.45842443\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is partially filled\n            // sell_mid pays 5, receives round_up(5 * 21/10) = 11\n            // call2 pays round_up(5*(21/10)*(1300/1289)) = 11, margin call fee = 0\n            // call2 is fully filled, freed collateral = 11-11 = 0\n            BOOST_CHECK( !db.find( call2_id ) );\n\n            // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455)\n            // call match price is 1300:1289 * 100:275 = 0.366739544\n            // sell_mid price = 100:210 = 0.476190476\n            // sell_mid is fully filled\n            BOOST_CHECK( !db.find( sell_mid_id ) );\n            // sell_mid pays 95, receives round_down(95*210/100) = 199\n            // call pays round_down(95*(210/100)*(1300/1289)) = 201, margin call fee = 2\n            // now feed price from call is 13:10 * (1000-95):(2750-201)\n            //                 = 13:10 * 905:2549 = 11765:25490 = 0.46155355 (> 10:22 = 0.454545455)\n            // so feed price is 11765:25490\n            // call match price is 1300:1289 * 905:2549 = 0.358071024\n            // sell_high price = 100:275 = 0.363636364\n            // sell_high is fully filled\n            BOOST_CHECK( !db.find( sell_high_id ) );\n            // sell_high pays 100, receives 275\n            // call pays round_down(275*1300/1289) = 277, margin call fee = 2\n            // now feed price from call is 13:10 * (1000-95-100):(2750-201-277)\n            //                 = 13:10 * 805:2272 = 10465:22720 = 0.460607394 (> 10:22 = 0.454545455)\n            // so feed price is 10465:22720\n            // call match price is 1300:1289 * 805:2272 = 0.357337001\n            // sell_highest price = 100:285 = 0.350877193\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(10465,mpa_id), asset(22720) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 805 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2272 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2071 ); // 1024+1047\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 11+199+275\n         }\n         else\n         {\n            BOOST_FAIL( \"to be fixed\" );\n         }\n      };\n\n      check_result();\n\n      // generate a block\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      // check again\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_CAPTURE_AND_RETHROW() }\n\n/// Tests updating debt positions when BSRM is no_settlement\nBOOST_AUTO_TEST_CASE( no_settlement_update_debt_test )\n{ try {\n\n   // Advance to core-2467 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // Several passes\n   for( int i = 0; i <= 20; ++ i )\n   {\n      idump( (i) );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller2));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n      fund( borrower2, asset(init_amount) );\n      fund( borrower3, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n      uint8_t bsrm_value = static_cast<uint8_t>(bsrm_type::no_settlement);\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // borrowers borrow some\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) );\n      BOOST_REQUIRE( call2_ptr );\n      call_order_id_type call2_id = call2_ptr->get_id();\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      f.settlement_price = price( asset(1000,mpa_id), asset(22) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed\n      // 100000 * (2000/125000) * 1.9 = 3040\n      // 100000 * (22/1000) * 1.9 = 4180\n      BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception );\n      // borrower3 create debt position right above ICR\n      const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) );\n      BOOST_REQUIRE( call3_ptr );\n      call_order_id_type call3_id = call3_ptr->get_id();\n\n      // borrower adjust debt position to right at MSSR\n      // 100000 * (22/1000) * 1.25 = 2750\n      borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR<MSSR\n      borrow( borrower, asset(0, mpa_id), asset(749) );\n\n      // check\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Sam update MSSR and MCFR\n      // note: borrower's position is undercollateralized again due to the mssr change\n      asset_update_bitasset_operation aubop;\n      aubop.issuer = sam_id;\n      aubop.asset_to_update = mpa_id;\n      aubop.new_options = mpa_id(db).bitasset_data(db).options;\n      aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300;\n      aubop.new_options.extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( aubop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u );\n      BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) );\n      BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() );\n\n      // Transfer funds to seller2\n      transfer( borrower3, seller2, asset(50000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n      // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later\n      const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) );\n      BOOST_REQUIRE( sell_mid );\n      limit_order_id_type sell_mid_id = sell_mid->get_id();\n      BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 );\n\n      // seller2 sells more, this order won't be filled in the beginning either\n      const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) );\n      BOOST_REQUIRE( sell_high );\n      limit_order_id_type sell_high_id = sell_high->get_id();\n      BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n\n      // seller2 sells more, this order won't be filled\n      const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) );\n      BOOST_REQUIRE( sell_highest );\n      limit_order_id_type sell_highest_id = sell_highest->get_id();\n      BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n      BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n\n      // update debt position\n      BOOST_TEST_MESSAGE( \"Update debt position\");\n      if( 0 == i )\n      {\n         // borrower2 slightly update the position and is not filled\n         borrow( borrower2, asset(0, mpa_id), asset(1) ); // to 100000:2101\n      }\n      else if( 1 == i )\n      {\n         // borrower2 update the position and is partially filled\n         borrow( borrower2, asset(0, mpa_id), asset(100) ); // to 100000:2200\n      }\n      else if( 2 == i )\n      {\n         // borrower2 update the position to smaller so it will be fully filled\n         borrow( borrower2, asset(-90000, mpa_id), asset(-1880) ); // to 10000:220\n      }\n      else if( 3 == i )\n      {\n         // borrower2 update the position to smaller so it will be fully filled,\n         // and borrower's position is partially filled\n         borrow( borrower2, asset(-91000, mpa_id), asset(-1880) ); // to 9000:220\n      }\n      else if( 4 == i )\n      {\n         // borrower2 update the position so that its CR is higher than borrower's,\n         // and borrower's position is partially filled\n         borrow( borrower2, asset(0, mpa_id), asset(651) ); // to 100000:2751\n      }\n      else if( 5 == i )\n      {\n         // borrower2 close the position, so borrwer's position is partially filled\n         borrow( borrower2, asset(-100000, mpa_id), asset(-2100) ); // to 10000:220\n      }\n      else if( 6 == i )\n      {\n         // borrower close the position, no order is filled\n         borrow( borrower, asset(-100000, mpa_id), asset(-2750) );\n      }\n      else\n      {\n         BOOST_TEST_MESSAGE( \"No more test cases so far\" );\n         break;\n      }\n\n      // check result\n      auto check_result = [&]\n      {\n         BOOST_TEST_MESSAGE( \"Check result\");\n         BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n         BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 );\n         BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 );\n\n         if( 0 == i ) // no order is filled\n         {\n            // now feed price is 13:10 * 100000:2101 = 130000:2101 = 61.875297477 (> 1000:22 = 45.454545455)\n            // call2 pays price = 100000:2101\n            //       match price = 100000:2101 * 1300:1289 = 48.002558167\n            // sell_mid price = 10000:210 = 47.619047619\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(130000,mpa_id), asset(2101) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2101 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2101 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else if( 1 == i )\n         {\n            // now feed price is 13:10 * 100000:2200 = 1300:22 = 59.090909091 (> 1000:22 = 45.454545455)\n            // call2 pays price = 100000:2200\n            //       match price = 100000:2200 * 1300:1289 = 45.84244305\n            // sell_mid price = 10000:210 = 47.619047619\n            // sell_mid is filled\n            BOOST_REQUIRE( !db.find( sell_mid_id ) );\n            // sell_mid receives 210\n            // call2 pays round_down(10000*(210*1300/1289) = 211, margin call fee = 1\n            // now feed price is 13:10 * (100000-10000):(2200-211)\n            //                 = 13:10 * 90000:1989 = 1170000:19890 = 58.823529412 (> 1000:22 = 45.454545455)\n            // call2 match price is 1300:1289 * 90000:1989 = 42.124625705\n            // sell_high price = 10000:275 = 36.363636364\n\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(1170000,mpa_id), asset(19890) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90000 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1989 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2200 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 );\n         }\n         else if( 2 == i )\n         {\n            // now feed price is 13:10 * 10000:220 = 1300:22 = 59.090909091 (> 1000:22 = 45.454545455)\n            // call2 pays price = 10000:220\n            //       match price = 10000:220 * 1300:1289 = 45.84244305\n            // sell_mid price = 10000:210 = 47.619047619\n            // sell_mid is filled\n            BOOST_REQUIRE( !db.find( sell_mid_id ) );\n            // sell_mid receives 210\n            // call2 pays round_up(210*1300/1289) = 212, margin call fee = 2\n            // call2 is filled\n            BOOST_REQUIRE( !db.find( call2_id ) );\n            // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455)\n            // call match price is 1300:1289 * 100000:2750 = 36.67395444\n            // sell_high price = 10000:275 = 36.363636364\n\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(13000,mpa_id), asset(275) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 212 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 );\n         }\n         else if( 3 == i )\n         {\n            // now feed price is 13:10 * 9000:220 = 1170:22 = 53.181818182 (> 1000:22 = 45.454545455)\n            // call2 pays price = 9000:220\n            //       match price = 9000:220 * 1300:1289 = 41.258198745\n            // sell_mid price = 10000:210 = 47.619047619\n            // call2 is filled\n            BOOST_REQUIRE( !db.find( call2_id ) );\n            // call2 receives 9000\n            // sell_mid receives round_up(9000*210/10000) = 189\n            // call2 pays round_up(9000*(210/10000)*(1300/1289)) = 191, margin call fee = 2\n\n            // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455)\n            // call match price is 1300:1289 * 100000:2750 = 36.67395444\n            // sell_mid price = 10000:210 = 47.619047619\n            // sell_mid is filled\n            BOOST_REQUIRE( !db.find( sell_mid_id ) );\n            // call receives 1000\n            // sell_mid receives round_down(1000*210/10000) = 21\n            // call pays round_down(1000*(210/10000)*(1300/1289)) = 21, margin call fee = 0\n\n            // now feed price is 13:10 * (100000-1000):(2750-21)\n            //                 = 13:10 * 99000:2729 = 128700:2729 = 47.160131916 (> 1000:22 = 45.454545455)\n            // call pays price = 99000:2729\n            //      match price = 99000:2729 * 1300:1289 = 36.586603504\n            // sell_high price = 10000:275 = 36.363636364 (does not match)\n\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(128700,mpa_id), asset(2729) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 99000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2729 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 191 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // 189 + 21\n         }\n         else if( 4 == i )\n         {\n            // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455)\n            // call match price is 1300:1289 * 100000:2750 = 36.67395444\n            // sell_mid price = 10000:210 = 47.619047619\n            // sell_mid is filled\n            BOOST_REQUIRE( !db.find( sell_mid_id ) );\n            // call receives 10000\n            // sell_mid receives round_down(10000*210/10000) = 210\n            // call pays round_down(10000*(210/10000)*(1300/1289)) = 211, margin call fee = 1\n            // call is now (100000-10000):(2750-211) = 90000:2539 = 35.447026388\n            // call2 is 100000:2751 = 36.35041803\n\n            // now feed price is 13:10 * 100000:2751 = 130000:2751 = 47.255543439 (> 1000:22 = 45.454545455)\n            // call2 pays price = 100000:2751\n            //       match price = 100000:2751 * 1300:1289 = 36.660623304\n            // sell_high price = 10000:275 = 36.363636364 (does not match)\n\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(130000,mpa_id), asset(2751) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 90000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2539 );\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2751 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2751 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 );\n         }\n         else if( 5 == i )\n         {\n            BOOST_REQUIRE( !db.find( call2_id ) );\n            // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455)\n            // call match price is 1300:1289 * 100000:2750 = 36.67395444\n            // sell_mid price = 10000:210 = 47.619047619\n            // sell_mid is filled\n            BOOST_REQUIRE( !db.find( sell_mid_id ) );\n            // call receives 10000\n            // sell_mid receives round_down(10000*210/10000) = 210\n            // call pays round_down(10000*(210/10000)*(1300/1289)) = 211, margin call fee = 1\n            // call is now (100000-10000):(2750-211) = 90000:2539 = 35.447026388\n\n            // now feed price is 13:10 * 90000:2539 = 117000:2539 = 46.081134305 (> 1000:22 = 45.454545455)\n            // call pays price = 90000:2539\n            //      match price = 90000:2539 * 1300:1289 = 35.749522347\n            // sell_high price = 10000:275 = 36.363636364\n            // sell_high is filled\n            BOOST_REQUIRE( !db.find( sell_high_id ) );\n            // call receives 10000\n            // sell_mid receives round_down(10000*275/10000) = 275\n            // call pays round_down(10000*(275/10000)*(1300/1289)) = 277, margin call fee = 2\n            // call is now (100000-20000):(2750-211-277) = 80000:2262 = 35.366931919\n            // now feed price is 13:10 * 80000:2262 = 104000:2262 = 45.977011494 (> 1000:22 = 45.454545455)\n            // call pays price = 80000:2262\n            //      match price = 80000:2262 * 1300:1289 = 35.668744371\n            // sell_highest price = 10000:285 = 35.087719298 (does not match)\n\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(104000,mpa_id), asset(2262) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call_id(db).debt.value, 80000 );\n            BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275\n         }\n         else if( 6 == i )\n         {\n            BOOST_REQUIRE( !db.find( call_id ) );\n            // now feed price is 13:10 * 100000:2100 = 130000:2100 = 61.904761905 (> 1000:22 = 45.454545455)\n            // call2 pays price = 100000:2100\n            //       match price = 100000:2100 * 1300:1289 = 48.025416528\n            // sell_mid price = 10000:210 = 47.619047619\n            BOOST_REQUIRE( db.find( sell_mid_id ) );\n            BOOST_REQUIRE( db.find( sell_high_id ) );\n            BOOST_REQUIRE( db.find( sell_highest_id ) );\n            BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 );\n            BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 );\n\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price\n                         == price( asset(130000,mpa_id), asset(2100) ) );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n\n            BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 );\n            BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount );\n            BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000\n            BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 );\n         }\n         else\n         {\n            BOOST_FAIL( \"to be fixed\" );\n         }\n      };\n      check_result();\n\n      // generate a block\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      // check again\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_CAPTURE_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/call_order_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <random>\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( call_order_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( call_order_object_test )\n{ try {\n   // assume GRAPHENE_COLLATERAL_RATIO_DENOM is 1000 in this test case\n   BOOST_REQUIRE_EQUAL( 1000, GRAPHENE_COLLATERAL_RATIO_DENOM );\n\n   // function to create a new call_order_object\n   auto new_call_obj = []( const share_type c, const share_type d, int16_t mcr, optional<uint16_t> tcr = {} ) {\n      call_order_object o;\n      o.collateral = c;\n      o.debt = d;\n      o.call_price = price::call_price( asset( d, asset_id_type(1)), asset(c) , mcr );\n      o.target_collateral_ratio = tcr;\n      return o;\n   };\n\n   // function to validate result of call_order_object::get_max_debt_to_cover(...)\n   auto validate_result = []( const call_order_object& o, const price& match_price, const price& feed_price,\n                              int16_t mcr, const share_type result, bool print_log = true ) {\n      if( result == 0 )\n         return 1;\n\n      BOOST_REQUIRE_GT( result.value, 0 );\n      BOOST_REQUIRE_LE( result.value, o.debt.value );\n\n      BOOST_REQUIRE( match_price.base.asset_id == o.collateral_type() );\n      BOOST_REQUIRE( match_price.quote.asset_id == o.debt_type() );\n      BOOST_REQUIRE( feed_price.base.asset_id == o.collateral_type() );\n      BOOST_REQUIRE( feed_price.quote.asset_id == o.debt_type() );\n\n      // should be in margin call territory\n      price call_price = price::call_price( o.get_debt(), o.get_collateral(), mcr );\n      BOOST_CHECK( call_price <= feed_price );\n\n      if( !o.target_collateral_ratio.valid() )\n      {\n         BOOST_CHECK_EQUAL( result.value, o.debt.value );\n         return 2;\n      }\n\n      auto tcr = *o.target_collateral_ratio;\n      if( tcr == 0 )\n         tcr = 1;\n\n      asset to_cover( result, o.debt_type() );\n      asset to_pay = o.get_collateral();\n      if( result < o.debt )\n      {\n         to_pay = to_cover.multiply_and_round_up( match_price );\n         BOOST_CHECK_LT( to_pay.amount.value, o.collateral.value ); // should cover more on black swan event\n         BOOST_CHECK_EQUAL( result.value, (to_pay * match_price).amount.value ); // should not change after rounded down debt to cover\n\n         // should have target_cr set\n         // after sold some collateral, the collateral ratio will be higher than expected\n         price new_tcr_call_price = price::call_price( o.get_debt() - to_cover, o.get_collateral() - to_pay, tcr );\n         price new_mcr_call_price = price::call_price( o.get_debt() - to_cover, o.get_collateral() - to_pay, mcr );\n         BOOST_CHECK( new_tcr_call_price > feed_price );\n         BOOST_CHECK( new_mcr_call_price > feed_price );\n      }\n\n      // if sell less than calculated, the collateral ratio will not be higher than expected\n      int j = 3;\n      for( int i = 100000; i >= 10; i /= 10, ++j )\n      {\n         int total_passes = 3;\n         for( int k = 1; k <= total_passes; ++k )\n         {\n            bool last_check = (k == total_passes);\n            asset sell_less = to_pay;\n            asset cover_less;\n            for( int m = 0; m < k; ++m )\n            {\n               if( i == 100000 )\n                  sell_less.amount -= 1;\n               else\n                  sell_less.amount -= ( ( sell_less.amount + i - 1 ) / i );\n               cover_less = sell_less * match_price; // round down debt to cover\n               if( cover_less >= to_cover )\n               {\n                  cover_less.amount = to_cover.amount - 1;\n                  sell_less = cover_less * match_price; // round down collateral\n                  cover_less = sell_less * match_price; // round down debt to cover\n               }\n               sell_less = cover_less.multiply_and_round_up( match_price ); // round up to get collateral to sell\n               if( sell_less.amount <= 0 || cover_less.amount <= 0 ) // unable to sell or cover less, we return\n               {\n                  if( to_pay.amount == o.collateral )\n                     return j;\n                  return (j + 10);\n               }\n            }\n            BOOST_REQUIRE_LT( cover_less.amount.value, o.debt.value );\n            BOOST_REQUIRE_LT( sell_less.amount.value, o.collateral.value );\n            price tmp_tcr_call_price = price::call_price( o.get_debt() - cover_less, o.get_collateral() - sell_less, tcr );\n            price tmp_mcr_call_price = price::call_price( o.get_debt() - cover_less, o.get_collateral() - sell_less, mcr );\n            bool cover_less_is_enough = ( tmp_tcr_call_price > feed_price && tmp_mcr_call_price > feed_price );\n            if( !cover_less_is_enough )\n            {\n               if( !last_check )\n                  continue;\n               if( to_pay.amount == o.collateral )\n                  return j;\n               return (j + 10);\n            }\n            if( print_log )\n            {\n               print_log = false;\n               wlog( \"Impefect result >= 1 / ${i}\", (\"i\",i) );\n               wdump( (o)(match_price)(feed_price)(mcr)(result)(sell_less)(cover_less)(tmp_mcr_call_price)(tmp_tcr_call_price) );\n            }\n            break;\n         }\n      }\n      if( to_pay.amount == o.collateral )\n         return j;\n      return (j + 10);\n   };\n\n   // init\n   int16_t mcr = 1750;\n   price mp, fp;\n   call_order_object obj;\n   int64_t expected;\n   share_type result;\n\n   mp = price( asset(1100), asset(1000, asset_id_type(1)) ); // match_price\n   fp = price( asset(1000), asset(1000, asset_id_type(1)) ); // feed_price\n\n   // fixed tests\n   obj = new_call_obj( 1751, 1000, mcr ); // order is not in margin call territory\n   expected = 0;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 1751, 1000, mcr, 10000 ); // order is not in margin call territory\n   expected = 0;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 160, 100, mcr ); // target_cr is not set\n   expected = 100;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 1009, 1000, mcr, 200 ); // target_cr set, but order is in black swan territory\n   expected = 1000;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 1499,  999, mcr, 1600 ); // target_cr is 160%, less than 175%, so use 175%\n   expected = 385;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 1500, 1000, mcr, 1800 ); // target_cr is 180%\n   expected = 429;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 1501, 1001, mcr, 2000 ); // target_cr is 200%\n   expected = 558;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   obj = new_call_obj( 1502, 1002, mcr, 3000 ); // target_cr is 300%\n   expected = 793;\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   BOOST_CHECK_EQUAL( result.value, expected );\n   validate_result( obj, mp, fp, mcr, result );\n\n   mcr = 1750;\n   mp = price( asset(40009), asset(79070, asset_id_type(1)) ); // match_price\n   fp = price( asset(40009), asset(86977, asset_id_type(1)) ); // feed_price\n\n   obj = new_call_obj( 557197, 701502, mcr, 1700 ); // target_cr is less than mcr\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   validate_result( obj, mp, fp, mcr, result );\n\n   mcr = 1455;\n   mp = price( asset(1150171), asset(985450, asset_id_type(1)) ); // match_price\n   fp = price( asset(418244), asset(394180, asset_id_type(1)) ); // feed_price\n\n   obj = new_call_obj( 423536, 302688, mcr, 200 ); // target_cr is less than mcr\n   result = obj.get_max_debt_to_cover( mp, fp, mcr );\n   validate_result( obj, mp, fp, mcr, result );\n\n   // random tests\n   std::mt19937_64 gen( time(NULL) );\n   std::uniform_int_distribution<int64_t> amt_uid(1, GRAPHENE_MAX_SHARE_SUPPLY);\n   std::uniform_int_distribution<int64_t> amt_uid2(1, 1000*1000*1000);\n   std::uniform_int_distribution<int64_t> amt_uid3(1, 1000*1000);\n   std::uniform_int_distribution<int64_t> amt_uid4(1, 300);\n   std::uniform_int_distribution<int64_t> mp_num_uid(800, 1100);\n   std::uniform_int_distribution<int16_t> mcr_uid(1001, 32767);\n   std::uniform_int_distribution<int16_t> mcr_uid2(1001, 3000);\n   std::uniform_int_distribution<uint16_t> tcr_uid(0, 65535);\n   std::uniform_int_distribution<uint16_t> tcr_uid2(0, 3000);\n\n   vector<int> count(20,0);\n   int total = 500*1000;\n   for( int i = total; i > 0; --i )\n   {\n      if( i % 9 == 0 )\n         mcr = 1002;\n      else if( i % 3 == 0 )\n         mcr = 1750;\n      else if( i % 3 == 1 )\n         mcr = mcr_uid(gen);\n      else // if( i % 3 == 2 )\n         mcr = mcr_uid2(gen);\n\n      // call_object\n      if( i % 17 <= 0 )\n         obj = new_call_obj( amt_uid(gen), amt_uid(gen), mcr, tcr_uid(gen) );\n      else if( i % 17 <= 2 )\n         obj = new_call_obj( amt_uid2(gen), amt_uid2(gen), mcr, tcr_uid(gen) );\n      else if( i % 17 <= 3 )\n         obj = new_call_obj( amt_uid3(gen), amt_uid3(gen), mcr, tcr_uid(gen) );\n      else if( i % 17 <= 4 )\n         obj = new_call_obj( amt_uid4(gen), amt_uid4(gen), mcr, tcr_uid(gen) );\n      else if( i % 17 <= 5 )\n         obj = new_call_obj( amt_uid(gen), amt_uid(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 7 )\n         obj = new_call_obj( amt_uid2(gen), amt_uid2(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 8 )\n         obj = new_call_obj( amt_uid3(gen), amt_uid3(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 9 )\n         obj = new_call_obj( amt_uid4(gen), amt_uid4(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 11 )\n         obj = new_call_obj( amt_uid3(gen), amt_uid2(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 12 )\n         obj = new_call_obj( amt_uid2(gen), amt_uid3(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 13 )\n         obj = new_call_obj( amt_uid4(gen), amt_uid2(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 14 )\n         obj = new_call_obj( amt_uid2(gen), amt_uid4(gen), mcr, tcr_uid2(gen) );\n      else if( i % 17 <= 15 )\n         obj = new_call_obj( amt_uid3(gen), amt_uid4(gen), mcr, tcr_uid2(gen) );\n      else // if( i % 17 <= 16 )\n         obj = new_call_obj( amt_uid4(gen), amt_uid3(gen), mcr, tcr_uid2(gen) );\n\n      // call_price\n      price cp = price::call_price( obj.get_debt(), obj.get_collateral(), mcr );\n\n      // get feed_price, and make sure we have sufficient good samples\n      int retry = 20;\n      do {\n         if( i % 5 == 0 )\n            fp = price( asset(amt_uid(gen)), asset(amt_uid(gen), asset_id_type(1)) );\n         else if( i % 5 == 1 )\n            fp = price( asset(amt_uid2(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n         else if( i % 5 == 2 )\n            fp = price( asset(amt_uid3(gen)), asset(amt_uid3(gen), asset_id_type(1)) );\n         else if( i % 25 <= 18 )\n            fp = price( asset(amt_uid4(gen)), asset(amt_uid4(gen), asset_id_type(1)) );\n         else if( i % 25 == 19 )\n            fp = price( asset(amt_uid2(gen)), asset(amt_uid3(gen), asset_id_type(1)) );\n         else if( i % 25 == 20 )\n            fp = price( asset(amt_uid3(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n         else if( i % 25 == 21 )\n            fp = price( asset(amt_uid3(gen)), asset(amt_uid4(gen), asset_id_type(1)) );\n         else if( i % 25 == 22 )\n            fp = price( asset(amt_uid4(gen)), asset(amt_uid3(gen), asset_id_type(1)) );\n         else if( i % 25 == 23 )\n            fp = price( asset(amt_uid4(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n         else // if( i % 25 == 24 )\n            fp = price( asset(amt_uid2(gen)), asset(amt_uid4(gen), asset_id_type(1)) );\n         --retry;\n      } while( retry > 0 && ( cp > fp || cp < ( fp / ratio_type( mcr, 1000 ) ) ) );\n\n      // match_price\n      if( i % 16 == 0 )\n         mp = fp * ratio_type( 1001, 1000 );\n      else if( i % 4 == 0 )\n         mp = fp * ratio_type( 1100, 1000 );\n      else if( i % 4 == 1 )\n         mp = fp * ratio_type( mp_num_uid(gen) , 1000 );\n      else if( i % 8 == 4 )\n         mp = price( asset(amt_uid2(gen)), asset(amt_uid3(gen), asset_id_type(1)) );\n      else if( i % 8 == 5 )\n         mp = price( asset(amt_uid3(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n      else if( i % 8 == 6 )\n         mp = price( asset(amt_uid2(gen)), asset(amt_uid2(gen), asset_id_type(1)) );\n      else // if( i % 8 == 7 )\n         mp = price( asset(amt_uid(gen)), asset(amt_uid(gen), asset_id_type(1)) );\n\n      try {\n         result = obj.get_max_debt_to_cover( mp, fp, mcr );\n         auto vr = validate_result( obj, mp, fp, mcr, result, false );\n         ++count[vr];\n      }\n      catch( fc::assert_exception& e )\n      {\n         BOOST_CHECK( e.to_detail_string().find( \"result <= GRAPHENE_MAX_SHARE_SUPPLY\" ) != string::npos );\n         ++count[0];\n      }\n   }\n   ilog( \"count: [bad_input,sell zero,not set,\"\n         \" sell full (perfect), sell full (<0.01%), sell full (<0.1%),sell full (<1%), sell full (other), ...,\"\n         \" sell some (perfect), sell some (<0.01%), sell some (<0.1%),sell some (<1%), sell some (other), ... ]\" );\n   idump( (total)(count) );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/confidential_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <graphene/db/simple_index.hpp>\n\n#include <fc/crypto/digest.hpp>\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\n\nBOOST_FIXTURE_TEST_SUITE( confidential_tests, database_fixture )\nBOOST_AUTO_TEST_CASE( confidential_test )\n{ try {\n   ACTORS( (dan)(nathan) )\n   const asset_object& core = asset_id_type()(db);\n\n   transfer(account_id_type()(db), dan, core.amount(1000000));\n\n   transfer_to_blind_operation to_blind;\n   to_blind.amount = core.amount(1000);\n   to_blind.from   = dan.id;\n\n   auto owner1_key = fc::ecc::private_key::generate();\n   auto owner1_pub = owner1_key.get_public_key();\n   auto owner2_key = fc::ecc::private_key::generate();\n   auto owner2_pub = owner2_key.get_public_key();\n  \n   blind_output out1, out2;\n   out1.owner = authority( 1, public_key_type(owner1_pub), 1 );\n   out2.owner = authority( 1, public_key_type(owner2_pub), 1 );\n\n\n   auto InB1  = fc::sha256::hash(\"InB1\");\n   auto InB2  = fc::sha256::hash(\"InB2\");\n   auto nonce1 = fc::sha256::hash(\"nonce\");\n   auto nonce2 = fc::sha256::hash(\"nonce2\");\n\n   out1.commitment  = fc::ecc::blind(InB1,250);\n   out1.range_proof = fc::ecc::range_proof_sign( 0, out1.commitment, InB1, nonce1, 0, 0, 250 );\n\n   out2.commitment = fc::ecc::blind(InB2,750);\n   out2.range_proof = fc::ecc::range_proof_sign( 0, out2.commitment, InB1, nonce2, 0, 0, 750 );\n\n   to_blind.blinding_factor = fc::ecc::blind_sum( {InB1,InB2}, 2 );\n   to_blind.outputs = {out2,out1};\n\n   trx.operations = {to_blind};\n   sign( trx,  dan_private_key  );\n   PUSH_TX(db, trx);\n   trx.clear_signatures();\n\n   BOOST_TEST_MESSAGE( \"Transfering from blind to blind with change address\" );\n   auto Out3B  = fc::sha256::hash(\"Out3B\");\n   auto Out4B  = fc::ecc::blind_sum( {InB2,Out3B}, 1 ); // add InB2 - Out3b\n   blind_output out3, out4;\n   out3.commitment = fc::ecc::blind(Out3B,300);\n   out3.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 300 );\n   out4.commitment = fc::ecc::blind(Out4B,750-300-10);\n   out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-10 );\n\n\n   blind_transfer_operation blind_tr;\n   blind_tr.fee = core.amount(10);\n   blind_tr.inputs.push_back( {out2.commitment, out2.owner} );\n   blind_tr.outputs = {out3,out4};\n   blind_tr.validate();\n   trx.operations = {blind_tr};\n   sign( trx,  owner2_key  );\n   PUSH_TX(db, trx);\n\n   BOOST_TEST_MESSAGE( \"Attempting to double spend the same commitments\" );\n   blind_tr.fee = core.amount(11);\n\n   Out4B  = fc::ecc::blind_sum( {InB2,Out3B}, 1 ); // add InB2 - Out3b\n   out4.commitment = fc::ecc::blind(Out4B,750-300-11);\n   auto out4_amount = 750-300-10;\n   out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-11 );\n   blind_tr.outputs = {out4,out3};\n   trx.operations = {blind_tr};\n   BOOST_REQUIRE_THROW( PUSH_TX(db, trx, ~0), graphene::chain::blind_transfer_unknown_commitment );\n\n\n   BOOST_TEST_MESSAGE( \"Transfering from blind to nathan public\" );\n   out4.commitment = fc::ecc::blind(Out4B,750-300-10);\n\n   transfer_from_blind_operation from_blind;\n   from_blind.fee = core.amount(10);\n   from_blind.to  = nathan.id;\n   from_blind.amount = core.amount( out4_amount - 10 );\n   from_blind.blinding_factor = Out4B;\n   from_blind.inputs.push_back( {out4.commitment, out4.owner} );\n   trx.operations = {from_blind};\n   trx.clear_signatures();\n   PUSH_TX(db, trx);\n\n   BOOST_REQUIRE_EQUAL( get_balance( nathan, core ), 750-300-10-10 );\n\n} FC_LOG_AND_RETHROW() }\n\n\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/credit_offer_tests.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/credit_offer_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( credit_offer_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( credit_offer_hardfork_time_test )\n{ try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_2262_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( sam, usd.amount(init_amount) );\n\n      // Before the hard fork, unable to create a credit offer or transact against a credit offer or a credit deal,\n      // or do any of them with proposals\n      flat_map<asset_id_type, price> collateral_map;\n      collateral_map[usd_id] = price( asset(1), asset(1, usd_id) );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              db.head_block_time() + fc::days(1), collateral_map, {} ),\n                         fc::exception );\n\n      credit_offer_id_type tmp_co_id;\n      credit_deal_id_type  tmp_cd_id;\n      BOOST_CHECK_THROW( delete_credit_offer( sam_id, tmp_co_id ), fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, tmp_co_id, core.amount(100), 200, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( borrow_from_credit_offer( sam_id, tmp_co_id, core.amount(100), usd.amount(1000) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( repay_credit_deal( sam_id, tmp_cd_id, core.amount(100), core.amount(100) ),\n                         fc::exception );\n\n      credit_offer_create_operation cop = make_credit_offer_create_op( sam_id, core.get_id(), 10000, 100, 3600, 0,\n                                              false, db.head_block_time() + fc::days(1), collateral_map, {} );\n      BOOST_CHECK_THROW( propose( cop ), fc::exception );\n\n      credit_offer_delete_operation dop = make_credit_offer_delete_op( sam_id, tmp_co_id );\n      BOOST_CHECK_THROW( propose( dop ), fc::exception );\n\n      credit_offer_update_operation uop = make_credit_offer_update_op( sam_id, tmp_co_id, core.amount(100), 200,\n                                                                       {}, {}, {}, {}, {}, {} );\n      BOOST_CHECK_THROW( propose( uop ), fc::exception );\n\n      credit_offer_accept_operation aop = make_credit_offer_accept_op( sam_id, tmp_co_id, core.amount(100),\n                                                                       usd.amount(1000) );\n      BOOST_CHECK_THROW( propose( aop ), fc::exception );\n\n      credit_deal_repay_operation rop = make_credit_deal_repay_op( sam_id, tmp_cd_id, core.amount(100),\n                                                                   core.amount(100) );\n      BOOST_CHECK_THROW( propose( rop ), fc::exception );\n\n      credit_deal_expired_operation eop( tmp_cd_id, tmp_co_id, sam_id, account_id_type(),\n                                         core.amount(1), usd.amount(2), 1 );\n      BOOST_CHECK_THROW( eop.validate(), fc::exception );\n      BOOST_CHECK_THROW( propose( eop ), fc::exception );\n\n      trx.operations.clear();\n      trx.operations.push_back( eop );\n\n      for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n      BOOST_CHECK_THROW( trx.validate(), fc::exception );\n      set_expiration( db, trx );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( credit_deal_auto_repay_hardfork_time_test )\n{ try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_2362_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((ray)(sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( ray, asset(init_amount) );\n      fund( sam, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, white_list );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( ray, usd.amount(init_amount) );\n      issue_uia( sam, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( ray, eur.amount(init_amount) );\n      issue_uia( sam, eur.amount(init_amount) );\n\n      // create a credit offer\n      auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init\n\n      flat_map<asset_id_type, price> collateral_map1;\n      collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) );\n      collateral_map1[eur_id] = price( asset(1), asset(1, eur_id) );\n\n      const credit_offer_object& coo1 = create_credit_offer( sam_id, core.get_id(), 10000, 30000, 3600, 0, true,\n                                              disable_time1, collateral_map1, {} );\n      credit_offer_id_type co1_id = coo1.get_id();\n\n      // Before the hard fork, unable to borrow with the \"auto-repay\" extension field enabled\n      // or propose to do so\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 1 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ),\n                         fc::exception );\n\n      credit_offer_accept_operation accop = make_credit_offer_accept_op( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 0 );\n      BOOST_CHECK_THROW( propose( accop ), fc::exception );\n\n      accop = make_credit_offer_accept_op( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 1 );\n      BOOST_CHECK_THROW( propose( accop ), fc::exception );\n\n      accop = make_credit_offer_accept_op( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 );\n      BOOST_CHECK_THROW( propose( accop ), fc::exception );\n\n      // borrow\n      BOOST_TEST_MESSAGE( \"Ray borrows\" );\n      const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(100),\n                                                   asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, {} );\n      credit_deal_id_type cd11_id = cdo11.get_id();\n\n      BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 0U );\n\n      // Before the hard fork, unable to update a credit deal\n      // or update with proposals\n      BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id, 1 ), fc::exception );\n\n      credit_deal_update_operation updop = make_credit_deal_update_op( ray_id, cd11_id, 1 );\n      BOOST_CHECK_THROW( propose( updop ), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( credit_offer_crud_and_proposal_test )\n{ try {\n\n      // Pass the hard fork time\n      if( hf2595 )\n         generate_blocks( HARDFORK_CORE_2595_TIME );\n      else\n         generate_blocks( HARDFORK_CORE_2362_TIME );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted)(por));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = eur.id;\n         uop.issuer = sam_id;\n         uop.new_options = eur.options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Sam to the whitelist, but do not add others\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      asset_id_type no_asset_id( core.id + 100 );\n      BOOST_REQUIRE( !db.find( no_asset_id ) );\n\n      account_id_type no_account_id( sam.id + 1000 );\n      BOOST_REQUIRE( !db.find( no_account_id ) );\n\n      // Able to propose\n      credit_offer_id_type tmp_co_id;\n      credit_deal_id_type  tmp_cd_id;\n      {\n         flat_map<asset_id_type, price> collateral_map;\n         collateral_map[usd_id] = price( asset(1), asset(1, usd_id) );\n\n         credit_offer_create_operation cop = make_credit_offer_create_op( sam_id, core.get_id(), 10000, 100, 3600, 0,\n                                              false, db.head_block_time() + fc::days(1), collateral_map, {} );\n         propose( cop );\n\n         credit_offer_delete_operation dop = make_credit_offer_delete_op( sam_id, tmp_co_id );\n         propose( dop );\n\n         credit_offer_update_operation uop = make_credit_offer_update_op( sam_id, tmp_co_id, core.amount(100), 200,\n                                                                       {}, {}, {}, {}, {}, {} );\n         propose( uop );\n\n         credit_offer_accept_operation aop = make_credit_offer_accept_op( sam_id, tmp_co_id, core.amount(100),\n                                                                       usd.amount(1000) );\n         propose( aop );\n\n         credit_deal_repay_operation rop = make_credit_deal_repay_op( sam_id, tmp_cd_id, core.amount(100),\n                                                                   core.amount(100) );\n         propose( rop );\n      }\n\n      // Test virtual operation\n      {\n         credit_deal_expired_operation eop( tmp_cd_id, tmp_co_id, sam_id, account_id_type(),\n                                         core.amount(1), usd.amount(2), 1 );\n         BOOST_CHECK_THROW( eop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( eop ), fc::exception );\n\n         trx.operations.clear();\n         trx.operations.push_back( eop );\n\n         for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n         BOOST_CHECK_THROW( trx.validate(), fc::exception );\n         set_expiration( db, trx );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      }\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_sam_usd = init_amount;\n      int64_t expected_balance_sam_eur = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_ted_usd = init_amount;\n      int64_t expected_balance_ted_eur = init_amount;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, 0 );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 0 );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, eur_id ).amount.value, 0 );\n      };\n\n      check_balances();\n\n      // Able to create credit offers with valid data\n      // 1.\n      auto disable_time1 = db.head_block_time() - fc::minutes(1); // a time in the past\n\n      flat_map<asset_id_type, price> collateral_map1;\n      collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) );\n\n      const credit_offer_object& coo1 = create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, collateral_map1, {} );\n      credit_offer_id_type co1_id = coo1.get_id();\n      BOOST_CHECK( coo1.owner_account == sam_id );\n      BOOST_CHECK( coo1.asset_type == core.id );\n      BOOST_CHECK( coo1.total_balance == 10000 );\n      BOOST_CHECK( coo1.current_balance == 10000 );\n      BOOST_CHECK( coo1.fee_rate == 100u );\n      BOOST_CHECK( coo1.max_duration_seconds == 3600u );\n      BOOST_CHECK( coo1.min_deal_amount == 0 );\n      BOOST_CHECK( coo1.enabled == false );\n      BOOST_CHECK( coo1.auto_disable_time == disable_time1 );\n      BOOST_CHECK( coo1.acceptable_collateral == collateral_map1 );\n      BOOST_CHECK( coo1.acceptable_borrowers.empty() );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // 2.\n      auto duration2 = GRAPHENE_MAX_CREDIT_DEAL_SECS;\n      auto disable_time2 = db.head_block_time() + fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS);\n\n      flat_map<asset_id_type, price> collateral_map2;\n      collateral_map2[core_id] = price( asset(2, usd_id), asset(3) );\n      collateral_map2[eur_id] = price( asset(3, usd_id), asset(4, eur_id) );\n\n      flat_map<account_id_type, share_type> borrower_map2;\n      borrower_map2[account_id_type()] = 0;\n      borrower_map2[sam_id] = 1;\n      borrower_map2[ted_id] = GRAPHENE_MAX_SHARE_SUPPLY;\n\n      const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, borrower_map2 );\n      credit_offer_id_type co2_id = coo2.get_id();\n      BOOST_CHECK( coo2.owner_account == ted_id );\n      BOOST_CHECK( coo2.asset_type == usd_id );\n      BOOST_CHECK( coo2.total_balance == 1 );\n      BOOST_CHECK( coo2.current_balance == 1 );\n      BOOST_CHECK( coo2.fee_rate == 10000000u );\n      BOOST_CHECK( coo2.max_duration_seconds == duration2 );\n      BOOST_CHECK( coo2.min_deal_amount == 10000 );\n      BOOST_CHECK( coo2.enabled == true );\n      BOOST_CHECK( coo2.auto_disable_time == disable_time2 );\n      BOOST_CHECK( coo2.acceptable_collateral == collateral_map2 );\n      BOOST_CHECK( coo2.acceptable_borrowers == borrower_map2 );\n\n      expected_balance_ted_usd -= 1;\n      check_balances();\n\n      // 3.\n      // A time far in the future\n      auto disable_time3 = db.head_block_time() + fc::seconds(GRAPHENE_MAX_CREDIT_OFFER_SECS + 1);\n\n      flat_map<asset_id_type, price> collateral_map3;\n      collateral_map3[usd_id] = price( asset(1, eur_id), asset(2, usd_id) );\n\n      const credit_offer_object& coo3 = create_credit_offer( sam_id, eur_id, 10, 1, 30, 1, false,\n                                              disable_time3, collateral_map3, {} ); // Account is whitelisted\n      credit_offer_id_type co3_id = coo3.get_id();\n      BOOST_CHECK( coo3.owner_account == sam_id );\n      BOOST_CHECK( coo3.asset_type == eur_id );\n      BOOST_CHECK( coo3.total_balance == 10 );\n      BOOST_CHECK( coo3.current_balance == 10 );\n      BOOST_CHECK( coo3.fee_rate == 1u );\n      BOOST_CHECK( coo3.max_duration_seconds == 30u );\n      BOOST_CHECK( coo3.min_deal_amount == 1 );\n      BOOST_CHECK( coo3.enabled == false );\n      BOOST_CHECK( coo3.auto_disable_time == disable_time3 );\n      BOOST_CHECK( coo3.acceptable_collateral == collateral_map3 );\n      BOOST_CHECK( coo3.acceptable_borrowers.empty() );\n\n      expected_balance_sam_eur -= 10;\n      check_balances();\n\n      // Unable to create a credit offer with invalid data\n      auto too_big_duration = GRAPHENE_MAX_CREDIT_DEAL_SECS + 1;\n      auto too_late_disable_time = db.head_block_time() + fc::seconds(GRAPHENE_MAX_CREDIT_OFFER_SECS + 1);\n\n      flat_map<asset_id_type, price> empty_collateral_map;\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_1;\n      invalid_collateral_map1_1[usd_id] = price( asset(1), asset(0, usd_id) ); // zero amount\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_2;\n      invalid_collateral_map1_2[usd_id] = price( asset(1), asset(2, eur_id) ); // quote asset type mismatch\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_3;\n      invalid_collateral_map1_3[usd_id] = price( asset(1), asset(2, usd_id) );\n      invalid_collateral_map1_3[eur_id] = price( asset(1), asset(2, usd_id) ); // quote asset type mismatch\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_4; // amount too big\n      invalid_collateral_map1_4[usd_id] = price( asset(GRAPHENE_MAX_SHARE_SUPPLY + 1), asset(1, usd_id) );\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_5;\n      invalid_collateral_map1_5[usd_id] = price( asset(2, usd_id), asset(1) ); // base asset type mismatch\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_6;\n      invalid_collateral_map1_6[usd_id] = price( asset(1), asset(2, usd_id) );\n      invalid_collateral_map1_6[eur_id] = price( asset(1, usd_id), asset(2, eur_id) ); // base asset type mismatch\n\n      flat_map<asset_id_type, price> invalid_collateral_map1_7;\n      invalid_collateral_map1_7[no_asset_id] = price( asset(1), asset(2, no_asset_id) ); // asset does not exist\n\n      auto invalid_borrower_map2_1 = borrower_map2;\n      invalid_borrower_map2_1[sam_id] = -1; // negative amount\n\n      auto invalid_borrower_map2_2 = borrower_map2;\n      invalid_borrower_map2_2[ted_id] = GRAPHENE_MAX_SHARE_SUPPLY + 1; // amount too big\n\n      auto invalid_borrower_map2_3 = borrower_map2;\n      invalid_borrower_map2_3[no_account_id] = 1; // account does not exist\n\n      // Non-positive balance\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 0, 100, 3600, 0, false,\n                                              disable_time1, collateral_map1, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, -1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, borrower_map2 ),\n                         fc::exception );\n      // Insufficient account balance\n      BOOST_CHECK_THROW( create_credit_offer( por_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, borrower_map2 ),\n                         fc::exception );\n      // Nonexistent asset type\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, no_asset_id, 10000, 100, 3600, 0, false,\n                                              disable_time1, collateral_map1, {} ),\n                         fc::exception );\n      // Duration too big\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, too_big_duration, 10000, true,\n                                              disable_time2, collateral_map2, borrower_map2 ),\n                         fc::exception );\n      // Negative minimum deal amount\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, -1, false,\n                                              disable_time1, collateral_map1, {} ),\n                         fc::exception );\n      // Too big minimum deal amount\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, GRAPHENE_MAX_SHARE_SUPPLY + 1,\n                                              false, disable_time1, collateral_map1, {} ),\n                         fc::exception );\n      // Auto-disable time in the past and the offer is enabled\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, true,\n                                              disable_time1, collateral_map1, {} ),\n                         fc::exception );\n      // Auto-disable time too late\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              too_late_disable_time, collateral_map2, borrower_map2 ),\n                         fc::exception );\n      // Empty allowed collateral map\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, empty_collateral_map, {} ),\n                         fc::exception );\n      // Invalid allowed collateral map\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_1, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_2, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_3, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_4, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_5, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_6, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, core.get_id(), 10000, 100, 3600, 0, false,\n                                              disable_time1, invalid_collateral_map1_7, {} ),\n                         fc::exception );\n      // Invalid acceptable borrowers map\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, invalid_borrower_map2_1 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, invalid_borrower_map2_2 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true,\n                                              disable_time2, collateral_map2, invalid_borrower_map2_3 ),\n                         fc::exception );\n      // Account is not whitelisted\n      BOOST_CHECK_THROW( create_credit_offer( ted_id, eur_id, 10, 1, 30, 1, false,\n                                              disable_time3, collateral_map3, {} ),\n                         fc::exception );\n\n      check_balances();\n\n      // Uable to update a credit offer with invalid data\n      // Changes nothing\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Object owner mismatch\n      BOOST_CHECK_THROW( update_credit_offer( ted_id, co1_id, asset(1), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Zero delta\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(0), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Asset type mismatch\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(1, usd_id), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Trying to withdraw too much\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(-10000), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Insufficient account balance\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(init_amount), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Duration too big\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, too_big_duration, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Invalid minimum deal amount\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, -1, {}, {}, {}, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {},\n                                              GRAPHENE_MAX_SHARE_SUPPLY + 1, {}, {}, {}, {} ),\n                         fc::exception );\n      // Enabled but auto-disable time in the past\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, true, {}, {}, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( ted_id, co2_id, {}, {}, {}, {}, {}, disable_time1, {}, {} ),\n                         fc::exception );\n      // Enabled but auto-disable time too late\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co3_id, {}, {}, {}, {}, true, {}, {}, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( ted_id, co2_id, {}, {}, {}, {}, {}, disable_time3, {}, {} ),\n                         fc::exception );\n      // Invalid collateral map\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, empty_collateral_map, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_1, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_2, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_3, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_4, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_5, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_6, {} ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_7, {} ),\n                         fc::exception );\n      // Invalid borrowers map\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, invalid_borrower_map2_1 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, invalid_borrower_map2_2 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, invalid_borrower_map2_3 ),\n                         fc::exception );\n\n      check_balances();\n\n      // Able to update a credit offer with valid data\n      // Only deposit\n      update_credit_offer( sam_id, co1_id, asset(1), {}, {}, {}, {}, {}, {}, {} );\n\n      BOOST_CHECK( co1_id(db).owner_account == sam_id );\n      BOOST_CHECK( co1_id(db).asset_type == core_id );\n      BOOST_CHECK( co1_id(db).total_balance == 10001 );\n      BOOST_CHECK( co1_id(db).current_balance == 10001 );\n      BOOST_CHECK( co1_id(db).fee_rate == 100u );\n      BOOST_CHECK( co1_id(db).max_duration_seconds == 3600u );\n      BOOST_CHECK( co1_id(db).min_deal_amount == 0 );\n      BOOST_CHECK( co1_id(db).enabled == false );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n      BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1 );\n      BOOST_CHECK( co1_id(db).acceptable_borrowers.empty() );\n\n      expected_balance_sam_core -= 1;\n      check_balances();\n\n      // Only update fee rate\n      update_credit_offer( sam_id, co1_id, {}, 101u, {}, {}, {}, {}, {}, {} );\n\n      BOOST_CHECK( co1_id(db).owner_account == sam_id );\n      BOOST_CHECK( co1_id(db).asset_type == core_id );\n      BOOST_CHECK( co1_id(db).total_balance == 10001 );\n      BOOST_CHECK( co1_id(db).current_balance == 10001 );\n      BOOST_CHECK( co1_id(db).fee_rate == 101u );\n      BOOST_CHECK( co1_id(db).max_duration_seconds == 3600u );\n      BOOST_CHECK( co1_id(db).min_deal_amount == 0 );\n      BOOST_CHECK( co1_id(db).enabled == false );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n      BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1 );\n      BOOST_CHECK( co1_id(db).acceptable_borrowers.empty() );\n\n      check_balances();\n\n      // Withdraw, update fee rate and other data\n      flat_map<asset_id_type, price> collateral_map1_1;\n      collateral_map1_1[usd_id] = price( asset(1), asset(2, usd_id) );\n      collateral_map1_1[eur_id] = price( asset(1), asset(3, eur_id) );\n\n      update_credit_offer( sam_id, co1_id, asset(-9999), 10u, 600u, 100, true,\n                           db.head_block_time() + fc::days(10), collateral_map1_1, borrower_map2 );\n\n      BOOST_CHECK( co1_id(db).owner_account == sam_id );\n      BOOST_CHECK( co1_id(db).asset_type == core_id );\n      BOOST_CHECK( co1_id(db).total_balance == 2 );\n      BOOST_CHECK( co1_id(db).current_balance == 2 );\n      BOOST_CHECK( co1_id(db).fee_rate == 10u );\n      BOOST_CHECK( co1_id(db).max_duration_seconds == 600u );\n      BOOST_CHECK( co1_id(db).min_deal_amount == 100 );\n      BOOST_CHECK( co1_id(db).enabled == true );\n      BOOST_CHECK( co1_id(db).auto_disable_time == db.head_block_time() + fc::days(10) );\n      BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1_1 );\n      BOOST_CHECK( co1_id(db).acceptable_borrowers == borrower_map2 );\n\n      expected_balance_sam_core += 9999;\n      check_balances();\n\n      // Sam is able to delete his own credit offer\n      asset released = delete_credit_offer( sam_id, co1_id );\n\n      BOOST_REQUIRE( !db.find( co1_id ) );\n      BOOST_REQUIRE( db.find( co2_id ) );\n      BOOST_REQUIRE( db.find( co3_id ) );\n\n      BOOST_CHECK( released == asset( 2, core_id ) );\n\n      expected_balance_sam_core += 2;\n      check_balances();\n\n      // Unable to update a credit offer that does not exist\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(1), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n      // Unable to delete a credit offer that does not exist\n      BOOST_CHECK_THROW( delete_credit_offer( sam_id, co1_id ), fc::exception );\n      // Unable to delete a credit offer that is not owned by him\n      BOOST_CHECK_THROW( delete_credit_offer( sam_id, co2_id ), fc::exception );\n\n      BOOST_REQUIRE( !db.find( co1_id ) );\n      BOOST_REQUIRE( db.find( co2_id ) );\n      BOOST_REQUIRE( db.find( co3_id ) );\n\n      check_balances();\n\n      {\n         // Add Ted to the whitelist and remove Sam\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = ted_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::no_listing;\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam is now unable to deposit to the credit offer\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co3_id, asset(1, eur_id), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n\n      BOOST_CHECK( co3_id(db).owner_account == sam_id );\n      BOOST_CHECK( co3_id(db).asset_type == eur_id );\n      BOOST_CHECK( co3_id(db).total_balance == 10 );\n      BOOST_CHECK( co3_id(db).current_balance == 10 );\n      BOOST_CHECK( co3_id(db).fee_rate == 1u );\n      BOOST_CHECK( co3_id(db).max_duration_seconds == 30u );\n      BOOST_CHECK( co3_id(db).min_deal_amount == 1 );\n      BOOST_CHECK( co3_id(db).enabled == false );\n      BOOST_CHECK( co3_id(db).auto_disable_time == disable_time3 );\n      BOOST_CHECK( co3_id(db).acceptable_collateral == collateral_map3 );\n      BOOST_CHECK( co3_id(db).acceptable_borrowers.empty() );\n\n      check_balances();\n\n      // Sam is still able to withdraw from the credit offer\n      update_credit_offer( sam_id, co3_id, asset(-1, eur_id), {}, {}, {}, {}, {}, {}, {} );\n\n      BOOST_CHECK( co3_id(db).owner_account == sam_id );\n      BOOST_CHECK( co3_id(db).asset_type == eur_id );\n      BOOST_CHECK( co3_id(db).total_balance == 9 );\n      BOOST_CHECK( co3_id(db).current_balance == 9 );\n      BOOST_CHECK( co3_id(db).fee_rate == 1u );\n      BOOST_CHECK( co3_id(db).max_duration_seconds == 30u );\n      BOOST_CHECK( co3_id(db).min_deal_amount == 1 );\n      BOOST_CHECK( co3_id(db).enabled == false );\n      BOOST_CHECK( co3_id(db).auto_disable_time == disable_time3 );\n      BOOST_CHECK( co3_id(db).acceptable_collateral == collateral_map3 );\n      BOOST_CHECK( co3_id(db).acceptable_borrowers.empty() );\n\n      expected_balance_sam_eur += 1;\n      check_balances();\n\n      // Sam is still able to update other data\n      flat_map<asset_id_type, price> collateral_map3_1;\n      collateral_map3_1[core_id] = price( asset(2, eur_id), asset(5, core_id) );\n\n      update_credit_offer( sam_id, co3_id, {}, 10u, 600u, 100, true,\n                           disable_time2, collateral_map3_1, borrower_map2 );\n\n      BOOST_CHECK( co3_id(db).owner_account == sam_id );\n      BOOST_CHECK( co3_id(db).asset_type == eur_id );\n      BOOST_CHECK( co3_id(db).total_balance == 9 );\n      BOOST_CHECK( co3_id(db).current_balance == 9 );\n      BOOST_CHECK( co3_id(db).fee_rate == 10u );\n      BOOST_CHECK( co3_id(db).max_duration_seconds == 600u );\n      BOOST_CHECK( co3_id(db).min_deal_amount == 100 );\n      BOOST_CHECK( co3_id(db).enabled == true );\n      BOOST_CHECK( co3_id(db).auto_disable_time == disable_time2 );\n      BOOST_CHECK( co3_id(db).acceptable_collateral == collateral_map3_1 );\n      BOOST_CHECK( co3_id(db).acceptable_borrowers == borrower_map2 );\n\n      check_balances();\n\n      // Sam is still able to delete the credit offer\n      released = delete_credit_offer( sam_id, co3_id );\n      BOOST_REQUIRE( !db.find( co3_id ) );\n\n      BOOST_CHECK( released == asset( 9, eur_id ) );\n\n      expected_balance_sam_eur += 9;\n      check_balances();\n\n      // Sam is unable to recreate the credit offer\n      BOOST_CHECK_THROW( create_credit_offer( sam_id, eur_id, 10, 1, 30, 1, false,\n                                              disable_time3, collateral_map3, {} ),\n                         fc::exception );\n      check_balances();\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test )\n{ try {\n\n      // Pass the hard fork time\n      if( hf2595 )\n         generate_blocks( HARDFORK_CORE_2595_TIME );\n      else\n         generate_blocks( HARDFORK_CORE_2362_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((ray)(sam)(ted)(por));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( ray, asset(init_amount) );\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, white_list );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( ray, usd.amount(init_amount) );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( ray, eur.amount(init_amount) );\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n\n      const asset_object& cny = create_user_issued_asset( \"MYCNY\" );\n      asset_id_type cny_id = cny.get_id();\n      issue_uia( ray, cny.amount(init_amount) );\n      issue_uia( sam, cny.amount(init_amount) );\n      issue_uia( ted, cny.amount(init_amount) );\n\n      // Make a whitelist USD managed by Ted\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = usd.id;\n         uop.issuer = ted_id;\n         uop.new_options = usd.options;\n         // The whitelist is managed by Ted\n         uop.new_options.whitelist_authorities.insert(ted_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Ted so that he can manage the whitelist\n         upgrade_to_lifetime_member( ted_id );\n\n         // Add Sam and Ray to the whitelist\n         account_whitelist_operation wop;\n         wop.authorizing_account = ted_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         wop.account_to_list = ray_id;\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Make a whitelist : EUR managed by Sam\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = eur.id;\n         uop.issuer = sam_id;\n         uop.new_options = eur.options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Ted to the whitelist\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = ted_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      asset_id_type no_asset_id( core.id + 100 );\n      BOOST_REQUIRE( !db.find( no_asset_id ) );\n\n      int64_t expected_balance_ray_core = init_amount;\n      int64_t expected_balance_ray_usd = init_amount;\n      int64_t expected_balance_ray_eur = init_amount;\n      int64_t expected_balance_ray_cny = init_amount;\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_sam_usd = init_amount;\n      int64_t expected_balance_sam_eur = init_amount;\n      int64_t expected_balance_sam_cny = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_ted_usd = init_amount;\n      int64_t expected_balance_ted_eur = init_amount;\n      int64_t expected_balance_ted_cny = init_amount;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( ray_id, core_id ).amount.value, expected_balance_ray_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ray_id, usd_id ).amount.value, expected_balance_ray_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ray_id, eur_id ).amount.value, expected_balance_ray_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ray_id, cny_id ).amount.value, expected_balance_ray_cny );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, cny_id ).amount.value, expected_balance_sam_cny );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, cny_id ).amount.value, expected_balance_ted_cny );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, 0 );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 0 );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, eur_id ).amount.value, 0 );\n         BOOST_CHECK_EQUAL( db.get_balance( por_id, cny_id ).amount.value, 0 );\n      };\n\n      check_balances();\n\n      // Unable to borrow : the credit offer does not exist\n      credit_offer_id_type tmp_co_id;\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, tmp_co_id, asset(100), asset(200, usd_id) ),\n                         fc::exception );\n\n      // create credit offers\n      auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init\n\n      flat_map<asset_id_type, price> collateral_map1;\n      collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) );\n      collateral_map1[eur_id] = price( asset(1), asset(1, eur_id) );\n\n      const credit_offer_object& coo1 = create_credit_offer( sam_id, core.get_id(), 10000, 30000, 3600, 0, false,\n                                              disable_time1, collateral_map1, {} );\n      credit_offer_id_type co1_id = coo1.get_id();\n      BOOST_CHECK( co1_id(db).owner_account == sam_id );\n      BOOST_CHECK( co1_id(db).asset_type == core.id );\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 10000 );\n      BOOST_CHECK( co1_id(db).fee_rate == 30000u );\n      BOOST_CHECK( co1_id(db).max_duration_seconds == 3600u );\n      BOOST_CHECK( co1_id(db).min_deal_amount == 0 );\n      BOOST_CHECK( co1_id(db).enabled == false );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n      BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1 );\n      BOOST_CHECK( co1_id(db).acceptable_borrowers.empty() );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // Unable to borrow : the credit offer is disabled\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) ), fc::exception );\n\n      // Enable the offer\n      update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, true, {}, {}, {} );\n\n      BOOST_CHECK( co1_id(db).enabled == true );\n\n      // Now able to borrow\n      BOOST_TEST_MESSAGE( \"Ray borrows\" );\n      const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) );\n      credit_deal_id_type cd11_id = cdo11.get_id();\n      time_point_sec expected_repay_time11 = db.head_block_time() + fc::seconds(3600); // 60 minutes after init\n\n      BOOST_CHECK( cd11_id(db).borrower == ray_id );\n      BOOST_CHECK( cd11_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd11_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd11_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd11_id(db).debt_amount == 100 );\n      BOOST_CHECK( cd11_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd11_id(db).collateral_amount == 200 );\n      BOOST_CHECK( cd11_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd11_id(db).latest_repay_time == expected_repay_time11 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 9900 );\n\n      expected_balance_ray_core += 100;\n      expected_balance_ray_usd -= 200;\n      check_balances();\n\n      // Unable to delete the credit offer : there exists unpaid debt\n      BOOST_CHECK_THROW( delete_credit_offer( sam_id, co1_id ), fc::exception );\n      // Unable to withdraw more than balance available\n      BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(-9901), {}, {}, {}, {}, {}, {}, {} ),\n                         fc::exception );\n\n      // Unable to borrow : asset type mismatch\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100, cny_id), asset(200, usd_id) ),\n                         fc::exception );\n      // Unable to borrow : zero or negative amount\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(0), asset(200, usd_id) ), fc::exception );\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(-1), asset(200, usd_id) ), fc::exception );\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(1), asset(0, usd_id) ), fc::exception );\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(1), asset(-1, usd_id) ), fc::exception );\n\n      // Set a minimum deal amount\n      update_credit_offer( sam_id, co1_id, {}, {}, {}, 100, {}, {}, {}, {} );\n\n      BOOST_CHECK( co1_id(db).min_deal_amount == 100 );\n\n      // Unable to borrow : amount too small\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(99), asset(200, usd_id) ), fc::exception );\n      // Unable to borrow : collateral amount too small\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(199, usd_id) ), fc::exception );\n      // Unable to borrow : collateral not acceptable\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, cny_id) ), fc::exception );\n      // Unable to borrow : account not authorized by debt asset\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(100), asset(200, usd_id) ), fc::exception );\n      // Unable to borrow : account not authorized by collateral asset\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, eur_id) ), fc::exception );\n      // Unable to borrow : insufficient balance in credit offer\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(9901), asset(20000, usd_id) ),\n                         fc::exception );\n      // Unable to borrow : insufficient account balance\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(init_amount, usd_id) ),\n                         fc::exception );\n\n      // Unable to borrow : maximum acceptable fee rate too low\n      auto ok_fee_rate = co1_id(db).fee_rate;\n      auto low_fee_rate = co1_id(db).fee_rate - 1;\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id), low_fee_rate ),\n                         fc::exception );\n\n      // Unable to borrow : minimum acceptable duration too long\n      auto ok_duration = co1_id(db).max_duration_seconds;\n      auto long_duration = co1_id(db).max_duration_seconds + 1;\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id), ok_fee_rate,\n                                                   long_duration ),\n                         fc::exception );\n\n      // Able to borrow the same amount with the same collateral\n      BOOST_TEST_MESSAGE( \"Ray borrows more\" );\n      const credit_deal_object& cdo12 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id),\n                                                                  ok_fee_rate, ok_duration );\n      credit_deal_id_type cd12_id = cdo12.get_id();\n      time_point_sec expected_repay_time12 = db.head_block_time() + fc::seconds(3600);  // 60 minutes after init\n\n      BOOST_CHECK( cd12_id(db).borrower == ray_id );\n      BOOST_CHECK( cd12_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd12_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd12_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd12_id(db).debt_amount == 100 );\n      BOOST_CHECK( cd12_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd12_id(db).collateral_amount == 200 );\n      BOOST_CHECK( cd12_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd12_id(db).latest_repay_time == expected_repay_time12 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 9800 );\n\n      expected_balance_ray_core += 100;\n      expected_balance_ray_usd -= 200;\n      check_balances();\n\n      // Time goes by\n      generate_blocks( db.head_block_time() + fc::minutes(5) ); // now is 5 minutes after init\n      set_expiration( db, trx );\n\n      // Able to borrow the same amount with more collateral\n      BOOST_TEST_MESSAGE( \"Ray borrows even more\" );\n      const credit_deal_object& cdo13 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(499, usd_id) );\n      credit_deal_id_type cd13_id = cdo13.get_id();\n      time_point_sec expected_repay_time13 = db.head_block_time() + fc::seconds(3600); // 65 minutes after init\n\n      BOOST_CHECK( cd13_id(db).borrower == ray_id );\n      BOOST_CHECK( cd13_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd13_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd13_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd13_id(db).debt_amount == 100 );\n      BOOST_CHECK( cd13_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd13_id(db).collateral_amount == 499 );\n      BOOST_CHECK( cd13_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 9700 );\n\n      expected_balance_ray_core += 100;\n      expected_balance_ray_usd -= 499;\n      check_balances();\n\n      // The offer changes\n      auto collateral_map1_new = collateral_map1;\n      collateral_map1_new[cny_id] = price( asset(1), asset(1, cny_id) );\n      BOOST_CHECK( collateral_map1 != collateral_map1_new );\n\n      flat_map<account_id_type, share_type> borrower_map1;\n      borrower_map1[ted_id] = 300;\n\n      update_credit_offer( sam_id, co1_id, {}, 500u, 600u, 0, {}, {}, collateral_map1_new, borrower_map1 );\n\n      BOOST_CHECK( co1_id(db).owner_account == sam_id );\n      BOOST_CHECK( co1_id(db).asset_type == core_id );\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 9700 );\n      BOOST_CHECK( co1_id(db).fee_rate == 500u );\n      BOOST_CHECK( co1_id(db).max_duration_seconds == 600u );\n      BOOST_CHECK( co1_id(db).min_deal_amount == 0 );\n      BOOST_CHECK( co1_id(db).enabled == true );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n      BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1_new );\n      BOOST_CHECK( co1_id(db).acceptable_borrowers == borrower_map1 );\n\n      // Existing credit deals are unchanged\n      BOOST_CHECK( cd11_id(db).borrower == ray_id );\n      BOOST_CHECK( cd11_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd11_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd11_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd11_id(db).debt_amount == 100 );\n      BOOST_CHECK( cd11_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd11_id(db).collateral_amount == 200 );\n      BOOST_CHECK( cd11_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd11_id(db).latest_repay_time == expected_repay_time11 );\n\n      // Ted is now able to borrow with CNY\n      const credit_deal_object& cdo14 = borrow_from_credit_offer( ted_id, co1_id, asset(200), asset(200, cny_id) );\n      credit_deal_id_type cd14_id = cdo14.get_id();\n      time_point_sec expected_repay_time14 = db.head_block_time() + fc::seconds(600); // 15 minutes after init\n\n      BOOST_CHECK( cd14_id(db).borrower == ted_id );\n      BOOST_CHECK( cd14_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd14_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd14_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd14_id(db).debt_amount == 200 );\n      BOOST_CHECK( cd14_id(db).collateral_asset == cny_id );\n      BOOST_CHECK( cd14_id(db).collateral_amount == 200 );\n      BOOST_CHECK( cd14_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd14_id(db).latest_repay_time == expected_repay_time14 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 9500 );\n\n      expected_balance_ted_core += 200;\n      expected_balance_ted_cny -= 200;\n      check_balances();\n\n      // Ray is now unable to borrow\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(200), asset(200, cny_id) ),\n                         fc::exception );\n      // Ted is now unable to borrow same amount again because it would exceed the limit\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(200), asset(200, cny_id) ),\n                         fc::exception );\n\n      // Ted is able to borrow less with CNY\n      const credit_deal_object& cdo15 = borrow_from_credit_offer( ted_id, co1_id, asset(50), asset(100, cny_id) );\n      credit_deal_id_type cd15_id = cdo15.get_id();\n      time_point_sec expected_repay_time15 = db.head_block_time() + fc::seconds(600); // 15 minutes after init\n\n      BOOST_CHECK( cd15_id(db).borrower == ted_id );\n      BOOST_CHECK( cd15_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd15_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd15_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd15_id(db).debt_amount == 50 );\n      BOOST_CHECK( cd15_id(db).collateral_asset == cny_id );\n      BOOST_CHECK( cd15_id(db).collateral_amount == 100 );\n      BOOST_CHECK( cd15_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd15_id(db).latest_repay_time == expected_repay_time15 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 10000 );\n      BOOST_CHECK( co1_id(db).current_balance == 9450 );\n\n      expected_balance_ted_core += 50;\n      expected_balance_ted_cny -= 100;\n      check_balances();\n\n      // Time goes by\n      generate_blocks( db.head_block_time() + fc::minutes(3) ); // now is 8 minutes after init\n      set_expiration( db, trx );\n\n      // Sam withdraw most of funds from the credit offer\n      update_credit_offer( sam_id, co1_id, asset(-9410), {}, {}, {}, {}, {}, {}, {} );\n      BOOST_CHECK( co1_id(db).total_balance == 590 );\n      BOOST_CHECK( co1_id(db).current_balance == 40 );\n\n      expected_balance_sam_core += 9410;\n      check_balances();\n\n      // Ted is unable to borrow with EUR because Sam is not authorized by EUR asset\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(40), asset(499, eur_id) ),\n                         fc::exception );\n\n      {\n         // Add Sam to the whitelist of EUR\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Now Ted is able to borrow 40 CORE with EUR\n      const credit_deal_object& cdo16 = borrow_from_credit_offer( ted_id, co1_id, asset(40), asset(499, eur_id) );\n      credit_deal_id_type cd16_id = cdo16.get_id();\n      time_point_sec expected_repay_time16 = db.head_block_time() + fc::seconds(600); // 18 minutes after init\n\n      BOOST_CHECK( cd16_id(db).borrower == ted_id );\n      BOOST_CHECK( cd16_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd16_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd16_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd16_id(db).debt_amount == 40 );\n      BOOST_CHECK( cd16_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd16_id(db).collateral_amount == 499 );\n      BOOST_CHECK( cd16_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd16_id(db).latest_repay_time == expected_repay_time16 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 590 );\n      BOOST_CHECK( co1_id(db).current_balance == 0 );\n\n      expected_balance_ted_core += 40;\n      expected_balance_ted_eur -= 499;\n      check_balances();\n\n      // Ted is unable to borrow 1 more CORE with EUR\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(1), asset(500, eur_id) ),\n                         fc::exception );\n\n      // Time goes by\n      generate_blocks( db.head_block_time() + fc::minutes(4) ); // now is 12 minutes after init\n      set_expiration( db, trx );\n\n      // Unable to repay : zero or negative amount\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(0), asset(1) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(-1), asset(1) ),\n                         fc::exception );\n      // Note: credit fee is allowed to be zero\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1), asset(-1) ),\n                         fc::exception );\n\n      // Unable to repay : asset type mismatch\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1), asset(1, usd_id) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1, usd_id), asset(1, usd_id) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1, usd_id), asset(1) ),\n                         fc::exception );\n\n      // Unable to repay : credit deal does not belong to the account\n      BOOST_CHECK_THROW( repay_credit_deal( ted_id, cd13_id, asset(1), asset(1) ),\n                         fc::exception );\n\n      // Ray partially repays\n      auto result = repay_credit_deal( ray_id, cd13_id, asset(1), asset(1) );\n      BOOST_REQUIRE( result.received.valid() );\n      BOOST_REQUIRE( result.received->size() == 1 );\n      asset collateral_released = result.received->front();\n\n      BOOST_CHECK( collateral_released == asset(4, usd_id) ); // round_down(499/100)\n\n      BOOST_REQUIRE( result.updated_objects.valid() );\n      BOOST_CHECK( result.updated_objects->size() == 2 );\n      BOOST_CHECK( *result.updated_objects == flat_set<object_id_type>({ co1_id(db).id, cd13_id(db).id }) );\n\n      BOOST_CHECK( !result.removed_objects.valid() );\n\n      BOOST_REQUIRE( result.impacted_accounts.valid() );\n      BOOST_CHECK( result.impacted_accounts->size() == 1 );\n      BOOST_CHECK( *result.impacted_accounts == flat_set<account_id_type>({ sam_id }) );\n\n      BOOST_CHECK( cd13_id(db).borrower == ray_id );\n      BOOST_CHECK( cd13_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd13_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd13_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd13_id(db).debt_amount == 99 );\n      BOOST_CHECK( cd13_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd13_id(db).collateral_amount == 495 );\n      BOOST_CHECK( cd13_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 591 );\n      BOOST_CHECK( co1_id(db).current_balance == 2 );\n\n      expected_balance_ray_core -= 2;\n      expected_balance_ray_usd += 4;\n      check_balances();\n\n      // Ted is able to borrow 2 CORE with EUR\n      const credit_deal_object& cdo17 = borrow_from_credit_offer( ted_id, co1_id, asset(2), asset(49, eur_id) );\n      credit_deal_id_type cd17_id = cdo17.get_id();\n      time_point_sec expected_repay_time17 = db.head_block_time() + fc::seconds(600); // 22 minutes after init\n\n      BOOST_CHECK( cd17_id(db).borrower == ted_id );\n      BOOST_CHECK( cd17_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd17_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd17_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd17_id(db).debt_amount == 2 );\n      BOOST_CHECK( cd17_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd17_id(db).collateral_amount == 49 );\n      BOOST_CHECK( cd17_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd17_id(db).latest_repay_time == expected_repay_time17 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 591 );\n      BOOST_CHECK( co1_id(db).current_balance == 0 );\n\n      expected_balance_ted_core += 2;\n      expected_balance_ted_eur -= 49;\n      check_balances();\n\n      // Ray partially repays with more fee than required\n      result = repay_credit_deal( ray_id, cd13_id, asset(1), asset(2) );\n      BOOST_REQUIRE( result.received.valid() );\n      BOOST_REQUIRE( result.received->size() == 1 );\n      collateral_released = result.received->front();\n\n      BOOST_CHECK( collateral_released == asset(5, usd_id) ); // round_down(495/99)\n\n      BOOST_REQUIRE( result.updated_objects.valid() );\n      BOOST_CHECK( result.updated_objects->size() == 2 );\n      BOOST_CHECK( *result.updated_objects == flat_set<object_id_type>({ co1_id(db).id, cd13_id(db).id }) );\n\n      BOOST_CHECK( !result.removed_objects.valid() );\n\n      BOOST_REQUIRE( result.impacted_accounts.valid() );\n      BOOST_CHECK( result.impacted_accounts->size() == 1 );\n      BOOST_CHECK( *result.impacted_accounts == flat_set<account_id_type>({ sam_id }) );\n\n      BOOST_CHECK( cd13_id(db).borrower == ray_id );\n      BOOST_CHECK( cd13_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd13_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd13_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd13_id(db).debt_amount == 98 );\n      BOOST_CHECK( cd13_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd13_id(db).collateral_amount == 490 );\n      BOOST_CHECK( cd13_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 593 );\n      BOOST_CHECK( co1_id(db).current_balance == 3 );\n\n      expected_balance_ray_core -= 3;\n      expected_balance_ray_usd += 5;\n      check_balances();\n\n      // Unable to repay : amount too big\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(99), asset(5) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd12_id, asset(101), asset(5) ),\n                         fc::exception );\n      // Unable to repay : insufficient credit fee : fee rate = 3%\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(98), asset(2) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd12_id, asset(100), asset(2) ),\n                         fc::exception );\n\n      // Fully repays\n      result = repay_credit_deal( ray_id, cd12_id, asset(100), asset(3) );\n      BOOST_REQUIRE( result.received.valid() );\n      BOOST_REQUIRE( result.received->size() == 1 );\n      collateral_released = result.received->front();\n\n      BOOST_CHECK( collateral_released == asset(200, usd_id) );\n\n      BOOST_REQUIRE( result.updated_objects.valid() );\n      BOOST_REQUIRE( result.updated_objects->size() == 1 );\n      BOOST_CHECK( *(result.updated_objects->begin()) == co1_id );\n\n      BOOST_REQUIRE( result.removed_objects.valid() );\n      BOOST_REQUIRE( result.removed_objects->size() == 1 );\n      BOOST_CHECK( *(result.removed_objects->begin()) == cd12_id );\n\n      BOOST_REQUIRE( result.impacted_accounts.valid() );\n      BOOST_CHECK( result.impacted_accounts->size() == 1 );\n      BOOST_CHECK( *result.impacted_accounts == flat_set<account_id_type>({ sam_id }) );\n\n      BOOST_CHECK( !db.find( cd12_id ) );\n\n      BOOST_CHECK( co1_id(db).total_balance == 596 );\n      BOOST_CHECK( co1_id(db).current_balance == 106 );\n\n      expected_balance_ray_core -= 103;\n      expected_balance_ray_usd += 200;\n      check_balances();\n\n      // Unable to repay : credit deal does not exist\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd12_id, asset(100), asset(3) ),\n                         fc::exception );\n\n      // Create another credit offer\n      auto disable_time2 = db.head_block_time() + fc::minutes(20); // 32 minites after init\n\n      flat_map<asset_id_type, price> collateral_map2;\n      collateral_map2[cny_id] = price( asset(10, usd_id), asset(12, cny_id) );\n      collateral_map2[eur_id] = price( asset(10, usd_id), asset(10, eur_id) );\n      const credit_offer_object& coo2 = create_credit_offer( sam_id, usd_id, 10000, 70000, 1800, 0, true,\n                                              disable_time2, collateral_map2, {} );\n      credit_offer_id_type co2_id = coo2.get_id();\n      BOOST_CHECK( co2_id(db).owner_account == sam_id );\n      BOOST_CHECK( co2_id(db).asset_type == usd_id );\n      BOOST_CHECK( co2_id(db).total_balance == 10000 );\n      BOOST_CHECK( co2_id(db).current_balance == 10000 );\n      BOOST_CHECK( co2_id(db).fee_rate == 70000u );\n      BOOST_CHECK( co2_id(db).max_duration_seconds == 1800u );\n      BOOST_CHECK( co2_id(db).min_deal_amount == 0 );\n      BOOST_CHECK( co2_id(db).enabled == true );\n      BOOST_CHECK( co2_id(db).auto_disable_time == disable_time2 );\n      BOOST_CHECK( co2_id(db).acceptable_collateral == collateral_map2 );\n      BOOST_CHECK( co2_id(db).acceptable_borrowers.empty() );\n\n      expected_balance_sam_usd -= 10000;\n      check_balances();\n\n      // Ray borrows from the new credit offer\n      const auto& cdo21 = borrow_from_credit_offer( ray_id, co2_id, asset(1000, usd_id), asset(1200, cny_id) );\n      credit_deal_id_type cd21_id = cdo21.get_id();\n      time_point_sec expected_repay_time21 = db.head_block_time() + fc::seconds(1800); // 42 minutes after init\n\n      BOOST_CHECK( cd21_id(db).borrower == ray_id );\n      BOOST_CHECK( cd21_id(db).offer_id == co2_id );\n      BOOST_CHECK( cd21_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd21_id(db).debt_asset == usd_id );\n      BOOST_CHECK( cd21_id(db).debt_amount == 1000 );\n      BOOST_CHECK( cd21_id(db).collateral_asset == cny_id );\n      BOOST_CHECK( cd21_id(db).collateral_amount == 1200 );\n      BOOST_CHECK( cd21_id(db).fee_rate == 70000u );\n      BOOST_CHECK( cd21_id(db).latest_repay_time == expected_repay_time21 );\n\n      BOOST_CHECK( co2_id(db).total_balance == 10000 );\n      BOOST_CHECK( co2_id(db).current_balance == 9000 );\n\n      expected_balance_ray_usd += 1000;\n      expected_balance_ray_cny -= 1200;\n      check_balances();\n\n      // Ray repays\n      result = repay_credit_deal( ray_id, cd21_id, asset(100, usd_id), asset(7, usd_id) );\n      BOOST_REQUIRE( result.received.valid() );\n      BOOST_REQUIRE( result.received->size() == 1 );\n      collateral_released = result.received->front();\n\n      BOOST_CHECK( collateral_released == asset(120, cny_id) );\n\n      BOOST_REQUIRE( result.updated_objects.valid() );\n      BOOST_CHECK( result.updated_objects->size() == 2 );\n      BOOST_CHECK( *result.updated_objects == flat_set<object_id_type>({ co2_id(db).id, cd21_id(db).id }) );\n\n      BOOST_CHECK( !result.removed_objects.valid() );\n\n      BOOST_REQUIRE( result.impacted_accounts.valid() );\n      BOOST_CHECK( result.impacted_accounts->size() == 1 );\n      BOOST_CHECK( *result.impacted_accounts == flat_set<account_id_type>({ sam_id }) );\n\n      BOOST_CHECK( cd21_id(db).borrower == ray_id );\n      BOOST_CHECK( cd21_id(db).offer_id == co2_id );\n      BOOST_CHECK( cd21_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd21_id(db).debt_asset == usd_id );\n      BOOST_CHECK( cd21_id(db).debt_amount == 900 );\n      BOOST_CHECK( cd21_id(db).collateral_asset == cny_id );\n      BOOST_CHECK( cd21_id(db).collateral_amount == 1080 );\n      BOOST_CHECK( cd21_id(db).fee_rate == 70000u );\n      BOOST_CHECK( cd21_id(db).latest_repay_time == expected_repay_time21 );\n\n      BOOST_CHECK( co2_id(db).total_balance == 10007 );\n      BOOST_CHECK( co2_id(db).current_balance == 9107 );\n\n      expected_balance_ray_usd -= 107;\n      expected_balance_ray_cny += 120;\n      check_balances();\n\n      {\n         // Remove Ray from the whitelist of USD\n         account_whitelist_operation wop;\n         wop.authorizing_account = ted_id;\n         wop.account_to_list = ray_id;\n         wop.new_listing = account_whitelist_operation::no_listing;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Ray is no longer able to borrow from co2\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co2_id, asset(1000, usd_id), asset(1200, cny_id) ),\n                         fc::exception );\n\n      // Ray is unable to repay the deal with USD\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd21_id, asset(100, usd_id), asset(7, usd_id) ),\n                         fc::exception );\n\n      // Ray is still able to repay another deal with CORE to get USD\n      result = repay_credit_deal( ray_id, cd13_id, asset(1), asset(1) );\n      BOOST_REQUIRE( result.received.valid() );\n      BOOST_REQUIRE( result.received->size() == 1 );\n      collateral_released = result.received->front();\n\n      BOOST_CHECK( collateral_released == asset(5, usd_id) ); // round_down(490/98)\n\n      BOOST_REQUIRE( result.updated_objects.valid() );\n      BOOST_CHECK( result.updated_objects->size() == 2 );\n      BOOST_CHECK( *result.updated_objects == flat_set<object_id_type>({ co1_id(db).id, cd13_id(db).id }) );\n\n      BOOST_CHECK( !result.removed_objects.valid() );\n\n      BOOST_REQUIRE( result.impacted_accounts.valid() );\n      BOOST_CHECK( result.impacted_accounts->size() == 1 );\n      BOOST_CHECK( *result.impacted_accounts == flat_set<account_id_type>({ sam_id }) );\n\n      BOOST_CHECK( cd13_id(db).borrower == ray_id );\n      BOOST_CHECK( cd13_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd13_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd13_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd13_id(db).debt_amount == 97 );\n      BOOST_CHECK( cd13_id(db).collateral_asset == usd_id );\n      BOOST_CHECK( cd13_id(db).collateral_amount == 485 );\n      BOOST_CHECK( cd13_id(db).fee_rate == 30000u );\n      BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 597 );\n      BOOST_CHECK( co1_id(db).current_balance == 108 );\n\n      expected_balance_ray_core -= 2;\n      expected_balance_ray_usd += 5;\n      check_balances();\n\n      // Ray transfer most of CORE to Sam\n      transfer( ray_id, sam_id, asset(expected_balance_ray_core - 10) );\n\n      expected_balance_sam_core += (expected_balance_ray_core - 10);\n      expected_balance_ray_core = 10;\n      check_balances();\n\n      // Unable to repay : insufficient account balance\n      BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(10), asset(1) ),\n                         fc::exception );\n\n      // Time goes by\n      generate_blocks( db.head_block_time() + fc::minutes(1) ); // now is 13 minutes after init\n      set_expiration( db, trx );\n\n      // Ted is unable to borrow from co2 : Ted is not authorized by USD\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co2_id, asset(1000, usd_id), asset(1100, eur_id) ),\n                         fc::exception );\n\n      {\n         // Add Ted to the whitelist of USD\n         account_whitelist_operation wop;\n         wop.authorizing_account = ted_id;\n         wop.account_to_list = ted_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Ted borrows from the new credit offer\n      const auto& cdo22 = borrow_from_credit_offer( ted_id, co2_id, asset(1000, usd_id), asset(1100, eur_id) );\n      credit_deal_id_type cd22_id = cdo22.get_id();\n      time_point_sec expected_repay_time22 = db.head_block_time() + fc::seconds(1800); // 43 minutes after init\n\n      BOOST_CHECK( cd22_id(db).borrower == ted_id );\n      BOOST_CHECK( cd22_id(db).offer_id == co2_id );\n      BOOST_CHECK( cd22_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd22_id(db).debt_asset == usd_id );\n      BOOST_CHECK( cd22_id(db).debt_amount == 1000 );\n      BOOST_CHECK( cd22_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd22_id(db).collateral_amount == 1100 );\n      BOOST_CHECK( cd22_id(db).fee_rate == 70000u );\n      BOOST_CHECK( cd22_id(db).latest_repay_time == expected_repay_time22 );\n\n      BOOST_CHECK( co2_id(db).total_balance == 10007 );\n      BOOST_CHECK( co2_id(db).current_balance == 8107 );\n\n      expected_balance_ted_usd += 1000;\n      expected_balance_ted_eur -= 1100;\n      check_balances();\n\n      // Ted repays\n      result = repay_credit_deal( ted_id, cd22_id, asset(200, usd_id), asset(15, usd_id) );\n      BOOST_REQUIRE( result.received.valid() );\n      BOOST_REQUIRE( result.received->size() == 1 );\n      collateral_released = result.received->front();\n\n      BOOST_CHECK( collateral_released == asset(220, eur_id) );\n\n      BOOST_REQUIRE( result.updated_objects.valid() );\n      BOOST_CHECK( result.updated_objects->size() == 2 );\n      BOOST_CHECK( *result.updated_objects == flat_set<object_id_type>({ co2_id(db).id, cd22_id(db).id }) );\n\n      BOOST_CHECK( !result.removed_objects.valid() );\n\n      BOOST_REQUIRE( result.impacted_accounts.valid() );\n      BOOST_CHECK( result.impacted_accounts->size() == 1 );\n      BOOST_CHECK( *result.impacted_accounts == flat_set<account_id_type>({ sam_id }) );\n\n      BOOST_CHECK( cd22_id(db).borrower == ted_id );\n      BOOST_CHECK( cd22_id(db).offer_id == co2_id );\n      BOOST_CHECK( cd22_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd22_id(db).debt_asset == usd_id );\n      BOOST_CHECK( cd22_id(db).debt_amount == 800 );\n      BOOST_CHECK( cd22_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd22_id(db).collateral_amount == 880 );\n      BOOST_CHECK( cd22_id(db).fee_rate == 70000u );\n      BOOST_CHECK( cd22_id(db).latest_repay_time == expected_repay_time22 );\n\n      BOOST_CHECK( co2_id(db).total_balance == 10022 );\n      BOOST_CHECK( co2_id(db).current_balance == 8322 );\n\n      expected_balance_ted_usd -= 215;\n      expected_balance_ted_eur += 220;\n      check_balances();\n\n      {\n         // Remove Sam from the whitelist of USD\n         account_whitelist_operation wop;\n         wop.authorizing_account = ted_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::no_listing;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Ted is unable to borrow from co2 : credit offer owner Sam is now not authorized by USD\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co2_id, asset(1000, usd_id), asset(1100, eur_id) ),\n                         fc::exception );\n\n      // Ted is unable to repay the co2 deal : credit offer owner Sam is now not authorized by USD\n      BOOST_CHECK_THROW( repay_credit_deal( ted_id, cd22_id, asset(200, usd_id), asset(15, usd_id) ),\n                         fc::exception );\n\n      // ===== Time table =========\n      // now: 13\n      // expected_repay_time14 : 15\n      // expected_repay_time15 : 15\n      // expected_repay_time16 : 18\n      // disable_time1 : 20\n      // expected_repay_time17 : 22\n      // disable_time2 : 32\n      // expected_repay_time21 : 42\n      // expected_repay_time22 : 43\n      // expected_repay_time11 : 60\n      // expected_repay_time12 : 60 // fully repaid already\n      // expected_repay_time13 : 65\n\n      // Time goes by\n      generate_blocks( expected_repay_time14 ); // now is 15 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_REQUIRE( !db.find( cd14_id ) );\n      BOOST_REQUIRE( !db.find( cd15_id ) );\n\n      BOOST_CHECK( co1_id(db).total_balance == 347 ); // 597 - 200 - 50\n      BOOST_CHECK( co1_id(db).current_balance == 108 ); // unchanged\n      BOOST_CHECK( co1_id(db).enabled == true );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n\n      expected_balance_sam_cny += 200; // cd14\n      expected_balance_sam_cny += 100; // cd15\n      check_balances();\n\n      BOOST_REQUIRE( db.find( cd16_id ) );\n      BOOST_CHECK( cd16_id(db).borrower == ted_id );\n      BOOST_CHECK( cd16_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd16_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd16_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd16_id(db).debt_amount == 40 );\n      BOOST_CHECK( cd16_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd16_id(db).collateral_amount == 499 );\n      BOOST_CHECK( cd16_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd16_id(db).latest_repay_time == expected_repay_time16 );\n\n      // Time goes by\n      generate_blocks( expected_repay_time16 ); // now is 18 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_REQUIRE( !db.find( cd16_id ) );\n\n      BOOST_CHECK( co1_id(db).total_balance == 307 ); // 347  - 40\n      BOOST_CHECK( co1_id(db).current_balance == 108 ); // unchanged\n      BOOST_CHECK( co1_id(db).enabled == true );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n\n      expected_balance_sam_eur += 499; // cd16\n      check_balances();\n\n      BOOST_REQUIRE( db.find( cd17_id ) );\n      BOOST_CHECK( cd17_id(db).borrower == ted_id );\n      BOOST_CHECK( cd17_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd17_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd17_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd17_id(db).debt_amount == 2 );\n      BOOST_CHECK( cd17_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd17_id(db).collateral_amount == 49 );\n      BOOST_CHECK( cd17_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd17_id(db).latest_repay_time == expected_repay_time17 );\n\n      // Ted borrows more\n      const credit_deal_object& cdo18 = borrow_from_credit_offer( ted_id, co1_id, asset(10), asset(30, eur_id) );\n      credit_deal_id_type cd18_id = cdo18.get_id();\n      time_point_sec expected_repay_time18 = db.head_block_time() + fc::seconds(600); // 28 minutes after init\n\n      BOOST_CHECK( cd18_id(db).borrower == ted_id );\n      BOOST_CHECK( cd18_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd18_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd18_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd18_id(db).debt_amount == 10 );\n      BOOST_CHECK( cd18_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd18_id(db).collateral_amount == 30 );\n      BOOST_CHECK( cd18_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd18_id(db).latest_repay_time == expected_repay_time18 );\n\n      BOOST_CHECK( co1_id(db).total_balance == 307 );\n      BOOST_CHECK( co1_id(db).current_balance == 98 );\n\n      expected_balance_ted_core += 10;\n      expected_balance_ted_eur -= 30;\n      check_balances();\n\n      // ===== Time table =========\n      // now: 18\n      // expected_repay_time14 : 15 // expired\n      // expected_repay_time15 : 15 // expired\n      // expected_repay_time16 : 18 // expired\n      // disable_time1 : 20\n      // expected_repay_time17 : 22\n      // expected_repay_time18 : 28\n      // disable_time2 : 32\n      // expected_repay_time21 : 42\n      // expected_repay_time22 : 43\n      // expected_repay_time11 : 60\n      // expected_repay_time12 : 60 // fully repaid already\n      // expected_repay_time13 : 65\n\n      // Time goes by\n      generate_blocks( disable_time1 ); // now is 20 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_CHECK( co1_id(db).total_balance == 307 );\n      BOOST_CHECK( co1_id(db).current_balance == 98 );\n      BOOST_CHECK( co1_id(db).enabled == false );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n\n      // Unable to borrow from co1\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(10), asset(30, eur_id) ),\n                         fc::exception );\n\n      BOOST_REQUIRE( db.find( cd17_id ) );\n      BOOST_CHECK( cd17_id(db).borrower == ted_id );\n      BOOST_CHECK( cd17_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd17_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd17_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd17_id(db).debt_amount == 2 );\n      BOOST_CHECK( cd17_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd17_id(db).collateral_amount == 49 );\n      BOOST_CHECK( cd17_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd17_id(db).latest_repay_time == expected_repay_time17 );\n\n      // Time goes by\n      generate_blocks( expected_repay_time17 ); // now is 22 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_REQUIRE( !db.find( cd17_id ) );\n\n      BOOST_CHECK( co1_id(db).total_balance == 305 ); // 307  - 2\n      BOOST_CHECK( co1_id(db).current_balance == 98 ); // unchanged\n      BOOST_CHECK( co1_id(db).enabled == false );\n      BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 );\n\n      expected_balance_sam_eur += 49; // cd17\n      check_balances();\n\n      BOOST_REQUIRE( db.find( cd18_id ) );\n      BOOST_CHECK( cd18_id(db).borrower == ted_id );\n      BOOST_CHECK( cd18_id(db).offer_id == co1_id );\n      BOOST_CHECK( cd18_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd18_id(db).debt_asset == core_id );\n      BOOST_CHECK( cd18_id(db).debt_amount == 10 );\n      BOOST_CHECK( cd18_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd18_id(db).collateral_amount == 30 );\n      BOOST_CHECK( cd18_id(db).fee_rate == 500u );\n      BOOST_CHECK( cd18_id(db).latest_repay_time == expected_repay_time18 );\n\n      // Time goes by\n      generate_blocks( expected_repay_time18 ); // now is 28 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_REQUIRE( !db.find( cd18_id ) );\n\n      BOOST_CHECK( co1_id(db).total_balance == 295 ); // 305  - 10\n      BOOST_CHECK( co1_id(db).current_balance == 98 ); // unchanged\n\n      expected_balance_sam_eur += 30; // cd18\n      check_balances();\n\n      BOOST_CHECK( co2_id(db).enabled == true );\n      BOOST_CHECK( co2_id(db).auto_disable_time == disable_time2 );\n\n      // ===== Time table =========\n      // now: 28\n      // expected_repay_time14 : 15 // expired\n      // expected_repay_time15 : 15 // expired\n      // expected_repay_time16 : 18 // expired\n      // disable_time1 : 20         // expired\n      // expected_repay_time17 : 22 // expired\n      // expected_repay_time18 : 28 // expired\n      // disable_time2 : 32\n      // expected_repay_time21 : 42\n      // expected_repay_time22 : 43\n      // expected_repay_time11 : 60\n      // expected_repay_time12 : 60 // fully repaid already\n      // expected_repay_time13 : 65\n\n      // Time goes by\n      generate_blocks( disable_time2 ); // now is 32 minutes after init\n      set_expiration( db, trx );\n\n      BOOST_CHECK( co2_id(db).enabled == false );\n      BOOST_CHECK( co2_id(db).auto_disable_time == disable_time2 );\n\n      BOOST_REQUIRE( db.find( cd21_id ) );\n      BOOST_CHECK( cd21_id(db).borrower == ray_id );\n      BOOST_CHECK( cd21_id(db).offer_id == co2_id );\n      BOOST_CHECK( cd21_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd21_id(db).debt_asset == usd_id );\n      BOOST_CHECK( cd21_id(db).debt_amount == 900 );\n      BOOST_CHECK( cd21_id(db).collateral_asset == cny_id );\n      BOOST_CHECK( cd21_id(db).collateral_amount == 1080 );\n      BOOST_CHECK( cd21_id(db).fee_rate == 70000u );\n      BOOST_CHECK( cd21_id(db).latest_repay_time == expected_repay_time21 );\n\n      // Time goes by\n      generate_blocks( expected_repay_time21 ); // now is 42 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_REQUIRE( !db.find( cd21_id ) );\n\n      BOOST_CHECK( co2_id(db).total_balance == 9122 ); // 10022 - 900\n      BOOST_CHECK( co2_id(db).current_balance == 8322 ); // unchanged\n\n      expected_balance_sam_cny += 1080; // cd21\n      check_balances();\n\n      BOOST_CHECK( cd22_id(db).borrower == ted_id );\n      BOOST_CHECK( cd22_id(db).offer_id == co2_id );\n      BOOST_CHECK( cd22_id(db).offer_owner == sam_id );\n      BOOST_CHECK( cd22_id(db).debt_asset == usd_id );\n      BOOST_CHECK( cd22_id(db).debt_amount == 800 );\n      BOOST_CHECK( cd22_id(db).collateral_asset == eur_id );\n      BOOST_CHECK( cd22_id(db).collateral_amount == 880 );\n      BOOST_CHECK( cd22_id(db).fee_rate == 70000u );\n      BOOST_CHECK( cd22_id(db).latest_repay_time == expected_repay_time22 );\n\n      {\n         // Remove Sam from the whitelist of EUR\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::no_listing;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Time goes by\n      generate_blocks( expected_repay_time22 ); // now is 43 minutes after init\n      set_expiration( db, trx );\n\n      // Expiration\n      BOOST_REQUIRE( !db.find( cd22_id ) );\n\n      BOOST_CHECK( co2_id(db).total_balance == 8322 ); // 9122 - 800\n      BOOST_CHECK( co2_id(db).current_balance == 8322 ); // unchanged\n\n      // Funds go to account balance ignoring asset authorization\n      expected_balance_sam_eur += 880; // cd22\n      check_balances();\n\n      // Sam delete credit offer\n      delete_credit_offer( sam_id, co2_id );\n\n      BOOST_REQUIRE( db.find( co1_id ) );\n      BOOST_REQUIRE( !db.find( co2_id ) );\n\n      expected_balance_sam_usd += 8322;\n      check_balances();\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( credit_offer_crud_and_proposal_test_after_hf2595 )\n{\n   hf2595 = true;\n   INVOKE( credit_offer_crud_and_proposal_test );\n}\n\nBOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test_after_hf2595 )\n{\n   hf2595 = true;\n   INVOKE( credit_offer_borrow_repay_test );\n}\n\nBOOST_AUTO_TEST_CASE( credit_deal_auto_repay_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2595_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((ray)(sam)(ted)(por)(np1)(np2)(np3)(np4)(np5));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( por, asset(init_amount) );\n      fund( np4, asset(init_amount) );\n      fund( np5, asset(init_amount) );\n\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, white_list );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( sam, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( ray, eur.amount(init_amount) );\n      issue_uia( np1, eur.amount(init_amount) );\n      issue_uia( np2, eur.amount(init_amount) );\n      issue_uia( np3, eur.amount(init_amount) );\n\n      const asset_object& jpy = create_user_issued_asset( \"MYJPY\", ted, white_list );\n      asset_id_type jpy_id = jpy.get_id();\n      issue_uia( sam, jpy.amount(init_amount * 1000) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, init_amount );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, jpy_id ).amount.value, init_amount * 1000 );\n\n      // create a credit offer\n      auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init\n\n      flat_map<asset_id_type, price> collateral_map1;\n      collateral_map1[core_id] = price( asset(19, usd_id), asset(23, core_id) );\n      collateral_map1[eur_id] = price( asset(19, usd_id), asset(17, eur_id) );\n\n      const credit_offer_object& coo1 = create_credit_offer( sam_id, usd_id, 10000, 30000, 3600, 0, true,\n                                              disable_time1, collateral_map1, {} );\n      credit_offer_id_type co1_id = coo1.get_id();\n\n      // create another credit offer with a very high fee rate\n      flat_map<asset_id_type, price> collateral_map2;\n      collateral_map2[core_id] = price( asset(init_amount, jpy_id), asset(1, core_id) );\n\n      const credit_offer_object& coo2 = create_credit_offer( sam_id, jpy_id, init_amount * 1000,\n                                              4000 * GRAPHENE_FEE_RATE_DENOM, 3600, 0, true,\n                                              disable_time1, collateral_map2, {} );\n      credit_offer_id_type co2_id = coo2.get_id();\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, init_amount - 10000 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, jpy_id ).amount.value, 0 );\n\n      // Unable to accept offer with an invalid auto-repayment type\n      BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 3 ),\n                         fc::exception );\n      // Unable to accept offer with a proposal with an invalid auto-repayment type\n      credit_offer_accept_operation accop = make_credit_offer_accept_op( ray_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 3 );\n      BOOST_CHECK_THROW( propose( accop ), fc::exception );\n\n      // Able to accept offer with a valid auto-repayment type\n      const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 );\n      credit_deal_id_type cd11_id = cdo11.get_id();\n\n      BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 2U );\n\n      // Unable to update the deal with an invalid auto-repayment type\n      BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id, 3 ), fc::exception );\n      // Unable to propose an update with an invalid auto-repayment type\n      credit_deal_update_operation updop = make_credit_deal_update_op( ray_id, cd11_id, 3 );\n      BOOST_CHECK_THROW( propose( updop ), fc::exception );\n\n      // Unable to update the deal if nothing would change\n      BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id, 2 ), fc::exception );\n      // Unable to update a deal if it does not exist\n      BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id + 100, 1 ), fc::exception );\n      // Unable to update a deal if the account is not the owner\n      BOOST_CHECK_THROW( update_credit_deal( sam_id, cd11_id, 1 ), fc::exception );\n\n      // Able to propose an update with a valid auto-repayment type\n      updop = make_credit_deal_update_op( sam_id, cd11_id + 100, 2 );\n      propose( updop );\n\n      // Able to update a deal with valid data\n      update_credit_deal( ray_id, cd11_id, 1 );\n      BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 1U );\n      update_credit_deal( ray_id, cd11_id, 0 );\n      BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 0U );\n      update_credit_deal( ray_id, cd11_id, 2 );\n      BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 2U );\n\n      // People borrow more\n      const credit_deal_object& cdo12 = borrow_from_credit_offer( por_id, co1_id, asset(190, usd_id),\n                                                   asset(310), GRAPHENE_FEE_RATE_DENOM, 0, 1 );\n      credit_deal_id_type cd12_id = cdo12.get_id();\n\n      BOOST_CHECK_EQUAL( cd12_id(db).auto_repay, 1U );\n\n      const credit_deal_object& cdo13 = borrow_from_credit_offer( np1_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 0 );\n      credit_deal_id_type cd13_id = cdo13.get_id();\n\n      BOOST_CHECK_EQUAL( cd13_id(db).auto_repay, 0U );\n\n      const credit_deal_object& cdo14 = borrow_from_credit_offer( np2_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 );\n      credit_deal_id_type cd14_id = cdo14.get_id();\n\n      BOOST_CHECK_EQUAL( cd14_id(db).auto_repay, 2U );\n\n      const credit_deal_object& cdo15 = borrow_from_credit_offer( np3_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 );\n      credit_deal_id_type cd15_id = cdo15.get_id();\n\n      BOOST_CHECK_EQUAL( cd15_id(db).auto_repay, 2U );\n\n      const credit_deal_object& cdo16 = borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 1 );\n      credit_deal_id_type cd16_id = cdo16.get_id();\n\n      BOOST_CHECK_EQUAL( cd16_id(db).auto_repay, 1U );\n\n      const credit_deal_object& cdo17 = borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id),\n                                                   asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 );\n      credit_deal_id_type cd17_id = cdo17.get_id();\n\n      BOOST_CHECK_EQUAL( cd17_id(db).auto_repay, 2U );\n\n      BOOST_CHECK_EQUAL( db.get_balance( ray_id, usd_id ).amount.value, 190 * 3 );\n      BOOST_CHECK_EQUAL( db.get_balance( ray_id, eur_id ).amount.value, init_amount - 170 * 3 );\n      BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, init_amount - 310 );\n      BOOST_CHECK_EQUAL( db.get_balance( np1_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( np1_id, eur_id ).amount.value, init_amount - 170 );\n      BOOST_CHECK_EQUAL( db.get_balance( np2_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( np2_id, eur_id ).amount.value, init_amount - 170 );\n      BOOST_CHECK_EQUAL( db.get_balance( np3_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( np3_id, eur_id ).amount.value, init_amount - 170 );\n\n      BOOST_CHECK_EQUAL( co1_id(db).total_balance.value, 10000 );\n      BOOST_CHECK_EQUAL( co1_id(db).current_balance.value, 8670 ); // 10000 - 190 * 7\n\n      const credit_deal_object& cdo21 = borrow_from_credit_offer( np4_id, co2_id,\n                                                   asset(init_amount * 999, jpy_id),\n                                                   asset(999), GRAPHENE_FEE_RATE_DENOM * 4000, 0, 1 );\n      credit_deal_id_type cd21_id = cdo21.get_id();\n\n      BOOST_CHECK_EQUAL( cd21_id(db).auto_repay, 1U );\n\n      const credit_deal_object& cdo22 = borrow_from_credit_offer( np5_id, co2_id,\n                                                   asset(init_amount, jpy_id),\n                                                   asset(1), GRAPHENE_FEE_RATE_DENOM * 4000, 0, 2 );\n      credit_deal_id_type cd22_id = cdo22.get_id();\n\n      BOOST_CHECK_EQUAL( cd22_id(db).auto_repay, 2U );\n\n      BOOST_CHECK_EQUAL( db.get_balance( np4_id, jpy_id ).amount.value, init_amount * 999 );\n      BOOST_CHECK_EQUAL( db.get_balance( np4_id, core_id ).amount.value, init_amount - 999 );\n      BOOST_CHECK_EQUAL( db.get_balance( np5_id, jpy_id ).amount.value, init_amount );\n      BOOST_CHECK_EQUAL( db.get_balance( np5_id, core_id ).amount.value, init_amount - 1 );\n\n      BOOST_CHECK_EQUAL( co2_id(db).total_balance.value, init_amount * 1000 );\n      BOOST_CHECK_EQUAL( co2_id(db).current_balance.value, 0 );\n\n      // Setup blacklists\n      {\n         BOOST_TEST_MESSAGE( \"Setting up EUR blacklisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = eur.id;\n         uop.issuer = sam_id;\n         uop.new_options = eur.options;\n         // The EUR blacklist is managed by Sam\n         uop.new_options.blacklist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the blacklist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add np2 to the EUR blacklist\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = np2_id;\n         wop.new_listing = account_whitelist_operation::black_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n      {\n         BOOST_TEST_MESSAGE( \"Setting up USD blacklisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = usd.id;\n         uop.issuer = ted_id;\n         uop.new_options = usd.options;\n         // The USD blacklist is managed by Ted\n         uop.new_options.blacklist_authorities.insert(ted_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Ted so that he can manage the blacklist\n         upgrade_to_lifetime_member( ted_id );\n\n         // Add np3 to the USD blacklist\n         account_whitelist_operation wop;\n         wop.authorizing_account = ted_id;\n         wop.account_to_list = np3_id;\n         wop.new_listing = account_whitelist_operation::black_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Let the credit deals expire\n      generate_blocks( db.head_block_time() + 3600 );\n      generate_block();\n\n      BOOST_CHECK( !db.find( cd11_id ) );\n      BOOST_CHECK( !db.find( cd12_id ) );\n      BOOST_CHECK( !db.find( cd13_id ) );\n      BOOST_CHECK( !db.find( cd14_id ) );\n      BOOST_CHECK( !db.find( cd15_id ) );\n      BOOST_CHECK( !db.find( cd16_id ) );\n      BOOST_CHECK( !db.find( cd17_id ) );\n      BOOST_CHECK( !db.find( cd21_id ) );\n      BOOST_CHECK( !db.find( cd22_id ) );\n\n      // Ray fully repaid cd11, fee = round_up(190 * 3 / 100) = 6\n      // Por is unable to repay cd12 due to insufficient balance\n      // Np1 decided to not pay cd13\n      // Np2 failed to repay cd14 due to blacklisting\n      // Np3 is blacklisted by the collateral asset (EUR), however it doesn't affect the repayment for cd15\n      //   Balance was 190\n      //   cd15 debt was 190, collateral was 170\n      //   To repay: round_down(190 * 100 / 103) = 184\n      //   Collateral released = round_down(184 * 170 / 190) = 164\n      //   Updated repay amount = round_up(164 * 190 / 170) = 184\n      //   Fee = round_up(184 * 3 / 100) = 6\n      //   Total Pays = 184 + 6 = 190\n      //   New balance = 190 - 190 = 0\n      //   cd17 unpaid debt = 190 - 184 = 6, unreleased collateral = 170 - 164 = 6\n      // Ray fully repaid cd16, fee = round_up(190 * 3 / 100) = 6\n      // Ray partially repaid cd17\n      //   Balance was 190 - 6 * 2 = 178\n      //   cd17 debt was 190, collateral was 170\n      //   To repay: round_down(178 * 100 / 103) = 172\n      //   Collateral released = round_down(172 * 170 / 190) = 153\n      //   Updated repay amount = round_up(153 * 190 / 170) = 171\n      //   Fee = round_up(171 * 3 / 100) = 6\n      //   Total Pays = 171 + 6 = 177\n      //   New balance = 178 - 177 = 1\n      //   cd17 unpaid debt = 190 - 171 = 19, unreleased collateral = 170 - 153 = 17\n      // Np4 is unable to repay cd21 due to amount overflow or insufficient balance\n      // Np5 did not repay cd22 because no collateral will be returned on partial repayment\n\n      BOOST_CHECK_EQUAL( co1_id(db).total_balance.value, 9429 ); // 10000 - 190 * 3 - 6 - 19 + 6 * 4\n      BOOST_CHECK_EQUAL( co1_id(db).current_balance.value, 9429 );\n\n      BOOST_CHECK_EQUAL( co2_id(db).total_balance.value, 0 );\n      BOOST_CHECK_EQUAL( co2_id(db).current_balance.value, 0 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, 310 + 999 + 1 ); // cd13, cd21, cd22\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, init_amount - 10000 ); // no change\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, 170 * 2 + 6 + 17 ); // cd12, cd14, cd15, cd17\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, jpy_id ).amount.value, 0 ); // no change\n\n      BOOST_CHECK_EQUAL( db.get_balance( ray_id, usd_id ).amount.value, 1 );\n      BOOST_CHECK_EQUAL( db.get_balance( ray_id, eur_id ).amount.value, init_amount - 17 );\n      BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, init_amount - 310 );\n      BOOST_CHECK_EQUAL( db.get_balance( np1_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( np1_id, eur_id ).amount.value, init_amount - 170 );\n      BOOST_CHECK_EQUAL( db.get_balance( np2_id, usd_id ).amount.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( np2_id, eur_id ).amount.value, init_amount - 6 );\n      BOOST_CHECK_EQUAL( db.get_balance( np3_id, usd_id ).amount.value, 190 );\n      BOOST_CHECK_EQUAL( db.get_balance( np3_id, eur_id ).amount.value, init_amount - 170 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( np4_id, jpy_id ).amount.value, init_amount * 999 );\n      BOOST_CHECK_EQUAL( db.get_balance( np4_id, core_id ).amount.value, init_amount - 999 );\n      BOOST_CHECK_EQUAL( db.get_balance( np5_id, jpy_id ).amount.value, init_amount );\n      BOOST_CHECK_EQUAL( db.get_balance( np5_id, core_id ).amount.value, init_amount - 1 );\n\n      // Check history API\n      graphene::app::history_api hist_api(app);\n\n      // np5's last 2 operations are credit_deal_expired_op and credit_offer_accept_op\n      auto histories = hist_api.get_relative_account_history( \"np5\", 0, 2, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 2U );\n      BOOST_CHECK( histories[0].op.is_type<credit_deal_expired_operation>() );\n      BOOST_CHECK( histories[0].is_virtual );\n      BOOST_CHECK( histories[1].op.is_type<credit_offer_accept_operation>() );\n      BOOST_CHECK( !histories[1].is_virtual );\n\n      // np4's last 2 operations are credit_deal_expired_op and credit_offer_accept_op\n      histories = hist_api.get_relative_account_history( \"np4\", 0, 2, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 2U );\n      BOOST_CHECK( histories[0].op.is_type<credit_deal_expired_operation>() );\n      BOOST_CHECK( histories[0].is_virtual );\n      BOOST_CHECK( histories[1].op.is_type<credit_offer_accept_operation>() );\n      BOOST_CHECK( !histories[1].is_virtual );\n\n      // np2's last 4 operations are credit_deal_expired_op, credit_deal_repay_op, account_whitelist_op,\n      //                             and credit_offer_accept_op\n      histories = hist_api.get_relative_account_history( \"np2\", 0, 4, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 4U );\n      BOOST_CHECK( histories[0].op.is_type<credit_deal_expired_operation>() );\n      BOOST_CHECK( histories[0].is_virtual );\n      BOOST_CHECK( histories[1].op.is_type<credit_deal_repay_operation>() );\n      BOOST_CHECK( histories[1].is_virtual );\n      BOOST_CHECK( histories[2].op.is_type<account_whitelist_operation>() );\n      BOOST_CHECK( !histories[2].is_virtual );\n      BOOST_CHECK( histories[3].op.is_type<credit_offer_accept_operation>() );\n      BOOST_CHECK( !histories[3].is_virtual );\n\n      // ray's last 10 operations are 1 * credit_deal_expired_op, 3 * credit_deal_repay_op,\n      //                              2 * credit_offer_accept_op,\n      //                              3 * credit_deal_update_op, 1 * credit_accept_op\n      histories = hist_api.get_relative_account_history( \"ray\", 0, 10, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 10U );\n      BOOST_CHECK( histories[0].op.is_type<credit_deal_expired_operation>() );\n      BOOST_CHECK( histories[0].is_virtual );\n      BOOST_CHECK( histories[1].op.is_type<credit_deal_repay_operation>() );\n      BOOST_CHECK( histories[1].is_virtual );\n      BOOST_CHECK( histories[2].op.is_type<credit_deal_repay_operation>() );\n      BOOST_CHECK( histories[2].is_virtual );\n      BOOST_CHECK( histories[3].op.is_type<credit_deal_repay_operation>() );\n      BOOST_CHECK( histories[3].is_virtual );\n      BOOST_CHECK( histories[4].op.is_type<credit_offer_accept_operation>() );\n      BOOST_CHECK( !histories[4].is_virtual );\n      BOOST_CHECK( histories[5].op.is_type<credit_offer_accept_operation>() );\n      BOOST_CHECK( !histories[5].is_virtual );\n      BOOST_CHECK( histories[6].op.is_type<credit_deal_update_operation>() );\n      BOOST_CHECK( !histories[6].is_virtual );\n      BOOST_CHECK( histories[7].op.is_type<credit_deal_update_operation>() );\n      BOOST_CHECK( !histories[7].is_virtual );\n      BOOST_CHECK( histories[8].op.is_type<credit_deal_update_operation>() );\n      BOOST_CHECK( !histories[8].is_virtual );\n      BOOST_CHECK( histories[9].op.is_type<credit_offer_accept_operation>() );\n      BOOST_CHECK( !histories[9].is_virtual );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( credit_offer_apis_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2362_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((bob)(ray)(sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( bob, asset(init_amount) );\n      fund( ray, asset(init_amount) );\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( bob, usd.amount(init_amount) );\n      issue_uia( ray, usd.amount(init_amount) );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( bob, eur.amount(init_amount) );\n      issue_uia( ray, eur.amount(init_amount) );\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n\n      // create credit offers\n      flat_map<asset_id_type, price> collateral_map_core;\n      collateral_map_core[usd_id] = price( asset(1), asset(2, usd_id) );\n      collateral_map_core[eur_id] = price( asset(1), asset(1, eur_id) );\n\n      flat_map<asset_id_type, price> collateral_map_usd;\n      collateral_map_usd[eur_id] = price( asset(1, usd_id), asset(1, eur_id) );\n\n      flat_map<asset_id_type, price> collateral_map_eur;\n      collateral_map_eur[core_id] = price( asset(1, eur_id), asset(3) );\n\n      const credit_offer_object& coo1 = create_credit_offer( sam_id, core_id, 10000, 30000, 3600, 0, true,\n                                              db.head_block_time() + fc::days(1), collateral_map_core, {} );\n      credit_offer_id_type co1_id = coo1.get_id();\n\n      const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, true,\n                                              db.head_block_time() + fc::days(1), collateral_map_usd, {} );\n      credit_offer_id_type co2_id = coo2.get_id();\n\n      const credit_offer_object& coo3 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, true,\n                                              db.head_block_time() + fc::days(1), collateral_map_eur, {} );\n      credit_offer_id_type co3_id = coo3.get_id();\n\n      const credit_offer_object& coo4 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, true,\n                                              db.head_block_time() + fc::days(1), collateral_map_eur, {} );\n      credit_offer_id_type co4_id = coo4.get_id();\n\n      const credit_offer_object& coo5 = create_credit_offer( sam_id, usd_id, 10000, 30000, 3600, 0, true,\n                                              db.head_block_time() + fc::days(1), collateral_map_usd, {} );\n      credit_offer_id_type co5_id = coo5.get_id();\n\n      const credit_offer_object& coo6 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, true,\n                                              db.head_block_time() + fc::days(1), collateral_map_usd, {} );\n      credit_offer_id_type co6_id = coo6.get_id();\n\n      generate_block();\n\n      // Check database API\n      graphene::app::database_api db_api( db, &( app.get_options() ) );\n\n      // List all credit offers\n      auto offers = db_api.list_credit_offers();\n      BOOST_REQUIRE_EQUAL( offers.size(), 6u );\n      BOOST_CHECK( offers.front().id == co1_id );\n      BOOST_CHECK( offers.back().id == co6_id );\n\n      // Pagination : the first page\n      offers = db_api.list_credit_offers( 5 );\n      BOOST_REQUIRE_EQUAL( offers.size(), 5u );\n      BOOST_CHECK( offers.front().id == co1_id );\n      BOOST_CHECK( offers.back().id == co5_id );\n\n      // Pagination : the last page\n      offers = db_api.list_credit_offers( 5, co3_id );\n      BOOST_REQUIRE_EQUAL( offers.size(), 4u );\n      BOOST_CHECK( offers.front().id == co3_id );\n      BOOST_CHECK( offers.back().id == co6_id );\n\n      // Limit too large\n      BOOST_CHECK_THROW( db_api.list_credit_offers( 102 ), fc::exception );\n\n      // Get all credit offers owned by Sam\n      offers = db_api.get_credit_offers_by_owner( \"sam\" );\n      BOOST_REQUIRE_EQUAL( offers.size(), 4u );\n      BOOST_CHECK( offers.front().id == co1_id );\n      BOOST_CHECK( offers.back().id == co5_id );\n\n      // Pagination : the first page\n      offers = db_api.get_credit_offers_by_owner( \"sam\", 3, {} );\n      BOOST_REQUIRE_EQUAL( offers.size(), 3u );\n      BOOST_CHECK( offers.front().id == co1_id );\n      BOOST_CHECK( offers.back().id == co4_id );\n\n      // Pagination : another page\n      offers = db_api.get_credit_offers_by_owner( \"sam\", 3, co2_id );\n      BOOST_REQUIRE_EQUAL( offers.size(), 3u );\n      BOOST_CHECK( offers.front().id == co3_id );\n      BOOST_CHECK( offers.back().id == co5_id );\n\n      // Pagination : the first page of credit offers owned by Ted\n      offers = db_api.get_credit_offers_by_owner( string(\"1.2.\")+fc::to_string(ted_id.instance.value), 3 );\n      BOOST_REQUIRE_EQUAL( offers.size(), 2u );\n      BOOST_CHECK( offers.front().id == co2_id );\n      BOOST_CHECK( offers.back().id == co6_id );\n\n      // Nonexistent account\n      BOOST_CHECK_THROW( db_api.get_credit_offers_by_owner( \"nonexistent-account\" ), fc::exception );\n\n      // Limit too large\n      BOOST_CHECK_THROW( db_api.get_credit_offers_by_owner( \"ted\", 102 ), fc::exception );\n\n      // Get all credit offers whose asset type is USD\n      offers = db_api.get_credit_offers_by_asset( \"MYUSD\" );\n      BOOST_REQUIRE_EQUAL( offers.size(), 3u );\n      BOOST_CHECK( offers.front().id == co2_id );\n      BOOST_CHECK( offers.back().id == co6_id );\n\n      // Pagination : the first page\n      offers = db_api.get_credit_offers_by_asset( \"MYUSD\", 2 );\n      BOOST_REQUIRE_EQUAL( offers.size(), 2u );\n      BOOST_CHECK( offers.front().id == co2_id );\n      BOOST_CHECK( offers.back().id == co5_id );\n\n      // Pagination : another page\n      offers = db_api.get_credit_offers_by_asset( \"MYUSD\", 2, co4_id );\n      BOOST_REQUIRE_EQUAL( offers.size(), 2u );\n      BOOST_CHECK( offers.front().id == co5_id );\n      BOOST_CHECK( offers.back().id == co6_id );\n\n      // Pagination : the first page of credit offers whose asset type is CORE\n      offers = db_api.get_credit_offers_by_asset( \"1.3.0\", 2, {} );\n      BOOST_REQUIRE_EQUAL( offers.size(), 1u );\n      BOOST_CHECK( offers.front().id == co1_id );\n      BOOST_CHECK( offers.back().id == co1_id );\n\n      // Nonexistent asset\n      BOOST_CHECK_THROW( db_api.get_credit_offers_by_asset( \"NOSUCHASSET\" ), fc::exception );\n\n      // Limit too large\n      BOOST_CHECK_THROW( db_api.get_credit_offers_by_asset( \"MYUSD\", 102 ), fc::exception );\n\n      // Create credit deals\n      // Offer owner : sam\n      const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) );\n      credit_deal_id_type cd11_id = cdo11.get_id();\n\n      // Offer owner : sam\n      const credit_deal_object& cdo12 = borrow_from_credit_offer( ray_id, co1_id, asset(150), asset(400, eur_id) );\n      credit_deal_id_type cd12_id = cdo12.get_id();\n\n      // Offer owner : sam\n      const credit_deal_object& cdo13 = borrow_from_credit_offer( bob_id, co1_id, asset(200), asset(600, eur_id) );\n      credit_deal_id_type cd13_id = cdo13.get_id();\n\n      // Offer owner : ted\n      const credit_deal_object& cdo21 = borrow_from_credit_offer( bob_id, co2_id, asset(500, usd_id),\n                                                                  asset(500, eur_id) );\n      credit_deal_id_type cd21_id = cdo21.get_id();\n\n      // Offer owner : sam\n      const credit_deal_object& cdo31 = borrow_from_credit_offer( bob_id, co3_id, asset(500, eur_id), asset(5000) );\n      credit_deal_id_type cd31_id = cdo31.get_id();\n\n      // Offer owner : sam\n      const credit_deal_object& cdo51 = borrow_from_credit_offer( ray_id, co5_id, asset(400, usd_id),\n                                                                  asset(800, eur_id) );\n      credit_deal_id_type cd51_id = cdo51.get_id();\n\n      generate_block();\n\n      // Since all APIs are now calling the same template function,\n      // no need to to test with many cases only for pagination.\n      auto deals = db_api.list_credit_deals();\n      BOOST_REQUIRE_EQUAL( deals.size(), 6u );\n      BOOST_CHECK( deals.front().id == cd11_id );\n      BOOST_CHECK( deals.back().id == cd51_id );\n\n      deals = db_api.get_credit_deals_by_offer_id( co1_id );\n      BOOST_REQUIRE_EQUAL( deals.size(), 3u );\n      BOOST_CHECK( deals[0].id == cd11_id );\n      BOOST_CHECK( deals[1].id == cd12_id );\n      BOOST_CHECK( deals[2].id == cd13_id );\n\n      deals = db_api.get_credit_deals_by_offer_owner( \"sam\" );\n      BOOST_REQUIRE_EQUAL( deals.size(), 5u );\n      BOOST_CHECK( deals[0].id == cd11_id );\n      BOOST_CHECK( deals[1].id == cd12_id );\n      BOOST_CHECK( deals[2].id == cd13_id );\n      BOOST_CHECK( deals[3].id == cd31_id );\n      BOOST_CHECK( deals[4].id == cd51_id );\n\n      deals = db_api.get_credit_deals_by_borrower( \"bob\" );\n      BOOST_REQUIRE_EQUAL( deals.size(), 3u );\n      BOOST_CHECK( deals[0].id == cd13_id );\n      BOOST_CHECK( deals[1].id == cd21_id );\n      BOOST_CHECK( deals[2].id == cd31_id );\n\n      deals = db_api.get_credit_deals_by_debt_asset( \"MYUSD\" );\n      BOOST_REQUIRE_EQUAL( deals.size(), 2u );\n      BOOST_CHECK( deals[0].id == cd21_id );\n      BOOST_CHECK( deals[1].id == cd51_id );\n\n      deals = db_api.get_credit_deals_by_collateral_asset( \"MYEUR\" );\n      BOOST_REQUIRE_EQUAL( deals.size(), 4u );\n      BOOST_CHECK( deals[0].id == cd12_id );\n      BOOST_CHECK( deals[1].id == cd13_id );\n      BOOST_CHECK( deals[2].id == cd21_id );\n      BOOST_CHECK( deals[3].id == cd51_id );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/custom_authority_tests.cpp",
    "content": "/*\n * Copyright (c) 2019 Contributors\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/**\n * Readers of these custom active authority (CAA) tests may benefit by reviewing\n *\n * - rejection_indicator variant in restriction_predicate.hpp\n * - function_type enum in restriction.hpp\n * - GRAPHENE_OP_RESTRICTION_ARGUMENTS_VARIADIC in restriction.hpp\n */\n\n#include <string>\n#include <boost/test/unit_test.hpp>\n#include <fc/exception/exception.hpp>\n#include <graphene/protocol/restriction_predicate.hpp>\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/custom_authority_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\n// Dependencies required by the HTLC-related tests\n#include <random>\n#include <graphene/chain/htlc_object.hpp>\n\n// Dependencies for the voting and witness tests\n#include <graphene/chain/witness_object.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nnamespace graphene { namespace protocol {\nbool operator==(const restriction& a, const restriction& b) {\n   if (std::tie(a.member_index, a.restriction_type) != std::tie(b.member_index, b.restriction_type))\n      return false;\n   if (a.argument.is_type<void_t>())\n      return b.argument.is_type<void_t>();\n   using Value_Argument = static_variant<fc::typelist::slice<restriction::argument_type::list, 1>>;\n   return Value_Argument::import_from(a.argument) == Value_Argument::import_from(b.argument);\n}\n} }\n\n\nBOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture)\n\n#define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE)\n\ntemplate<typename Object>\nunsigned_int member_index(string name) {\n   unsigned_int index;\n   fc::typelist::runtime::for_each(typename fc::reflector<Object>::native_members(), [&name, &index](auto t) mutable {\n      if (name == decltype(t)::type::get_name())\n         index = decltype(t)::type::index;\n   });\n   return index;\n}\n\ntemplate<typename Expression>\nvoid expect_exception_string(const string& s, Expression e) {\n   try{\n      e();\n      FC_THROW_EXCEPTION(fc::assert_exception, \"Expected exception with string ${s}, but no exception thrown\",\n                         (\"s\", s));\n   } catch (const fc::exception& e) {\n      FC_ASSERT(e.to_detail_string().find(s) != string::npos, \"Did not find expected string ${s} in exception: ${e}\",\n                (\"s\", s)(\"e\", e));\n   }\n}\n#define EXPECT_EXCEPTION_STRING(S, E) \\\n    BOOST_TEST_CHECKPOINT(\"Expect exception containing string: \" S); \\\n    expect_exception_string(S, E)\n\nBOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try {\n   using namespace graphene::protocol;\n   //////\n   // Create a restriction that authorizes transfers only made to Account ID 12\n   //////\n   vector<restriction> restrictions;\n   auto to_index = member_index<transfer_operation>(\"to\");\n   restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12));\n\n   //////\n   // Create an operation that transfers to Account ID 0\n   // This should violate the restriction\n   //////\n   transfer_operation transfer;\n   // Check that the proposed operation to account ID 0 is not compliant with the restriction to account ID 12\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer) == false);\n   // Inspect the reasons why the proposed operation was rejected\n   // The rejection path will reference portions of the restrictions\n   //[\n   //  {\n   //    \"member_index\": 2,\n   //    \"restriction_type\": 0,\n   //    \"argument\": [\n   //      7,\n   //      \"1.2.12\"\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n   BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 1);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path.size() == 2);\n   // Index 0 (the outer-most) rejection path refers to the first and only outer-most sub-restriction\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[0].get<size_t>() == 0);\n   // Index 1 (the inner-most) rejection path refers to the first and only argument for an account ID of 1.2.12\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[1].get<predicate_result::rejection_reason>() == predicate_result::predicate_was_false);\n\n   //////\n   // Create an operation that transfer to Account ID 12\n   // This should satisfy the restriction\n   //////\n   transfer.to = account_id_type(12);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer) == true);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path.size() == 0);\n\n\n   //////\n   // Create an INVALID restriction that references an invalid member index\n   // (Index 6 is greater than the highest 0-based index of 5)\n   // of the transfer operation\n   //////\n   restrictions.front() = restriction(fc::typelist::length<fc::reflector<transfer_operation>::native_members>(),\n                                      FUNC(eq), account_id_type(12));\n   //[\n   //  {\n   //    \"member_index\": 6,\n   //    \"restriction_type\": 0,\n   //    \"argument\": [\n   //      7,\n   //      \"1.2.12\"\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n   //\n   // This restriction should throw an exception related to an invalid member index\n   //   10 assert_exception: Assert Exception\n   //   r.member_index < typelist::length<member_list>(): Invalid member index 6 for object graphene::protocol::transfer_operation\n   //           {\"I\":6,\"O\":\"graphene::protocol::transfer_operation\"}\n   //   th_a  restriction_predicate.hxx:493 create_field_predicate\n   BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value),\n                     fc::assert_exception);\n\n\n   //////\n   // Create an INVALID restriction that compares a transfer operation's account ID type to an asset ID type\n   //////\n   restrictions.front() = restriction(to_index, FUNC(eq), asset_id_type(12));\n   //[\n   //  {\n   //    \"member_index\": 2,\n   //    \"restriction_type\": 0,\n   //    \"argument\": [\n   //      8,\n   //      \"1.3.12\"\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n   //\n   // This restriction should throw an exception related to invalid type\n   //   10 assert_exception: Assert Exception\n   //   Invalid types for predicate\n   //   {}\n   //   th_a  restriction_predicate.hxx:147 predicate_invalid\n   //\n   //   {\"fc::get_typename<Field>::name()\":\"graphene::protocol::account_id_type\",\"func\":\"func_eq\",\"arg\":[8,\"1.3.12\"]}\n   //   th_a  restriction_predicate.hxx:476 create_predicate_function\n   BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value),\n                     fc::assert_exception);\n\n   //////\n   // Create a restriction such that the operation fee must be paid with Asset ID 0\n   //////\n   auto fee_index = member_index<transfer_operation>(\"fee\");\n   auto asset_id_index = member_index<asset>(\"asset_id\");\n   restrictions.front() = restriction(fee_index, FUNC(attr),\n                                      vector<restriction>{restriction(asset_id_index, FUNC(eq), asset_id_type(0))});\n\n   //////\n   // Check the transfer operation that pays the fee with Asset ID 0\n   // This should satisfy the restriction.\n   //////\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer) == true);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path.size() == 0);\n\n   //////\n   // Change the restriction such that the operation fee must be paid with Asset ID 1\n   //////\n   restrictions.front().argument.get<vector<restriction>>().front().argument = asset_id_type(1);\n   //[\n   //  {\n   //    \"member_index\": 0,\n   //    \"restriction_type\": 10,\n   //    \"argument\": [\n   //      39,\n   //      [\n   //        {\n   //          \"member_index\": 1,\n   //          \"restriction_type\": 0,\n   //          \"argument\": [\n   //            8,\n   //            \"1.3.1\"\n   //          ],\n   //          \"extensions\": []\n   //        }\n   //      ]\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n\n   BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 2);\n   //////\n   // Check the transfer operation that pays the fee with Asset ID 0 against the restriction.\n   // This should violate the restriction.\n   //////\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer) == false);\n   // Inspect the reasons why the proposed operation was rejected\n   // The rejection path will reference portions of the restrictions\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path.size() == 3);\n   // Index 0 (the outer-most) rejection path refers to the first and only outer-most sub-restriction\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[0].get<size_t>() == 0);\n   // Index 1 rejection path refers to the first and only attribute of the restriction\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[1].get<size_t>() == 0);\n   // Index 2 (the inner-most) rejection path refers to the expected rejection reason\n   // The rejection reason should be that the predicate was false\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[2].get<predicate_result::rejection_reason>() == predicate_result::predicate_was_false);\n\n   //////\n   // Create a restriction that authorizes transfers only to Account ID 12\n   //////\n   restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12));\n   //[\n   //  {\n   //    \"member_index\": 0,\n   //    \"restriction_type\": 10,\n   //    \"argument\": [\n   //      39,\n   //      [\n   //        {\n   //          \"member_index\": 1,\n   //          \"restriction_type\": 0,\n   //          \"argument\": [\n   //            8,\n   //            \"1.3.1\"\n   //          ],\n   //          \"extensions\": []\n   //        }\n   //      ]\n   //    ],\n   //    \"extensions\": []\n   //  },\n   //  {\n   //    \"member_index\": 2,\n   //    \"restriction_type\": 0,\n   //    \"argument\": [\n   //      7,\n   //      \"1.2.12\"\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n   BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 3);\n\n   //////\n   // Create a transfer operation that authorizes transfer to Account ID 12\n   // This operation should satisfy the restriction\n   //////\n   transfer.to = account_id_type(12);\n   transfer.fee.asset_id = asset_id_type(1);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer) == true);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path.size() == 0);\n\n   //////\n   // Create a transfer operation that transfers to Account ID 10\n   // This operation should violate the restriction\n   //////\n   transfer.to = account_id_type(10);\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer) == false);\n   // Inspect the reasons why the proposed operation was rejected\n   // The rejection path will reference portions of the restrictions\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path.size() == 2);\n   // Index 0 (the outer-most) rejection path refers to the first and only outer-most sub-restriction\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[0].get<size_t>() == 1);\n   // Index 1 (the inner-most) rejection path refers to the first and only argument\n   BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag<transfer_operation>::value)(transfer)\n               .rejection_path[1].get<predicate_result::rejection_reason>() == predicate_result::predicate_was_false);\n\n   //////\n   // Create a restriction where the ext.owner_special_authority field is unspecified\n   //////\n   restrictions.clear();\n   auto extensions_index = member_index<account_update_operation>(\"extensions\");\n   auto authority_index = member_index<account_update_operation::ext>(\"owner_special_authority\");\n   restrictions.emplace_back(extensions_index, FUNC(attr),\n                             vector<restriction>{restriction(authority_index, FUNC(eq), void_t())});\n   //[\n   //  {\n   //    \"member_index\": 5,\n   //    \"restriction_type\": 10,\n   //    \"argument\": [\n   //      39,\n   //      [\n   //        {\n   //          \"member_index\": 1,\n   //          \"restriction_type\": 0,\n   //          \"argument\": [\n   //            0,\n   //            {}\n   //          ],\n   //          \"extensions\": []\n   //        }\n   //      ]\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n   BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 2);\n   auto predicate = get_restriction_predicate(restrictions, operation::tag<account_update_operation>::value);\n\n   //////\n   // Create an account update operation without any owner_special_authority extension\n   //////\n   account_update_operation update;\n   // The transfer operation should violate the restriction\n   BOOST_CHECK_THROW(predicate(transfer), fc::assert_exception);\n   // The update operation should satisfy the restriction\n   BOOST_CHECK(predicate(update) == true);\n   BOOST_CHECK(predicate(update).rejection_path.size() == 0);\n\n   //////\n   // Change the update operation to include an owner_special_authority\n   // This should violate the restriction\n   //////\n   update.extensions.value.owner_special_authority = special_authority();\n   BOOST_CHECK(predicate(update) == false);\n   BOOST_CHECK_EQUAL(predicate(update).rejection_path.size(), 3);\n   // Index 0 (the outer-most) rejection path refers to the first and only restriction\n   BOOST_CHECK(predicate(update).rejection_path[0].get<size_t>() == 0);\n   // Index 1 rejection path refers to the first and only attribute of the restriction\n   BOOST_CHECK(predicate(update).rejection_path[1].get<size_t>() == 0);\n   // Index 2 (the inner-most) rejection path refers to the expected rejection reason\n   // The rejection reason should be that the predicate was false\n   BOOST_CHECK(predicate(update).rejection_path[2].get<predicate_result::rejection_reason>() ==\n               predicate_result::predicate_was_false);\n\n   //////\n   // Change the restriction where the ext.owner_special_authority field must be specified\n   //////\n   restrictions.front().argument.get<vector<restriction>>().front().restriction_type = FUNC(ne);\n   //[\n   //  {\n   //    \"member_index\": 5,\n   //    \"restriction_type\": 10,\n   //    \"argument\": [\n   //      39,\n   //      [\n   //        {\n   //          \"member_index\": 1,\n   //          \"restriction_type\": 1,\n   //          \"argument\": [\n   //            0,\n   //            {}\n   //          ],\n   //          \"extensions\": []\n   //        }\n   //      ]\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n\n   //////\n   // The update operation should satisfy the new restriction because the ext.owner_special_authority is specified\n   //////\n   predicate = get_restriction_predicate(restrictions, operation::tag<account_update_operation>::value);\n   BOOST_CHECK(predicate(update) == true);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(container_in_not_in_checks) { try {\n   vector<restriction> restrictions;\n   restrictions.emplace_back(member_index<asset_update_feed_producers_operation>(\"new_feed_producers\"), FUNC(in),\n                             flat_set<account_id_type>{account_id_type(5), account_id_type(6), account_id_type(7)});\n   auto pred = get_restriction_predicate(restrictions, operation::tag<asset_update_feed_producers_operation>::value);\n\n   asset_update_feed_producers_operation op;\n   BOOST_CHECK(pred(op));\n   op.new_feed_producers = {account_id_type(1)};\n   BOOST_CHECK(!pred(op));\n   op.new_feed_producers = {account_id_type(5)};\n   BOOST_CHECK(pred(op));\n   op.new_feed_producers = {account_id_type(5), account_id_type(6)};\n   BOOST_CHECK(pred(op));\n   op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7)};\n   BOOST_CHECK(pred(op));\n   op.new_feed_producers = {account_id_type(1), account_id_type(5), account_id_type(6), account_id_type(7)};\n   BOOST_CHECK(!pred(op));\n   op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7), account_id_type(8)};\n   BOOST_CHECK(!pred(op));\n\n   restrictions.front().restriction_type = FUNC(not_in);\n   pred = get_restriction_predicate(restrictions, operation::tag<asset_update_feed_producers_operation>::value);\n   op.new_feed_producers.clear();\n   BOOST_CHECK(pred(op));\n   op.new_feed_producers = {account_id_type(1)};\n   BOOST_CHECK(pred(op));\n   op.new_feed_producers = {account_id_type(5)};\n   BOOST_CHECK(!pred(op));\n   op.new_feed_producers = {account_id_type(5), account_id_type(6)};\n   BOOST_CHECK(!pred(op));\n   op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7)};\n   BOOST_CHECK(!pred(op));\n   op.new_feed_producers = {account_id_type(1), account_id_type(5), account_id_type(6), account_id_type(7)};\n   BOOST_CHECK(!pred(op));\n   op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7), account_id_type(8)};\n   BOOST_CHECK(!pred(op));\n} FC_LOG_AND_RETHROW() }\n\n   /**\n    * Test predicates containing logical ORs\n    * Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie)\n    * to transfer out of her account by using a single custom active authority with two logical OR branches.\n    *\n    * This can alternatively be achieved by using two custom active authority authorizations\n    * as is done in multiple_transfer_custom_auths\n    */\n   BOOST_AUTO_TEST_CASE(logical_or_transfer_predicate_tests) {\n      try {\n         using namespace graphene::protocol;\n         //////\n         // Create a restriction that authorizes transfers only made to Account ID 12 or Account 15\n         //////\n         auto to_index = member_index<transfer_operation>(\"to\");\n         vector<restriction> branch1 = vector<restriction>{restriction(to_index, FUNC(eq), account_id_type(12))};\n         vector<restriction> branch2 = vector<restriction>{restriction(to_index, FUNC(eq), account_id_type(15))};\n         unsigned_int dummy_index = 999;\n         vector<restriction> or_restrictions = {\n                 restriction(dummy_index, FUNC(logical_or), vector<vector<restriction>>{branch1, branch2})};\n         //[\n         //  {\n         //    \"member_index\": 999,\n         //    \"restriction_type\": 11,\n         //    \"argument\": [\n         //      40,\n         //      [\n         //        [\n         //          {\n         //            \"member_index\": 2,\n         //            \"restriction_type\": 0,\n         //            \"argument\": [\n         //              7,\n         //              \"1.2.12\"\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ],\n         //        [\n         //          {\n         //            \"member_index\": 2,\n         //            \"restriction_type\": 0,\n         //            \"argument\": [\n         //              7,\n         //              \"1.2.15\"\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ]\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n         BOOST_CHECK_EQUAL(restriction::restriction_count(or_restrictions), 3);\n         auto predicate = get_restriction_predicate(or_restrictions, operation::tag<transfer_operation>::value);\n\n         //////\n         // Create an operation that transfers to Account ID 12\n         // This should satisfy the restriction because Account ID 12 is authorized to transfer\n         //////\n         transfer_operation transfer_to_12 = transfer_operation();\n         transfer_to_12.to = account_id_type(12);\n         BOOST_CHECK_EQUAL(predicate(transfer_to_12).success, true);\n         BOOST_CHECK_EQUAL(predicate(transfer_to_12).rejection_path.size(), 0);\n\n         //////\n         // Create an operation that transfers to Account ID 15\n         // This should satisfy the restriction because Account ID 15 is authorized to transfer\n         //////\n         transfer_operation transfer_to_15 = transfer_operation();\n         transfer_to_15.to = account_id_type(15);\n         BOOST_CHECK(predicate(transfer_to_15) == true);\n         BOOST_CHECK_EQUAL(predicate(transfer_to_15).rejection_path.size(), 0);\n\n         //////\n         // Create an operation that transfers to Account ID 1\n         // This should violate the restriction because Account 1 is not authorized to transfer\n         //////\n         transfer_operation transfer_to_1;\n         transfer_to_1.to = account_id_type(1);\n         BOOST_CHECK(predicate(transfer_to_1) == false);\n\n         // JSON-formatted Rejection path\n         //[ // A vector of predicate results\n         //  [\n         //    0, // Index 0 (the outer-most) rejection path\n         //    0  // The first and only outer-most sub-restriction\n         //  ],\n         //  [\n         //    1,  // Index 1 (the inner-most) rejection path\n         //    [  // A vector of predicate results\n         //      {\n         //        \"success\": false,\n         //        \"rejection_path\": [\n         //          [\n         //            0, // Index 0 (the outer-most) rejection path\n         //            0  // Restriction 1 along this branch\n         //          ],\n         //          [\n         //            2, // Rejection reason\n         //            \"predicate_was_false\"\n         //          ]\n         //        ]\n         //      },\n         //      {\n         //        \"success\": false,\n         //        \"rejection_path\": [\n         //          [\n         //            0, // Index 0 (the outer-most) rejection path\n         //            0  // Restriction 1 along this branch\n         //          ],\n         //          [\n         //            2, // Rejection reason\n         //            \"predicate_was_false\"\n         //          ]\n         //        ]\n         //      }\n         //    ]\n         //  ]\n         //]\n\n         // C++ style check of the rejection path\n         BOOST_CHECK_EQUAL(predicate(transfer_to_1).rejection_path.size(), 2);\n         // Index 0 (the outer-most) rejection path refers to  and only outer-most sub-restriction\n         BOOST_CHECK(predicate(transfer_to_1).rejection_path[0].get<size_t>() == 0);\n         // Index 1 (the inner-most) rejection path refers to the first and only argument:\n         // the vector of branches each of which are one level deep\n         vector<predicate_result> branch_results = predicate(\n                 transfer_to_1).rejection_path[1].get<vector<predicate_result>>();\n         unsigned long nbr_branches = branch_results.size();\n         BOOST_CHECK_EQUAL(nbr_branches, 2);\n         for (unsigned long j = 0; j < nbr_branches; ++j) {\n            predicate_result &result = branch_results.at(j);\n            BOOST_CHECK_EQUAL(result.success, false);\n\n            BOOST_CHECK_EQUAL(result.rejection_path.size(), 2);\n            // Index 0 (the outer-most) rejection path refers to the first and only restriction\n            BOOST_CHECK_EQUAL(result.rejection_path[0].get<size_t>(), 0);\n            // Index 1 (the inner-most) rejection path refers to the first and only argument for an account ID:\n            // either 1.2.12 or 1.2.15\n            BOOST_CHECK(result.rejection_path[1].get<predicate_result::rejection_reason>() ==\n                        predicate_result::predicate_was_false);\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\nBOOST_AUTO_TEST_CASE(custom_auths_unsupported_operations_test) { try {\n   //////\n   // Initialize the test\n   //////\n   generate_blocks(HARDFORK_BSIP_40_TIME);\n   generate_blocks(5);\n   db.modify(global_property_id_type()(db), [](global_property_object& gpo) {\n      gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n   });\n   set_expiration(db, trx);\n   ACTORS((alice)(bob))\n   fund(alice, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION));\n   fund(bob, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION));\n\n   //////\n   // Create a custom authority where Bob is authorized to do \"fill_order_operation\" with Alice's account,\n   // This should fail since \"fill_order_operation\" is a virtual operation thus is not supported.\n   //////\n   custom_authority_create_operation op;\n   op.account = alice.get_id();\n   op.auth.add_authority(bob.get_id(), 1);\n   op.auth.weight_threshold = 1;\n   op.enabled = true;\n   op.valid_to = db.head_block_time() + 1000;\n   op.operation_type = operation::tag<fill_order_operation>::value;\n   auto account_index = member_index<fill_order_operation>(\"account_id\");\n   op.restrictions = { restriction(account_index, restriction::func_eq, account_id_type(0))};\n\n   //////\n   // Alice publishes the custom authority\n   //////\n   trx.clear();\n   trx.operations = {op};\n   sign(trx, alice_private_key);\n   BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(custom_auths) { try {\n   //////\n   // Initialize the test\n   //////\n   generate_blocks(HARDFORK_BSIP_40_TIME);\n   generate_blocks(5);\n   db.modify(global_property_id_type()(db), [](global_property_object& gpo) {\n      gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n   });\n   set_expiration(db, trx);\n   ACTORS((alice)(bob))\n   fund(alice, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION));\n   fund(bob, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION));\n\n   //////\n   // Create a custom authority where Bob is authorized to transfer from Alice's account\n   // if and only if the transfer amount is less than 100 of Asset ID 0.\n   // This custom authority is NOT YET published.\n   //////\n   custom_authority_create_operation op;\n   op.account = alice.get_id();\n   op.auth.add_authority(bob.get_id(), 1);\n   op.auth.weight_threshold = 1;\n   op.enabled = true;\n   op.valid_to = db.head_block_time() + 1000;\n   op.operation_type = operation::tag<transfer_operation>::value;\n   auto transfer_amount_index = member_index<transfer_operation>(\"amount\");\n   auto asset_amount_index = member_index<asset>(\"amount\");\n   auto asset_id_index = member_index<asset>(\"asset_id\");\n   op.restrictions = {restriction(transfer_amount_index, restriction::func_attr, vector<restriction>{\n                          restriction(asset_amount_index, restriction::func_lt,\n                                      int64_t(100*GRAPHENE_BLOCKCHAIN_PRECISION)),\n                          restriction(asset_id_index, restriction::func_eq, asset_id_type(0))})};\n   //[\n   //  {\n   //    \"member_index\": 3,\n   //    \"restriction_type\": 10,\n   //    \"argument\": [\n   //      39,\n   //      [\n   //        {\n   //          \"member_index\": 0,\n   //          \"restriction_type\": 2,\n   //          \"argument\": [\n   //            2,\n   //            10000000\n   //          ],\n   //          \"extensions\": []\n   //        },\n   //        {\n   //          \"member_index\": 1,\n   //          \"restriction_type\": 0,\n   //          \"argument\": [\n   //            8,\n   //            \"1.3.0\"\n   //          ],\n   //          \"extensions\": []\n   //        }\n   //      ]\n   //    ],\n   //    \"extensions\": []\n   //  }\n   //]\n   BOOST_CHECK_EQUAL(restriction::restriction_count(op.restrictions), 3);\n\n\n   //////\n   // Bob attempts to transfer 99 CORE from Alice's account\n   // This attempt should fail because it is attempted before the custom authority is published\n   //////\n   transfer_operation top;\n   top.to = bob.get_id();\n   top.from = alice.get_id();\n   top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION;\n   trx.operations = {top};\n   sign(trx, bob_private_key);\n   // No custom auth yet; bob's transfer should reject\n   BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n   //////\n   // Alice publishes the custom authority\n   //////\n   trx.clear();\n   trx.operations = {op};\n   sign(trx, alice_private_key);\n   PUSH_TX(db, trx);\n\n   custom_authority_id_type auth_id =\n           db.get_index_type<custom_authority_index>().indices().get<by_account_custom>().find(alice_id)->get_id();\n\n   //////\n   // Bob attempts to transfer 99 CORE from Alice's account\n   // This attempt should succeed because it is attempted after the custom authority is published\n   //////\n   trx.clear();\n   trx.operations = {top};\n   sign(trx, bob_private_key);\n   PUSH_TX(db, trx);\n\n   //////\n   // Bob attempts to transfer 100 CORE from Alice's account\n   // This attempt should fail because it exceeds the authorized amount\n   //////\n   trx.operations.front().get<transfer_operation>().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION;\n   trx.clear_signatures();\n   sign(trx, bob_private_key);\n   // If bob tries to transfer 100, it rejects because the restriction is strictly less than 100\n   EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n   //////\n   // Update the custom authority so that Bob is authorized to transfer from Alice's account\n   // if and only if the transfer amount EXACTLY EQUALS 100 of Asset ID 0.\n   // This custom authority is NOT YET published.\n   //////\n   op.restrictions.front().argument.get<vector<restriction>>().front().restriction_type = restriction::func_eq;\n   custom_authority_update_operation uop;\n   uop.account = alice.get_id();\n   uop.authority_to_update = auth_id;\n   uop.restrictions_to_remove = {0};\n   uop.restrictions_to_add = {op.restrictions.front()};\n   trx.clear();\n   trx.operations = {uop};\n   sign(trx, alice_private_key);\n   PUSH_TX(db, trx);\n\n   BOOST_CHECK(auth_id(db).get_restrictions() == uop.restrictions_to_add);\n\n   //////\n   // Bob attempts to transfer 99 CORE from Alice's account\n   // This attempt should fail because only transfers of 100 CORE are authorized\n   //////\n   trx.clear();\n   trx.operations = {top};\n   trx.expiration += 5;\n   sign(trx, bob_private_key);\n   // The transfer of 99 should reject because the requirement is for exactly 100\n   EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n   //////\n   // Bob attempts to transfer 100 CORE from Alice's account\n   // This attempt should succeed because transfers of exactly 100 CORE are authorized by Alice\n   //////\n   trx.operations.front().get<transfer_operation>().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION;\n   trx.clear_signatures();\n   sign(trx, bob_private_key);\n   PUSH_TX(db, trx);\n   auto transfer = trx;\n\n   generate_block();\n\n   //////\n   // Bob attempts to transfer 100 CORE from Alice's account AGAIN\n   // This attempt should succeed because there are no limits to the quantity of transfers\n   // besides potentially depleting the CORE in Alice's account\n   //////\n   trx.expiration += 5;\n   trx.clear_signatures();\n   sign(trx, bob_private_key);\n   PUSH_TX(db, trx);\n\n   //////\n   // Alice revokes the custom authority for Bob\n   //////\n   custom_authority_delete_operation dop;\n   dop.account = alice.get_id();\n   dop.authority_to_delete = auth_id;\n   trx.clear();\n   trx.operations = {dop};\n   sign(trx, alice_private_key);\n   PUSH_TX(db, trx);\n\n   //////\n   // Bob attempts to transfer 100 CORE from Alice's account\n   // This attempt should fail because it is attempted after the custom authority has been revoked\n   //////\n   transfer.expiration += 10;\n   transfer.clear_signatures();\n   sign(transfer, bob_private_key);\n   BOOST_CHECK_THROW(PUSH_TX(db, transfer), tx_missing_active_auth);\n} FC_LOG_AND_RETHROW() }\n\n\n   /**\n    * Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie)\n    * to transfer out of her account by using two distinct custom active authorities.\n    *\n    * This can alternatively be achieved by using a single custom active authority with two logical OR branches\n    * as is done in logical_or_transfer_predicate_tests\n    */\n   BOOST_AUTO_TEST_CASE(multiple_transfer_custom_auths) {\n      try {\n         //////\n         // Initialize the test\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n         ACTORS((alice)(bob)(charlie)(diana))\n         fund(alice, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n         fund(bob, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Charlie\n         // This attempt should fail because Alice has not authorized anyone to transfer from her account\n         //////\n         transfer_operation bob_transfers_from_alice_to_charlie;\n         bob_transfers_from_alice_to_charlie.to = charlie.get_id();\n         bob_transfers_from_alice_to_charlie.from = alice.get_id();\n         bob_transfers_from_alice_to_charlie.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         trx.operations = {bob_transfers_from_alice_to_charlie};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should fail because Alice has not authorized anyone to transfer from her account\n         //////\n         transfer_operation bob_transfers_from_alice_to_diana;\n         bob_transfers_from_alice_to_diana.to = diana.get_id();\n         bob_transfers_from_alice_to_diana.from = alice.get_id();\n         bob_transfers_from_alice_to_diana.amount.amount = 60 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         trx.operations = {bob_transfers_from_alice_to_diana};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n         //////\n         // Charlie attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should fail because Alice has not authorized anyone to transfer from her account\n         //////\n         transfer_operation charlie_transfers_from_alice_to_diana;\n         charlie_transfers_from_alice_to_diana.to = diana.get_id();\n         charlie_transfers_from_alice_to_diana.from = alice.get_id();\n         charlie_transfers_from_alice_to_diana.amount.amount = 25 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         trx.operations = {charlie_transfers_from_alice_to_diana};\n         sign(trx, charlie_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n         //////\n         // Create a custom authority where Bob is authorized to transfer from Alice's account to Charlie\n         //////\n         custom_authority_create_operation op;\n         op.account = alice.get_id();\n         op.auth.add_authority(bob.get_id(), 1);\n         op.auth.weight_threshold = 1;\n         op.enabled = true;\n         op.valid_to = db.head_block_time() + 1000;\n         op.operation_type = operation::tag<transfer_operation>::value;\n         auto to_index = member_index<transfer_operation>(\"to\");\n         vector<restriction> restrictions;\n         restrictions.emplace_back(to_index, FUNC(eq), charlie.get_id());\n         op.restrictions = restrictions;\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.18\"\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         // Alice publishes the custom authority\n         trx.clear();\n         trx.operations = {op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n         custom_authority_id_type ca_bob_transfers_from_alice_to_charlie =\n                 db.get_index_type<custom_authority_index>().indices().get<by_account_custom>().find(alice_id)\n                    ->get_id();\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Charlie\n         // This attempt should succeed because it is attempted after the custom authority is published\n         //////\n         trx.clear();\n         trx.operations = {bob_transfers_from_alice_to_charlie};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should fail because Alice has not authorized Bob to transfer to Diana\n         //////\n         trx.clear();\n         trx.operations = {bob_transfers_from_alice_to_diana};\n         sign(trx, charlie_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // {\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n         //////\n         // Charlie attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should fail because Alice has not authorized Charlie to transfer to Diana\n         //////\n         trx.clear();\n         trx.operations = {charlie_transfers_from_alice_to_diana};\n         sign(trx, charlie_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // {\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the re-used transactions\n         //////\n         generate_blocks(1);\n\n         //////\n         // Create a custom authority where Charlie is authorized to transfer from Alice's account to Diana\n         //////\n         op = custom_authority_create_operation();\n         op.account = alice.get_id();\n         op.auth.add_authority(charlie.get_id(), 1);\n         op.auth.weight_threshold = 1;\n         op.enabled = true;\n         op.valid_to = db.head_block_time() + 1000;\n         op.operation_type = operation::tag<transfer_operation>::value;\n         restrictions.clear();\n         restrictions.emplace_back(to_index, FUNC(eq), diana.get_id());\n         op.restrictions = restrictions;\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.19\"\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         // Alice publishes the additional custom authority\n         trx.clear();\n         trx.operations = {op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n         // Note the additional custom authority\n         const auto &ca_index = db.get_index_type<custom_authority_index>().indices().get<by_account_custom>();\n\n         auto ca_alice_range = ca_index.equal_range(alice_id);\n         long nbr_alice_auths = std::distance(ca_alice_range.first, ca_alice_range.second);\n         BOOST_CHECK_EQUAL(2, nbr_alice_auths);\n         auto iter = ca_alice_range.first;\n         custom_authority_id_type *ca_charlie_transfers_from_alice_to_diana = nullptr;\n         while (iter != ca_index.end()) {\n            custom_authority_id_type ca_id = iter->get_id();\n            const custom_authority_object *ca = db.find(ca_id);\n            flat_map<account_id_type, weight_type> ca_authorities = ca->auth.account_auths;\n            BOOST_CHECK_EQUAL(1, ca_authorities.size());\n            if (ca_authorities.find(charlie.get_id()) != ca_authorities.end()) {\n               ca_charlie_transfers_from_alice_to_diana = &ca_id;\n               break;\n            }\n\n            iter++;\n         }\n         BOOST_CHECK(ca_charlie_transfers_from_alice_to_diana != nullptr);\n\n         //////\n         // Charlie attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should succeed because it is attempted after the custom authority is published\n         //////\n         trx.clear();\n         trx.operations = {charlie_transfers_from_alice_to_diana};\n         sign(trx, charlie_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob should still be able to transfer from Alice to Charlie\n         // Bob attempts to transfer 100 CORE from Alice's account to Charlie\n         // This attempt should succeed because it was previously authorized by Alice\n         //////\n         trx.clear();\n         trx.operations = {bob_transfers_from_alice_to_charlie};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should fail because Alice has not authorized Bob to transfer to Diana\n         //////\n         trx.clear();\n         trx.operations = {bob_transfers_from_alice_to_diana};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path for the first custom authority\n         // \"rejected_custom_auths\":[[\"1.17.0\",[0,{\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}]]]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         // Check for reference to the second CAA 1.17.0\n         // \"rejected_custom_auths\":[[\"1.17.0\",[0,{\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}]]]\n         EXPECT_EXCEPTION_STRING(\"1.17.0\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the re-used transactions\n         //////\n         generate_blocks(1);\n\n         //////\n         // Alice revokes the custom authority for Bob\n         //////\n         custom_authority_delete_operation revoke_bob_authorization;\n         revoke_bob_authorization.account = alice.get_id();\n         revoke_bob_authorization.authority_to_delete = ca_bob_transfers_from_alice_to_charlie;\n         trx.clear();\n         trx.operations = {revoke_bob_authorization};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Charlie\n         // This attempt should fail because Alice has revoked authorization for Bob to transfer from her account\n         //////\n         trx.clear();\n         trx.operations = {bob_transfers_from_alice_to_charlie};\n         sign(trx, bob_private_key);\n         // General check of the exception\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // Check the rejection path\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         // Check for reference to the second CAA 1.17.1\n         // \"rejected_custom_auths\":[[\"1.17.1\",[0,{\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}]]]\n         EXPECT_EXCEPTION_STRING(\"1.17.1\", [&] {PUSH_TX(db, trx);});\n\n         //////\n         // Charlie attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should succeed because Alice should still be authorized to transfer from Alice account\n         //////\n         trx.clear();\n         trx.operations = {charlie_transfers_from_alice_to_diana};\n         sign(trx, charlie_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to transfer 100 CORE from Alice's account to Diana\n         // This attempt should fail because Alice has not authorized Bob to transfer to Diana\n         //////\n         trx.clear();\n         trx.operations = {bob_transfers_from_alice_to_diana};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n   /**\n    * Test of authorization and revocation of one account (Alice) authorizing another account (Bob)\n    * to trade with her account but not to transfer out of her account\n    */\n   BOOST_AUTO_TEST_CASE(authorized_trader_custom_auths) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Define a market-issued asset called USDBIT\n         //////\n         ACTORS((feedproducer))\n         create_bitasset(\"USDBIT\", feedproducer_id);\n         generate_blocks(1);\n         const auto& bitusd = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"USDBIT\");\n         const auto &core = asset_id_type()(db);\n         update_feed_producers(bitusd, {feedproducer.get_id()});\n\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(5);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         //////\n         // Initialize: Fund some accounts\n         //////\n         ACTORS((alice)(bob)(charlie)(diana))\n         fund(alice, asset(5000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n         fund(bob, asset(100 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         //////\n         // Bob attempts to create a limit order on behalf of Alice\n         // This should fail because Bob is not authorized to trade with her account\n         //////\n         set_expiration( db, trx );\n         trx.operations.clear();\n\n         limit_order_create_operation buy_order;\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = core.amount(59);\n         buy_order.min_to_receive = bitusd.amount(7);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to place limit orders that offer the any asset for sale\n         //////\n         custom_authority_create_operation authorize_limit_orders;\n         authorize_limit_orders.account = alice.get_id();\n         authorize_limit_orders.auth.add_authority(bob.get_id(), 1);\n         authorize_limit_orders.auth.weight_threshold = 1;\n         authorize_limit_orders.enabled = true;\n         authorize_limit_orders.valid_to = db.head_block_time() + 1000;\n         authorize_limit_orders.operation_type = operation::tag<limit_order_create_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_limit_orders};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n         auto caa =\n                 db.get_index_type<custom_authority_index>().indices().get<by_account_custom>().find(alice.get_id());\n         custom_authority_id_type auth_id = caa->get_id();\n\n         custom_authority_create_operation authorize_limit_order_cancellations;\n         authorize_limit_order_cancellations.account = alice.get_id();\n         authorize_limit_order_cancellations.auth.add_authority(bob.get_id(), 1);\n         authorize_limit_order_cancellations.auth.weight_threshold = 1;\n         authorize_limit_order_cancellations.enabled = true;\n         authorize_limit_order_cancellations.valid_to = db.head_block_time() + 1000;\n         authorize_limit_order_cancellations.operation_type = operation::tag<limit_order_cancel_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_limit_order_cancellations};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the buy order transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to create a limit order on behalf of Alice\n         // This should succeed because Bob is authorized to create limit orders\n         //////\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, bob_private_key);\n         auto processed_buy = PUSH_TX(db, trx);\n         const limit_order_object *buy_order_object = db.find<limit_order_object>( processed_buy.operation_results[0].get<object_id_type>() );\n\n\n         //////\n         // Bob attempts to cancel the limit order on behalf of Alice\n         // This should succeed because Bob is authorized to cancel limit orders\n         //////\n         limit_order_cancel_operation cancel_order;\n         cancel_order.fee_paying_account = alice_id;\n         cancel_order.order = buy_order_object->id;\n         trx.clear();\n         trx.operations = {cancel_order};\n         sign(trx, bob_private_key);\n         auto processed_cancelled = PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to transfer funds out of Alice's account\n         // This should fail because Bob is not authorized to transfer funds out of her account\n         //////\n         transfer_operation top;\n         top.to = bob.get_id();\n         top.from = alice.get_id();\n         top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the buy order transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Alice attempts to create her own limit order\n         // This should succeed because Alice has not relinquished her own authority to trade\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = core.amount(59);\n         buy_order.min_to_receive = bitusd.amount(7);\n         buy_order.expiration = time_point_sec::maximum();\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Alice revokes/disables the authorization to create limit orders\n         //////\n         custom_authority_update_operation disable_authorizations;\n         disable_authorizations.account = alice.get_id();\n         disable_authorizations.authority_to_update = auth_id;\n         disable_authorizations.new_enabled = false;\n         trx.clear();\n         trx.operations = {disable_authorizations};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the buy order transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to create a limit order on behalf of Alice\n         // This should fail because Bob is not authorized to trade with her account\n         //////\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of authorization of one account (Alice) authorizing another key\n    * for restricted trading between between ACOIN1 and any BCOIN (BCOIN1, BCOIN2, and BCOIN3).\n    *\n    * The restricted trading authortization will be constructed with one custom authority\n    * containing two \"logical_or\" branches.  One branch authorizes selling ACOINs for BCOINs.\n    * Another branch authorizes selling BCOINs for ACOINs.\n    */\n   BOOST_AUTO_TEST_CASE(authorized_restricted_trading_key) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Fund some accounts\n         //////\n         ACTORS((assetissuer)(alice))\n         fund(alice, asset(5000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         //////\n         // Define a key that can be authorized\n         // This can be a new key or an existing key. The existing key may even be the active key of an account.\n         //////\n         fc::ecc::private_key some_private_key = generate_private_key(\"some key\");\n         public_key_type some_public_key = public_key_type(some_private_key.get_public_key());\n\n\n         //////\n         // Initialize: Create user-issued assets\n         //////\n         upgrade_to_lifetime_member(assetissuer);\n         create_user_issued_asset(\"ACOIN1\", assetissuer,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         create_user_issued_asset(\"BCOIN1\", assetissuer,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         create_user_issued_asset(\"BCOIN2\", assetissuer,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         create_user_issued_asset(\"BCOIN3\", assetissuer,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         create_user_issued_asset(\"CCOIN1\", assetissuer,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         generate_blocks(1);\n         const asset_object &acoin1 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"ACOIN1\");\n         const asset_object &bcoin1 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"BCOIN1\");\n         const asset_object &bcoin2 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"BCOIN2\");\n         const asset_object &bcoin3 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"BCOIN3\");\n         const asset_object &ccoin1 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"CCOIN1\");\n\n         //////\n         // Initialize: Issue UIAs\n         //////\n\n         // Lambda for issuing an asset to an account\n         auto issue_amount_to = [](const account_id_type &issuer, const asset &amount, const account_id_type &to) {\n            asset_issue_operation op;\n            op.issuer = issuer;\n            op.asset_to_issue = amount;\n            op.issue_to_account = to;\n\n            return op;\n         };\n\n         // assetissuer issues A1, B1, and C1 to alice\n         asset_issue_operation issue_a1_to_alice_op\n                 = issue_amount_to(assetissuer.get_id(), asset(1000, acoin1.get_id()), alice.get_id());\n         asset_issue_operation issue_b1_to_alice_op\n                 = issue_amount_to(assetissuer.get_id(), asset(2000, bcoin1.get_id()), alice.get_id());\n         asset_issue_operation issue_c1_to_alice_op\n                 = issue_amount_to(assetissuer.get_id(), asset(2000, ccoin1.get_id()), alice.get_id());\n         trx.clear();\n         trx.operations = {issue_a1_to_alice_op, issue_b1_to_alice_op, issue_c1_to_alice_op};\n         sign(trx, assetissuer_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Some key attempts to create a limit order on behalf of Alice\n         // This should fail because the key is not authorized to trade with her account\n         //////\n         set_expiration( db, trx );\n         trx.operations.clear();\n\n         limit_order_create_operation buy_order;\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = acoin1.amount(60);\n         buy_order.min_to_receive = bcoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for the key's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes a particular key to place limit orders that offer the any asset for sale\n         //////\n         custom_authority_create_operation authorize_limit_orders;\n         authorize_limit_orders.account = alice.get_id();\n         authorize_limit_orders.auth.add_authority(some_public_key, 1);\n         authorize_limit_orders.auth.weight_threshold = 1;\n         authorize_limit_orders.enabled = true;\n         authorize_limit_orders.valid_to = db.head_block_time() + 1000;\n         authorize_limit_orders.operation_type = operation::tag<limit_order_create_operation>::value;\n\n         auto amount_to_sell_index = member_index<limit_order_create_operation>(\"amount_to_sell\");\n         auto min_to_receive_index = member_index<limit_order_create_operation>(\"min_to_receive\");\n         auto asset_id_index = member_index<asset>(\"asset_id\");\n\n         // Define the two set of assets: ACOINs and BCOINs\n         restriction is_acoin_rx = restriction(asset_id_index, FUNC(in),\n                                               flat_set<asset_id_type>{acoin1.get_id()});\n         restriction is_bcoin_rx = restriction(asset_id_index, FUNC(in),\n                                               flat_set<asset_id_type>{bcoin1.get_id(), bcoin2.get_id(), bcoin3.get_id()});\n\n         // Custom Authority 1: Sell ACOINs to buy BCOINs\n         restriction sell_acoin_rx = restriction(amount_to_sell_index, FUNC(attr), vector<restriction>{is_acoin_rx});\n\n         restriction buy_bcoin_rx = restriction(min_to_receive_index, FUNC(attr), vector<restriction>{is_bcoin_rx});\n\n         vector<restriction> branch_sell_acoin_buy_bcoin = {sell_acoin_rx, buy_bcoin_rx};\n\n\n         // Custom Authority 2: Sell BCOINs to buy ACOINs\n         restriction sell_bcoin_rx = restriction(amount_to_sell_index, FUNC(attr), vector<restriction>{is_bcoin_rx});\n         restriction buy_acoin_rx = restriction(min_to_receive_index, FUNC(attr), vector<restriction>{is_acoin_rx});\n\n         vector<restriction> branch_sell_bcoin_buy_acoin = {sell_bcoin_rx, buy_acoin_rx};\n\n\n         unsigned_int dummy_index = 999;\n         restriction trade_acoin_for_bcoin_rx = restriction(dummy_index, FUNC(logical_or),\n                                                            vector<vector<restriction>>{branch_sell_acoin_buy_bcoin,\n                                                                                        branch_sell_bcoin_buy_acoin});\n         authorize_limit_orders.restrictions = {trade_acoin_for_bcoin_rx};\n         //[\n         //  {\n         //    \"member_index\": 999,\n         //    \"restriction_type\": 11,\n         //    \"argument\": [\n         //      40,\n         //      [\n         //        [\n         //          {\n         //            \"member_index\": 2,\n         //            \"restriction_type\": 10,\n         //            \"argument\": [\n         //              39,\n         //              [\n         //                {\n         //                  \"member_index\": 1,\n         //                  \"restriction_type\": 6,\n         //                  \"argument\": [\n         //                    27,\n         //                    [\n         //                      \"1.3.2\"\n         //                    ]\n         //                  ],\n         //                  \"extensions\": []\n         //                }\n         //              ]\n         //            ],\n         //            \"extensions\": []\n         //          },\n         //          {\n         //            \"member_index\": 3,\n         //            \"restriction_type\": 10,\n         //            \"argument\": [\n         //              39,\n         //              [\n         //                {\n         //                  \"member_index\": 1,\n         //                  \"restriction_type\": 6,\n         //                  \"argument\": [\n         //                    27,\n         //                    [\n         //                      \"1.3.3\",\n         //                      \"1.3.4\",\n         //                      \"1.3.5\"\n         //                    ]\n         //                  ],\n         //                  \"extensions\": []\n         //                }\n         //              ]\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ],\n         //        [\n         //          {\n         //            \"member_index\": 2,\n         //            \"restriction_type\": 10,\n         //            \"argument\": [\n         //              39,\n         //              [\n         //                {\n         //                  \"member_index\": 1,\n         //                  \"restriction_type\": 6,\n         //                  \"argument\": [\n         //                    27,\n         //                    [\n         //                      \"1.3.3\",\n         //                      \"1.3.4\",\n         //                      \"1.3.5\"\n         //                    ]\n         //                  ],\n         //                  \"extensions\": []\n         //                }\n         //              ]\n         //            ],\n         //            \"extensions\": []\n         //          },\n         //          {\n         //            \"member_index\": 3,\n         //            \"restriction_type\": 10,\n         //            \"argument\": [\n         //              39,\n         //              [\n         //                {\n         //                  \"member_index\": 1,\n         //                  \"restriction_type\": 6,\n         //                  \"argument\": [\n         //                    27,\n         //                    [\n         //                      \"1.3.2\"\n         //                    ]\n         //                  ],\n         //                  \"extensions\": []\n         //                }\n         //              ]\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ]\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         // Broadcast the authorization\n         trx.clear();\n         trx.operations = {authorize_limit_orders};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         // Authorize the cancellation of orders\n         custom_authority_create_operation authorize_limit_order_cancellations;\n         authorize_limit_order_cancellations.account = alice.get_id();\n         authorize_limit_order_cancellations.auth.add_authority(some_public_key, 1);\n         authorize_limit_order_cancellations.auth.weight_threshold = 1;\n         authorize_limit_order_cancellations.enabled = true;\n         authorize_limit_order_cancellations.valid_to = db.head_block_time() + 1000;\n         authorize_limit_order_cancellations.operation_type = operation::tag<limit_order_cancel_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_limit_order_cancellations};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the buy order transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice\n         // This should succeed because Bob is authorized to create limit orders\n         //////\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         auto processed_buy = PUSH_TX(db, trx);\n         const limit_order_object *buy_order_object =\n            db.find<limit_order_object>( processed_buy.operation_results[0].get<object_id_type>() );\n\n\n         //////\n         // The key attempts to cancel the limit order on behalf of Alice\n         // This should succeed because the key is authorized to cancel limit orders\n         //////\n         limit_order_cancel_operation cancel_order;\n         cancel_order.fee_paying_account = alice_id;\n         cancel_order.order = buy_order_object->id;\n         trx.clear();\n         trx.operations = {cancel_order};\n         sign(trx, some_private_key);\n         auto processed_cancelled = PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the buy order transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell ACOIN1 for CCOIN1\n         // This should fail because the key is not authorized to sell ACOIN1 for CCOIN1\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = acoin1.amount(60);\n         buy_order.min_to_receive = ccoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell CCOIN1 for ACOIN1\n         // This should fail because the key is not authorized to create this exchange offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = ccoin1.amount(60);\n         buy_order.min_to_receive = acoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell BCOIN1 for CCOIN1\n         // This should fail because the key is not authorized to create this exchange offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = bcoin1.amount(60);\n         buy_order.min_to_receive = ccoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell CCOIN1 for BCOIN1\n         // This should fail because the key is not authorized to create this exchange offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = ccoin1.amount(60);\n         buy_order.min_to_receive = bcoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell BCOIN1 for BCOIN2\n         // This should fail because the key is NOT authorized to create this exchange offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = bcoin1.amount(60);\n         buy_order.min_to_receive = bcoin2.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell ACOIN1 for BCOIN1\n         // This should succeed because the key is authorized to create this offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = acoin1.amount(60);\n         buy_order.min_to_receive = bcoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell ACOIN1 for BCOIN2\n         // This should succeed because the key is authorized to create this offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = acoin1.amount(60);\n         buy_order.min_to_receive = bcoin2.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell ACOIN1 for BCOIN3\n         // This should succeed because the key is authorized to create this offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = acoin1.amount(60);\n         buy_order.min_to_receive = bcoin3.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // The key attempts to create a limit order on behalf of Alice to sell BCOIN1 for ACOIN1\n         // This should succeed because the key is authorized to create this offer\n         //////\n         buy_order = limit_order_create_operation();\n         buy_order.seller = alice_id;\n         buy_order.amount_to_sell = bcoin1.amount(60);\n         buy_order.min_to_receive = acoin1.amount(15);\n         buy_order.expiration = time_point_sec::maximum();\n\n         trx.clear();\n         trx.operations = {buy_order};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of authorization of one account (feedproducer) authorizing another account (Bob)\n    * to publish feeds. The authorization remains associated with account even when the account changes its keys.\n    */\n   BOOST_AUTO_TEST_CASE(feed_publisher_authorizes_other_account) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Define a market-issued asset called USDBIT\n         //////\n         ACTORS((feedproducer));\n         create_bitasset(\"USDBIT\", feedproducer_id);\n         generate_blocks(1);\n         const auto& bitusd = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"USDBIT\");\n         const auto &core = asset_id_type()(db);\n         update_feed_producers(bitusd, {feedproducer.get_id()});\n\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(5);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         //////\n         // Initialize: Fund other accounts\n         //////\n         ACTORS((bob))\n         fund(bob, asset(100 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to publish feed of USDBIT on behalf of feedproducer\n         // This should fail because Bob is not authorized to publish the feed\n         //////\n         asset_publish_feed_operation pop;\n         pop.publisher = feedproducer.id;\n         pop.asset_id = bitusd.id;\n         pop.feed = current_feed;\n         if (pop.feed.core_exchange_rate.is_null())\n            pop.feed.core_exchange_rate = pop.feed.settlement_price;\n         trx.clear();\n         trx.operations.emplace_back(std::move(pop));\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // feedproducer authorizes Bob to publish feeds on its behalf\n         //////\n         custom_authority_create_operation authorize_feed_publishing;\n         authorize_feed_publishing.account = feedproducer.get_id();\n         authorize_feed_publishing.auth.add_authority(bob.get_id(), 1);\n         authorize_feed_publishing.auth.weight_threshold = 1;\n         authorize_feed_publishing.enabled = true;\n         authorize_feed_publishing.valid_to = db.head_block_time() + 1000;\n         authorize_feed_publishing.operation_type = operation::tag<asset_publish_feed_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_feed_publishing};\n         sign(trx, feedproducer_private_key);\n         PUSH_TX(db, trx);\n\n         custom_authority_id_type auth_id =\n                 db.get_index_type<custom_authority_index>().indices().get<by_account_custom>().find(feedproducer.get_id())\n                    ->get_id();\n\n         //////\n         // Bob attempts to publish feed of USDBIT on behalf of feedproducer\n         // This should succeed because Bob is authorized by feedproducer to publish the feed\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(pop));\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob creates a new key\n         //////\n         fc::ecc::private_key new_bob_private_key = generate_private_key(\"new Bob key\");\n         public_key_type new_bob_public_key = public_key_type(new_bob_private_key.get_public_key());\n\n\n         //////\n         // Bob attempts to publish feed of USDBIT on behalf of feedproducer with new key\n         // This should fail because the new key is not associated with Bob on the blockchain\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(pop));\n         sign(trx, new_bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob changes his account's active key\n         //////\n         account_update_operation uop;\n         uop.account = bob.get_id();\n         uop.active = authority(1, new_bob_public_key, 1);\n         trx.clear();\n         trx.operations.emplace_back(std::move(uop));\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to publish feed of USDBIT on behalf of feedproducer\n         // This should succeed because Bob's new key is associated with Bob's authorized account.\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(pop));\n         sign(trx, new_bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Feedproducer revokes/disables the authorization by disabling it\n         //////\n         custom_authority_update_operation disable_authorizations;\n         disable_authorizations.account = feedproducer.get_id();\n         disable_authorizations.authority_to_update = auth_id;\n         disable_authorizations.new_enabled = false;\n         trx.clear();\n         trx.operations = {disable_authorizations};\n         sign(trx, feedproducer_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to publish feed of USDBIT on behalf of feedproducer with new key\n         // This should fail because Bob's account is no longer authorized by feedproducer\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(pop));\n         sign(trx, new_bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of authorization of one account (feedproducer) authorizing another key\n    * to publish feeds\n    */\n   BOOST_AUTO_TEST_CASE(authorized_feed_publisher_other_key_custom_auths) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Define a market-issued asset called USDBIT\n         //////\n         ACTORS((feedproducer));\n         const auto &bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n         const auto &core = asset_id_type()(db);\n         update_feed_producers(bitusd, {feedproducer.get_id()});\n\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(5);\n         // publish_feed(bitusd, feedproducer, current_feed);\n         asset_publish_feed_operation pop;\n         pop.publisher = feedproducer.id;\n         pop.asset_id = bitusd.id;\n         pop.feed = current_feed;\n         if (pop.feed.core_exchange_rate.is_null())\n            pop.feed.core_exchange_rate = pop.feed.settlement_price;\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Define a key that can be authorized\n         // This can be a new key or an existing key. The existing key may even be the active key of an account.\n         //////\n         fc::ecc::private_key some_private_key = generate_private_key(\"some key\");\n         public_key_type some_public_key = public_key_type(some_private_key.get_public_key());\n\n\n         //////\n         // feedproducer authorizes a key to publish feeds on its behalf\n         //////\n         custom_authority_create_operation authorize_feed_publishing;\n         authorize_feed_publishing.account = feedproducer.get_id();\n         authorize_feed_publishing.auth.add_authority(some_public_key, 1);\n         authorize_feed_publishing.auth.weight_threshold = 1;\n         authorize_feed_publishing.enabled = true;\n         authorize_feed_publishing.valid_to = db.head_block_time() + 1000;\n         authorize_feed_publishing.operation_type = operation::tag<asset_publish_feed_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_feed_publishing};\n         sign(trx, feedproducer_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Any software client with this key attempts to publish feed of USDBIT on behalf of feedproducer\n         // This should succeed because the pusher of this transaction signs the transaction with the authorized key\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(pop));\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of authorization of one account (faucet) authorizing another key\n    * to register accounts\n    */\n   BOOST_AUTO_TEST_CASE(authorized_faucet_other_key_custom_auths) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: faucet account\n         //////\n         ACTORS((faucet)(charlie));\n         fund(faucet, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n         account_upgrade_operation uop;\n         uop.account_to_upgrade = faucet.get_id();\n         uop.upgrade_to_lifetime_member = true;\n         trx.clear();\n         trx.operations.emplace_back(std::move(uop));\n         sign(trx, faucet_private_key);\n         PUSH_TX(db, trx);\n\n         // Lambda for creating account\n         auto create_account_by_name = [&](const string &name, const account_object& registrar) {\n            account_create_operation create_op;\n            create_op.name = name;\n            public_key_type new_key = public_key_type(generate_private_key(name + \" seed\").get_public_key());\n            create_op.registrar = registrar.id;\n            create_op.owner = authority(1, new_key, 1);\n            create_op.active = authority(1, new_key, 1);\n            create_op.options.memo_key = new_key;\n            create_op.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n\n            return create_op;\n         };\n\n\n         //////\n         // Attempt to register an account with this key\n         // This should succeed because faucet is a lifetime member account\n         //////\n         string name = \"account1\";\n         account_create_operation create_op = create_account_by_name(name, faucet);\n         trx.clear();\n         trx.operations = {create_op};\n         sign(trx, faucet_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Define a key that can be authorized\n         // This can be a new key or an existing key. The existing key may even be the active key of an account.\n         //////\n         fc::ecc::private_key some_private_key = generate_private_key(\"some key\");\n         public_key_type some_public_key = public_key_type(some_private_key.get_public_key());\n\n\n         //////\n         // Attempt to register an account with this key\n         // This should fail because the key is not authorized to register any accounts\n         //////\n         name = \"account2\";\n         create_op = create_account_by_name(name, faucet);\n         trx.clear();\n         trx.operations = {create_op};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n\n         //////\n         // faucet authorizes a key to register accounts on its behalf\n         //////\n         custom_authority_create_operation authorize_account_registration;\n         authorize_account_registration.account = faucet.get_id();\n         authorize_account_registration.auth.add_authority(some_public_key, 1);\n         authorize_account_registration.auth.weight_threshold = 1;\n         authorize_account_registration.enabled = true;\n         authorize_account_registration.valid_to = db.head_block_time() + 1000;\n         authorize_account_registration.operation_type = operation::tag<account_create_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_account_registration};\n         sign(trx, faucet_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the account registration transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Attempt to register an account with this key\n         // This should succeed because the key is authorized to register any accounts\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(create_op));\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Attempt to register an account with this key\n         // This should succeed because the key is authorized to register any accounts\n         //////\n         create_op = create_account_by_name(\"account3\", faucet);\n         trx.clear();\n         trx.operations = {create_op};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Attempt to transfer funds out of the faucet account\n         // This should fail because the key is not authorized to transfer from the faucet account\n         //////\n         transfer_operation top;\n         top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         top.from = faucet.get_id();\n         top.to = charlie.get_id();\n         top.fee.asset_id = asset_id_type(1);\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Attempt to register an account with this key\n         // This should succeed because the key is authorized to register any accounts\n         //////\n         create_op = create_account_by_name(\"account4\", faucet);\n         trx.clear();\n         trx.operations = {create_op};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of not equal (ne) restriction on an operation field\n    * Test of CAA for asset_issue_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to issue an asset (ALICECOIN) to any account except a banned account (banned1)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_asset_issue_exceptions_1) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(allowed1)(allowed2)(banned1)(allowed3));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         // Lambda for issuing an asset to an account\n         auto issue_amount_to = [&](const account_id_type &issuer, const asset &amount, const account_id_type &to) {\n            asset_issue_operation op;\n            op.issuer = issuer;\n            op.asset_to_issue = amount;\n            op.issue_to_account = to;\n\n            return op;\n         };\n\n         //////\n         // Create a UIA\n         //////\n         upgrade_to_lifetime_member(alice);\n         create_user_issued_asset(\"ALICECOIN\", alice, white_list);\n         create_user_issued_asset(\"SPECIALCOIN\", alice,  white_list);\n         generate_blocks(1);\n         const asset_object &alicecoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"ALICECOIN\");\n         const asset_object &specialcoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"SPECIALCOIN\");\n         const asset_id_type alicecoin_id = alicecoin.get_id();\n\n\n         //////\n         // Attempt to issue the UIA to an account with the Alice key\n         // This should succeed because Alice is the issuer\n         //////\n         asset_issue_operation issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin_id), allowed1.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to issue the UIA to an allowed account\n         // This should fail because Bob is not authorized to issue any ALICECOIN\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin_id), allowed2.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to issue assets on its behalf\n         // except for account banned1\n         //////\n         custom_authority_create_operation authorize_to_issue;\n         authorize_to_issue.account = alice.get_id();\n         authorize_to_issue.auth.add_authority(bob.get_id(), 1);\n         authorize_to_issue.auth.weight_threshold = 1;\n         authorize_to_issue.enabled = true;\n         authorize_to_issue.valid_to = db.head_block_time() + 1000;\n         authorize_to_issue.operation_type = operation::tag<asset_issue_operation>::value;\n\n         auto asset_index = member_index<asset_issue_operation>(\"asset_to_issue\");\n         auto asset_id_index = member_index<asset>(\"asset_id\");\n         authorize_to_issue.restrictions.emplace_back(restriction(asset_index, restriction::func_attr, vector<restriction>{\n                 restriction(asset_id_index, restriction::func_eq, alicecoin_id)}));\n         auto issue_to_index = member_index<asset_issue_operation>(\"issue_to_account\");\n         authorize_to_issue.restrictions.emplace_back(issue_to_index, FUNC(ne), banned1.get_id());\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 10,\n         //    \"argument\": [\n         //      39,\n         //      [\n         //        {\n         //          \"member_index\": 1,\n         //          \"restriction_type\": 0,\n         //          \"argument\": [\n         //            8,\n         //            \"1.3.2\"\n         //          ],\n         //          \"extensions\": []\n         //        }\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 1,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.20\"\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {authorize_to_issue};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the reused operation\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to issue the UIA to an allowed account\n         // This should succeed because Bob is now authorized to issue ALICECOIN\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(issue_op));\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to issue the special coin to an allowed account\n         // This should fail because Bob is not authorized to issue SPECIALCOIN to any account\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, specialcoin.get_id()), allowed3.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to issue the UIA to a banned account with the Bob's key\n         // This should fail because Bob is not authorized to issue ALICECOIN to the banned account\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin_id), banned1.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n\n\n   /**\n    * Test of not in (not_in) restriction on an operation field\n    * Test of CAA for asset_issue_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to issue an asset (ALICECOIN) except to 3 banned accounts (banned1, banned2, banned3)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_asset_issue_exceptions_2) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(allowed1)(allowed2)(banned1)(banned2)(banned3)(allowed3));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         // Lambda for issuing an asset to an account\n         auto issue_amount_to = [&](const account_id_type &issuer, const asset &amount, const account_id_type &to) {\n            asset_issue_operation op;\n            op.issuer = issuer;\n            op.asset_to_issue = amount;\n            op.issue_to_account = to;\n\n            return op;\n         };\n\n         //////\n         // Create user-issued assets\n         //////\n         upgrade_to_lifetime_member(alice);\n         create_user_issued_asset(\"ALICECOIN\", alice, white_list);\n         create_user_issued_asset(\"SPECIALCOIN\", alice,  white_list);\n         generate_blocks(1);\n         const asset_object &alicecoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"ALICECOIN\");\n         const asset_object &specialcoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"SPECIALCOIN\");\n         const asset_id_type alicecoin_id = alicecoin.get_id();\n\n\n         //////\n         // Attempt to issue the UIA to an account with the Alice key\n         // This should succeed because Alice is the issuer\n         //////\n         asset_issue_operation issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin_id), allowed1.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to issue the UIA to an allowed account\n         // This should fail because Bob is not authorized to issue any ALICECOIN\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin_id), allowed2.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to issue assets on its behalf\n         // except for accounts banned1, banned2, and banned3\n         //////\n         custom_authority_create_operation authorize_to_issue;\n         authorize_to_issue.account = alice.get_id();\n         authorize_to_issue.auth.add_authority(bob.get_id(), 1);\n         authorize_to_issue.auth.weight_threshold = 1;\n         authorize_to_issue.enabled = true;\n         authorize_to_issue.valid_to = db.head_block_time() + 1000;\n         authorize_to_issue.operation_type = operation::tag<asset_issue_operation>::value;\n\n         auto asset_index = member_index<asset_issue_operation>(\"asset_to_issue\");\n         auto asset_id_index = member_index<asset>(\"asset_id\");\n         authorize_to_issue.restrictions.emplace_back(restriction(asset_index, restriction::func_attr, vector<restriction>{\n                 restriction(asset_id_index, restriction::func_eq, alicecoin_id)}));\n         auto issue_to_index = member_index<asset_issue_operation>(\"issue_to_account\");\n         authorize_to_issue.restrictions\n                 .emplace_back(issue_to_index, FUNC(not_in),\n                               flat_set<account_id_type>{banned1.get_id(), banned2.get_id(), banned3.get_id()});\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 10,\n         //    \"argument\": [\n         //      39,\n         //      [\n         //        {\n         //          \"member_index\": 1,\n         //          \"restriction_type\": 0,\n         //          \"argument\": [\n         //            8,\n         //            \"1.3.2\"\n         //          ],\n         //          \"extensions\": []\n         //        }\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 7,\n         //    \"argument\": [\n         //      26,\n         //      [\n         //        \"1.2.20\",\n         //        \"1.2.21\",\n         //        \"1.2.22\"\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {authorize_to_issue};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the reused operation\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to issue the UIA to an allowed account\n         // This should succeed because Bob is now authorized to issue ALICECOIN\n         //////\n         trx.clear();\n         trx.operations.emplace_back(std::move(issue_op));\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to issue the special coin to an allowed account\n         // This should fail because Bob is not authorized to issue SPECIALCOIN to any account\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, specialcoin.get_id()), allowed3.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to issue the UIA to a banned account with the Bob's key\n         // This should fail because Bob is not authorized to issue ALICECOIN to banned account (banned1)\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin.get_id()), banned1.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to issue the UIA to a banned account with the Bob's key\n         // This should fail because Bob is not authorized to issue ALICECOIN to banned account (banned2)\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin.get_id()), banned2.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to issue the UIA to a banned account with the Bob's key\n         // This should fail because Bob is not authorized to issue ALICECOIN to banned account (banned3)\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin.get_id()), banned3.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to issue the UIA to an allowed account\n         // This should succeed because Bob is authorized to issue ALICECOIN to any account\n         //////\n         issue_op = issue_amount_to(alice.get_id(), asset(100, alicecoin.get_id()), allowed3.get_id());\n         trx.clear();\n         trx.operations = {issue_op};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of in (in) restriction on an operation field\n    * Test of CAA for override_transfer_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to override transfer an asset (ALICECOIN) from only 2 accounts (suspicious1, suspicious2)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_override_transfer) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(allowed1)(allowed2)(suspicious1)(suspicious2)(allowed3)(arbitrator));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         // Lambda for issuing an asset to an account\n         auto issue_amount_to = [&](const account_id_type &issuer, const asset &amount, const account_id_type &to) {\n            asset_issue_operation op;\n            op.issuer = issuer;\n            op.asset_to_issue = amount;\n            op.issue_to_account = to;\n\n            return op;\n         };\n\n         // Lambda for reserving an asset from an account\n         auto create_override = [&](const account_id_type &issuer, const account_id_type &from, const asset &amount,\n                                      const account_id_type &to) {\n            override_transfer_operation op;\n            op.issuer = issuer;\n            op.from = from;\n            op.amount = amount;\n            op.to = to;\n\n            return op;\n         };\n\n         //////\n         // Initialize: Create user-issued assets\n         //////\n         upgrade_to_lifetime_member(alice);\n         create_user_issued_asset(\"ALICECOIN\", alice, DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         create_user_issued_asset( \"SPECIALCOIN\", alice,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         generate_blocks(1);\n         const asset_object &alicecoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"ALICECOIN\");\n         const asset_object &specialcoin\n                 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"SPECIALCOIN\");\n\n         //////\n         // Initialize: Alice issues her two coins to different accounts\n         //////\n         asset_issue_operation issue_alice_to_allowed1_op\n                 = issue_amount_to(alice.get_id(), asset(100, alicecoin.get_id()), allowed1.get_id());\n         asset_issue_operation issue_alice_to_allowed2_op\n                 = issue_amount_to(alice.get_id(), asset(200, alicecoin.get_id()), allowed2.get_id());\n         asset_issue_operation issue_alice_to_allowed3_op\n                 = issue_amount_to(alice.get_id(), asset(300, alicecoin.get_id()), allowed3.get_id());\n         asset_issue_operation issue_alice_to_suspicious1_op\n                 = issue_amount_to(alice.get_id(), asset(100, alicecoin.get_id()), suspicious1.get_id());\n         asset_issue_operation issue_alice_to_suspicious2_op\n                 = issue_amount_to(alice.get_id(), asset(200, alicecoin.get_id()), suspicious2.get_id());\n\n         asset_issue_operation issue_special_to_allowed1_op\n                 = issue_amount_to(alice.get_id(), asset(1000, specialcoin.get_id()), allowed1.get_id());\n         asset_issue_operation issue_special_to_allowed2_op\n                 = issue_amount_to(alice.get_id(), asset(2000, specialcoin.get_id()), allowed2.get_id());\n         asset_issue_operation issue_special_to_allowed3_op\n                 = issue_amount_to(alice.get_id(), asset(3000, specialcoin.get_id()), allowed3.get_id());\n         asset_issue_operation issue_special_to_suspicious1_op\n                 = issue_amount_to(alice.get_id(), asset(1000, specialcoin.get_id()), suspicious1.get_id());\n         asset_issue_operation issue_special_to_suspicious2_op\n                 = issue_amount_to(alice.get_id(), asset(2000, specialcoin.get_id()), suspicious2.get_id());\n         trx.clear();\n         trx.operations = {issue_alice_to_allowed1_op, issue_alice_to_allowed2_op, issue_alice_to_allowed3_op,\n                 issue_alice_to_suspicious1_op, issue_alice_to_suspicious2_op,\n                 issue_special_to_allowed1_op, issue_special_to_allowed2_op, issue_special_to_allowed3_op,\n                 issue_special_to_suspicious1_op, issue_special_to_suspicious2_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Alice attempts to override some ALICECOIN from some account\n         // This should succeed because Alice is the issuer\n         //////\n         override_transfer_operation override_op\n                 = create_override(alice.get_id(), allowed1.get_id(), asset(20, alicecoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n         int64_t allowed1_balance_alicecoin_after_override1 = get_balance(allowed1.get_id(), alicecoin.get_id());\n         BOOST_CHECK_EQUAL(allowed1_balance_alicecoin_after_override1, 80);\n\n         override_op\n                 = create_override(alice.get_id(), suspicious1.get_id(), asset(20, alicecoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n         int64_t suspicious1_balance_alicecoin_after_override1\n                 = get_balance(suspicious1.get_id(), alicecoin.get_id());\n         BOOST_CHECK_EQUAL(suspicious1_balance_alicecoin_after_override1, 80);\n\n         override_op\n                 = create_override(alice.get_id(), allowed1.get_id(), asset(200, specialcoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n         int64_t allowed1_balance_specialcoin_after_override1 = get_balance(allowed1.get_id(), specialcoin.get_id());\n         BOOST_CHECK_EQUAL(allowed1_balance_specialcoin_after_override1, 800);\n\n         override_op\n                 = create_override(alice.get_id(), suspicious1.get_id(), asset(200, specialcoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n         int64_t suspicious1_balance_specialcoin_after_override1 = get_balance(suspicious1.get_id(), specialcoin.get_id());\n         BOOST_CHECK_EQUAL(suspicious1_balance_specialcoin_after_override1, 800);\n\n\n         //////\n         // Bob attempts to override some ALICECOIN and SPECIAL from some accounts\n         // This should fail because Bob is not authorized to override any ALICECOIN nor SPECIALCOIN\n         //////\n         override_op = create_override(alice.get_id(), allowed1.get_id(), asset(25, alicecoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n         override_op = create_override(alice.get_id(), allowed1.get_id(), asset(25, specialcoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to override transfer ALICECOIN on its behalf\n         // only for accounts suspicious1, and suspicious2\n         //////\n         custom_authority_create_operation authorize_to_override;\n         authorize_to_override.account = alice.get_id();\n         authorize_to_override.auth.add_authority(bob.get_id(), 1);\n         authorize_to_override.auth.weight_threshold = 1;\n         authorize_to_override.enabled = true;\n         authorize_to_override.valid_to = db.head_block_time() + 1000;\n         authorize_to_override.operation_type = operation::tag<override_transfer_operation>::value;\n\n         auto amount_index = member_index<override_transfer_operation>(\"amount\");\n         auto asset_id_index = member_index<asset>(\"asset_id\");\n         authorize_to_override.restrictions\n                 .emplace_back(restriction(amount_index, restriction::func_attr, vector<restriction>{\n                 restriction(asset_id_index, restriction::func_eq, alicecoin.get_id())}));\n         auto from_index = member_index<override_transfer_operation>(\"from\");\n         authorize_to_override.restrictions\n                 .emplace_back(from_index, FUNC(in),\n                               flat_set<account_id_type>{suspicious1.get_id(), suspicious2.get_id()});\n         //[\n         //  {\n         //    \"member_index\": 4,\n         //    \"restriction_type\": 10,\n         //    \"argument\": [\n         //      39,\n         //      [\n         //        {\n         //          \"member_index\": 1,\n         //          \"restriction_type\": 0,\n         //          \"argument\": [\n         //            8,\n         //            \"1.3.2\"\n         //          ],\n         //          \"extensions\": []\n         //        }\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 6,\n         //    \"argument\": [\n         //      26,\n         //      [\n         //        \"1.2.20\",\n         //        \"1.2.21\"\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {authorize_to_override};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the reused operation\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to override transfer some ALICECOIN from a suspicious account\n         // This should succeed because Bob is now authorized to override ALICECOIN from some accounts\n         //////\n         override_op = create_override(alice.get_id(), suspicious1.get_id(), asset(25, alicecoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n         int64_t suspicious1_balance_alicecoin_after_override2\n                 = get_balance(suspicious1.get_id(), alicecoin.get_id());\n         BOOST_CHECK_EQUAL(suspicious1_balance_alicecoin_after_override2, suspicious1_balance_alicecoin_after_override1 - 25);\n\n\n         //////\n         // Bob attempts to override transfer some SPECIALCOIN from a suspicious account\n         // This should fail because Bob is not authorized to override SPECIALCOIN from any accounts\n         //////\n         override_op = create_override(alice.get_id(), suspicious1.get_id(), asset(250, specialcoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to override transfer some SPECIALCOIN from an allowed account\n         // This should fail because Bob is not authorized to override SPECIALCOIN from any accounts\n         //////\n         override_op = create_override(alice.get_id(), allowed3.get_id(), asset(250, specialcoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to override transfer some ALICECOIN from an allowed account\n         // This should fail because Bob is only authorized to override ALICECOIN from suspicious accounts\n         //////\n         override_op = create_override(alice.get_id(), allowed2.get_id(), asset(20, alicecoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         int64_t allowed2_balance_alicecoin_after_no_override\n                 = get_balance(allowed2.get_id(), alicecoin.get_id());\n         BOOST_CHECK_EQUAL(allowed2_balance_alicecoin_after_no_override, 200);\n         int64_t allowed2_balance_specialcoin_no_override\n                 = get_balance(allowed2.get_id(), specialcoin.get_id());\n         BOOST_CHECK_EQUAL(allowed2_balance_specialcoin_no_override, 2000);\n\n\n         //////\n         // Alice attempts to override transfer of SPECIAL COIN from an allowed account\n         // This should succeed because Alice has not revoked her own authorities as issuer\n         //////\n         override_op = create_override(alice.get_id(), allowed3.get_id(), asset(500, specialcoin.get_id()), arbitrator.get_id());\n         trx.clear();\n         trx.operations = {override_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n         int64_t allowed3_balance_alicecoin_after_no_override\n                 = get_balance(allowed3.get_id(), alicecoin.get_id());\n         BOOST_CHECK_EQUAL(allowed3_balance_alicecoin_after_no_override, 300);\n         int64_t allowed3_balance_specialcoin_after_override1\n                 = get_balance(allowed3.get_id(), specialcoin.get_id());\n         BOOST_CHECK_EQUAL(allowed3_balance_specialcoin_after_override1, 3000 - 500);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of authorization of a key to transfer one asset type (USDBIT) from one account (coldwallet)\n    * to another account (hotwallet)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_cold_wallet_key_custom_auths) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         ACTORS((feedproducer)(coldwallet)(hotwallet)(hacker));\n         int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n         //////\n         // Initialize: Define a market-issued asset called USDBIT\n         //////\n         // Define core asset\n         const auto &core = asset_id_type()(db);\n         asset_id_type core_id = core.get_id();\n\n         // Create a smart asset\n         const asset_object &bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n         asset_id_type usd_id = bitusd.get_id();\n         update_feed_producers(bitusd, {feedproducer.get_id()});\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(5);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         //////\n         // Fund coldwallet with core asset\n         //////\n         fund(coldwallet, asset(init_balance));\n         // coldwallet will borrow 1000 bitUSD\n         borrow(coldwallet, bitusd.amount(1000), asset(15000));\n         int64_t coldwallet_balance_usd_before_offer = get_balance(coldwallet_id, usd_id);\n         BOOST_CHECK_EQUAL( 1000,  coldwallet_balance_usd_before_offer);\n         int64_t coldwallet_balance_core_before_offer = get_balance(coldwallet_id, core_id);\n         BOOST_CHECK_EQUAL( init_balance - 15000, coldwallet_balance_core_before_offer );\n\n\n         //////\n         // Define a key that can be authorized\n         // This can be a new key or an existing key. The existing key may even be the active key of an account.\n         //////\n         fc::ecc::private_key some_private_key = generate_private_key(\"some key\");\n         public_key_type some_public_key = public_key_type(some_private_key.get_public_key());\n\n\n         //////\n         // Create a custom authority where the key is authorized to transfer from the coldwallet account\n         // if and only if the transfer asset type is USDBIT and the recipient account is hotwallet.\n         //////\n         custom_authority_create_operation op;\n         op.account = coldwallet.get_id();\n         op.auth.add_authority(some_public_key, 1);\n         op.auth.weight_threshold = 1;\n         op.enabled = true;\n         op.valid_to = db.head_block_time() + 1000;\n\n         op.operation_type = operation::tag<transfer_operation>::value;\n\n         auto to_index = member_index<transfer_operation>(\"to\");\n         op.restrictions.emplace_back(to_index, FUNC(eq), hotwallet_id);\n\n         auto transfer_amount_index = member_index<transfer_operation>(\"amount\");\n         auto asset_id_index = member_index<asset>(\"asset_id\");\n         op.restrictions.emplace_back(restriction(transfer_amount_index, restriction::func_attr, vector<restriction>{\n                 restriction(asset_id_index, restriction::func_eq, usd_id)}));\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.18\"\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 10,\n         //    \"argument\": [\n         //      39,\n         //      [\n         //        {\n         //          \"member_index\": 1,\n         //          \"restriction_type\": 0,\n         //          \"argument\": [\n         //            8,\n         //            \"1.3.2\"\n         //          ],\n         //          \"extensions\": []\n         //        }\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n         BOOST_CHECK_EQUAL(restriction::restriction_count(op.restrictions), 3);\n\n         // Publish the new custom authority\n         trx.clear();\n         trx.operations = {op};\n         sign(trx, coldwallet_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Attempt to transfer USDBIT asset out of the coldwallet to the hacker account\n         // This should fail because the key is not authorized to transfer to the hacker account\n         //////\n         transfer_operation top;\n         top.from = coldwallet.get_id();\n         top.to = hacker.get_id();\n         top.amount.asset_id = usd_id;\n         top.amount.amount = 99;\n         top.fee.asset_id = core_id;\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Attempt to transfer CORE asset out of the coldwallet to the hotwallet account\n         // This should fail because the key is not authorized to transfer core asset to the hotwallet account\n         //////\n         top = transfer_operation();\n         top.from = coldwallet.get_id();\n         top.to = hotwallet.get_id();\n         top.amount.asset_id = core_id;\n         top.amount.amount = 99;\n         top.fee.asset_id = core_id;\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, some_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,1],[0,0],[2,\"predicate_was_false\"]\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Attempt to transfer USDBIT asset out of the coldwallet to the hotwallet account\n         // This should succeed because the key is authorized to transfer USDBIT asset to the hotwallet account\n         //////\n         top = transfer_operation();\n         top.from = coldwallet.get_id();\n         top.to = hotwallet.get_id();\n         top.amount.asset_id = usd_id;\n         top.amount.amount = 99;\n         top.fee.asset_id = core_id;\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, some_private_key);\n         PUSH_TX(db, trx);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n   /**\n    * Test of a restriction on an optional operation field\n    * Variation of the the original transfer_with_memo test for CAA\n    * Bob is authorized to transfer Alice's account to Charlies's account if\n    * - the memo is not set OR\n    * - the memo is set where the \"from\" equal's Bob's public key and \"to\" equals Diana's public *active* key\n    * (The active key is chosen for simplicity. Other keys such as the memo key or an alternate key could also be used.)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_transfer_with_memo_1) {\n      try {\n         //////\n         // Initialize the test\n         //////\n         ACTORS((alice)(bob)(charlie)(diana))\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object& gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n         transfer(account_id_type(), alice_id, asset(1000));\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 1000);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 00);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n\n         //////\n         // Alice transfers to Charlie with her own authorization\n         //////\n         transfer_operation top;\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         top.memo = memo_data();\n         top.memo->set_message(alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n         trx.operations = {top};\n         trx.sign(alice_private_key, db.get_chain_id());\n         auto processed = PUSH_TX(db, trx);\n\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 950);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 50);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n         auto memo = db.get_recent_transaction(processed.id()).operations.front().get<transfer_operation>().memo;\n         BOOST_CHECK(memo);\n         BOOST_CHECK_EQUAL(memo->get_message(bob_private_key, alice_public_key), \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie\n         // This should fail because Bob is not authorized\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the re-used transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to transfer to Charlie if\n         // - the memo is not set OR\n         // - the memo is set where the \"from\" equal's Bob's public key and \"to\" equals Diana's public key\n         //////\n         custom_authority_create_operation caop;\n         caop.account = alice.get_id();\n         caop.auth.add_authority(bob.get_id(), 1);\n         caop.auth.weight_threshold = 1;\n         caop.enabled = true;\n         caop.valid_to = db.head_block_time() + 1000;\n         caop.operation_type = operation::tag<transfer_operation>::value;\n\n         vector<restriction> restrictions;\n\n         // Restriction 1 should have \"to\" to equal Charlie\n         auto to_index = member_index<transfer_operation>(\"to\");\n         auto memo_index = member_index<transfer_operation>(\"memo\");\n         auto to_inside_memo_index = member_index<memo_data>(\"to\");\n         restrictions.emplace_back(to_index, FUNC(eq), charlie.get_id());\n\n         // Restriction 2 is logical OR restriction\n         // Branch 1 should have the memo \"to\" to not be set (to equal void)\n         vector<restriction> branch1 = vector<restriction>{restriction(memo_index, FUNC(eq), void_t())};\n         // Branch 2 should have the memo \"to\" reference Diana's public *active* key\n         // and \"from\" reference Bob's public *active* key\n         auto from_inside_memo_index = member_index<memo_data>(\"from\");\n         vector<restriction> branch2 = vector<restriction>{restriction(memo_index, restriction::func_attr,\n                                                                       vector<restriction>{\n                 restriction(to_inside_memo_index, FUNC(eq), diana_public_key),\n                 restriction(from_inside_memo_index, FUNC(eq), bob_public_key)})};\n         unsigned_int dummy_index = 999;\n         restriction or_restriction = restriction(dummy_index, FUNC(logical_or), vector<vector<restriction>>{branch1, branch2});\n         restrictions.emplace_back(or_restriction);\n         caop.restrictions = restrictions;\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.18\"\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 999,\n         //    \"restriction_type\": 11,\n         //    \"argument\": [\n         //      40,\n         //      [\n         //        [\n         //          {\n         //            \"member_index\": 4,\n         //            \"restriction_type\": 0,\n         //            \"argument\": [\n         //              0,\n         //              {}\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ],\n         //        [\n         //          {\n         //            \"member_index\": 4,\n         //            \"restriction_type\": 10,\n         //            \"argument\": [\n         //              39,\n         //              [\n         //                {\n         //                  \"member_index\": 1,\n         //                  \"restriction_type\": 0,\n         //                  \"argument\": [\n         //                    5,\n         //                    \"BTS6MWg7PpE6azCGwKuhB17DbtSqhzf8i25hspdhndsf7VfsLee7k\"\n         //                  ],\n         //                  \"extensions\": []\n         //                },\n         //                {\n         //                  \"member_index\": 0,\n         //                  \"restriction_type\": 0,\n         //                  \"argument\": [\n         //                    5,\n         //                    \"BTS5VE6Dgy9FUmd1mFotXwF88HkQN1KysCWLPqpVnDMjRvGRi1YrM\"\n         //                  ],\n         //                  \"extensions\": []\n         //                }\n         //              ]\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ]\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {caop};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie WITHOUT a memo\n         // This should succeed\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the re-used transfer op\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 900);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 100);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie with a memo\n         // where \"from\" equals Bob's public key and \"to\" equals Diana's public key\n         // This should succeed\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the similar transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         top.memo = memo_data();\n         top.memo->set_message(bob_private_key, diana_public_key,\n                               \"Dear Diana,\\n\\nOnly you should be able to read this\\n\\nLove, Bob\");\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         processed = PUSH_TX(db, trx);\n\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 850);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 150);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n         memo = db.get_recent_transaction(processed.id()).operations.front().get<transfer_operation>().memo;\n         BOOST_CHECK(memo);\n         BOOST_CHECK_EQUAL(memo->get_message(diana_private_key, bob_public_key),\n                           \"Dear Diana,\\n\\nOnly you should be able to read this\\n\\nLove, Bob\");\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie with a memo\n         // where \"from\" equals Bob's public key and \"to\" equals Charlie's public key\n         // This should fail because it violates the memo restriction\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the similar transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         top.memo = memo_data();\n         top.memo->set_message(bob_private_key, charlie_public_key,\n                               \"Dear Charlie,\\n\\nOnly you should be able to read this\\n\\nLove, Bob\");\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n\n         // The failure should indicate a violation of both branches of the OR memo restrictions\n         // JSON style check of the rejection path\n         // JSON-formatted Rejection path\n         //[ // A vector of predicate results\n         //  [\n         //    0, // Index 0 (the outer-most) rejection path\n         //    1  // 1 is the index for Restriction 2\n         //  ],\n         //  [\n         //    1, // A (sub-)vector of predicate results\n         //    [\n         //      {\n         //        \"success\": false,\n         //        \"rejection_path\": [\n         //          [\n         //            0, // Index 0 of Branch 1 rejection path\n         //            0  // Restriction 1 along this branch\n         //          ],\n         //          [\n         //            2, // Rejection reason\n         //            \"predicate_was_false\"\n         //          ]\n         //        ]\n         //      },\n         //      {\n         //        \"success\": false,\n         //        \"rejection_path\": [\n         //          [\n         //            0, // Index 0 of Branch 2 rejection path\n         //            0  // Restriction 1 along this branch\n         //          ],\n         //          [\n         //            0, // Index 1 of Branch 2 rejection path\n         //            0  // First and only attribute of sub-restriction\n         //          ],\n         //          [\n         //            2, // Rejection reeason\n         //            \"predicate_was_false\"\n         //          ]\n         //        ]\n         //      }\n         //    ]\n         //  ]\n         //]\n         EXPECT_EXCEPTION_STRING(\"[[0,1],[1,[{\\\"success\\\":false,\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]},{\\\"success\\\":false,\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]]}]]]\", [&] {PUSH_TX(db, trx);});\n\n         //////\n         // Bob attempts to transfer from Alice to Diana\n         // This should fail because the transfer must be to Charlie\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the similar transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = diana.get_id();\n         top.amount = asset(50);\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of a restriction on an optional operation field\n    * Variation of the the original transfer_with_memo test for CAA\n    * Bob is authorized to transfer from Alice's account to Charlies's account only if\n    * - the memo is set where the \"from\" equal's Bob's public key and \"to\" equals Diana's public *active* key\n    * (The active key is chosen for simplicity. Other keys such as the memo key or an alternate key could also be used.)\n    *\n    * A memo field is implicitly required.  Attempts without a memo field should have a rejection reason of null_optional\n    */\n   BOOST_AUTO_TEST_CASE(authorized_transfer_with_memo_2) {\n      try {\n         //////\n         // Initialize the test\n         //////\n         ACTORS((alice)(bob)(charlie)(diana))\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object& gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n         transfer(account_id_type(), alice_id, asset(1000));\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 1000);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 00);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n\n         //////\n         // Alice transfers to Charlie with her own authorization\n         //////\n         transfer_operation top;\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         top.memo = memo_data();\n         top.memo->set_message(alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n         trx.operations = {top};\n         trx.sign(alice_private_key, db.get_chain_id());\n         auto processed = PUSH_TX(db, trx);\n\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 950);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 50);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n         auto memo = db.get_recent_transaction(processed.id()).operations.front().get<transfer_operation>().memo;\n         BOOST_CHECK(memo);\n         BOOST_CHECK_EQUAL(memo->get_message(bob_private_key, alice_public_key), \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie\n         // This should fail because Bob is not authorized\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the re-used transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to transfer to Charlie if\n         // - the memo is set where the \"from\" equal's Bob's public key and \"to\" equals Diana's public key\n         //////\n         custom_authority_create_operation caop;\n         caop.account = alice.get_id();\n         caop.auth.add_authority(bob.get_id(), 1);\n         caop.auth.weight_threshold = 1;\n         caop.enabled = true;\n         caop.valid_to = db.head_block_time() + 1000;\n         caop.operation_type = operation::tag<transfer_operation>::value;\n\n         vector<restriction> restrictions;\n\n         // Restriction 1 should have \"to\" to equal Charlie\n         auto to_index = member_index<transfer_operation>(\"to\");\n         auto memo_index = member_index<transfer_operation>(\"memo\");\n         auto to_inside_memo_index = member_index<memo_data>(\"to\");\n         restrictions.emplace_back(to_index, FUNC(eq), charlie.get_id());\n\n         // Branch 2 should have the memo \"to\" reference Diana's public *active* key\n         // and \"from\" reference Bob's public *active* key\n         auto from_inside_memo_index = member_index<memo_data>(\"from\");\n         restrictions.emplace_back(restriction(memo_index, restriction::func_attr,\n                                               vector<restriction>{\n                                                       restriction(to_inside_memo_index, FUNC(eq), diana_public_key),\n                                                       restriction(from_inside_memo_index, FUNC(eq), bob_public_key)}));\n         caop.restrictions = restrictions;\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.18\"\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 4,\n         //    \"restriction_type\": 10,\n         //    \"argument\": [\n         //      39,\n         //      [\n         //        {\n         //          \"member_index\": 1,\n         //          \"restriction_type\": 0,\n         //          \"argument\": [\n         //            5,\n         //            \"BTS6MWg7PpE6azCGwKuhB17DbtSqhzf8i25hspdhndsf7VfsLee7k\"\n         //          ],\n         //          \"extensions\": []\n         //        },\n         //        {\n         //          \"member_index\": 0,\n         //          \"restriction_type\": 0,\n         //          \"argument\": [\n         //            5,\n         //            \"BTS5VE6Dgy9FUmd1mFotXwF88HkQN1KysCWLPqpVnDMjRvGRi1YrM\"\n         //          ],\n         //          \"extensions\": []\n         //        }\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {caop};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie WITHOUT a memo\n         // This should fail because Restriction 2 expects a memo\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the re-used transfer op\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [2,\"null_optional\"]: 0 is the rejection_indicator for rejection_reason; \"null_optional\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"null_optional\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie with a memo\n         // where \"from\" equals Bob's public key and \"to\" equals Diana's public key\n         // This should succeed\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the similar transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         top.memo = memo_data();\n         top.memo->set_message(bob_private_key, diana_public_key,\n                               \"Dear Diana,\\n\\nOnly you should be able to read this\\n\\nLove, Bob\");\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         processed = PUSH_TX(db, trx);\n\n         BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 900);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, asset_id_type()), 100);\n         BOOST_CHECK_EQUAL(get_balance(diana_id, asset_id_type()), 0);\n\n         memo = db.get_recent_transaction(processed.id()).operations.front().get<transfer_operation>().memo;\n         BOOST_CHECK(memo);\n         BOOST_CHECK_EQUAL(memo->get_message(diana_private_key, bob_public_key),\n                           \"Dear Diana,\\n\\nOnly you should be able to read this\\n\\nLove, Bob\");\n\n         //////\n         // Bob attempts to transfer from Alice to Charlie with a memo\n         // where \"from\" equals Bob's public key and \"to\" equals Charlie's public key\n         // This should fail because it violates the memo restriction\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the similar transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = charlie.get_id();\n         top.amount = asset(50);\n         top.memo = memo_data();\n         top.memo->set_message(bob_private_key, charlie_public_key,\n                               \"Dear Charlie,\\n\\nOnly you should be able to read this\\n\\nLove, Bob\");\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"null_optional\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n         //////\n         // Bob attempts to transfer from Alice to Diana\n         // This should fail because transfer must be to Charlie\n         //////\n         generate_blocks(1); // Advance the blockchain to generate a distinctive hash ID for the similar transfer op\n         top = transfer_operation();\n         top.from = alice.get_id();\n         top.to = diana.get_id();\n         top.amount = asset(50);\n         trx.clear();\n         trx.operations = {top};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of has none (has_none) restriction on a container field\n    * Test of CAA for asset_update_feed_producers_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to update an asset's feed producers as long as the list does not contain\n    * untrusted producers (untrusted1, untrusted2, untrusted3)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_feed_producers_1) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob));\n         ACTORS((trusted1)(trusted2)(trusted3)(trusted4)(trusted5)(trusted6));\n         ACTORS((untrusted1)(untrusted2)(untrusted3));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         // Lambda for update asset feed producers\n         auto create_producers_op = [&](const account_id_type &issuer, const asset_id_type &asset, const flat_set<account_id_type> &new_producers) {\n            asset_update_feed_producers_operation op;\n\n            op.issuer = issuer;\n            op.asset_to_update = asset;\n            op.new_feed_producers = new_producers;\n\n            return op;\n         };\n\n\n         //////\n         // Create user-issued assets\n         //////\n         upgrade_to_lifetime_member(alice);\n         create_bitasset(\"ALICECOIN\", alice.get_id());\n         generate_blocks(1);\n         const asset_object &alicecoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"ALICECOIN\");\n\n\n         //////\n         // Alice attempts to update the feed producers for ALICECOIN\n         // This should succeed because Alice can update her own asset\n         //////\n         flat_set<account_id_type> new_producers = {trusted1.get_id(), trusted2.get_id()};\n         asset_update_feed_producers_operation producers_op\n                 = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the same transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN\n         // This should fail because Bob is not authorized to update feed producers for ALICECOIN\n         //////\n         new_producers = {trusted3.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to update the feed producers\n         // but must not select untrusted1, untrusted2, untrusted3\n         //////\n         custom_authority_create_operation authorize_to_update_feed_producers;\n         authorize_to_update_feed_producers.account = alice.get_id();\n         authorize_to_update_feed_producers.auth.add_authority(bob.get_id(), 1);\n         authorize_to_update_feed_producers.auth.weight_threshold = 1;\n         authorize_to_update_feed_producers.enabled = true;\n         authorize_to_update_feed_producers.valid_to = db.head_block_time() + 1000;\n\n         authorize_to_update_feed_producers.operation_type = operation::tag<asset_update_feed_producers_operation>::value;\n         flat_set<account_id_type> untrusted_producers = {untrusted1.get_id(), untrusted2.get_id(), untrusted3.get_id()};\n         auto new_feed_producers_index = member_index<asset_update_feed_producers_operation>(\"new_feed_producers\");\n         authorize_to_update_feed_producers.restrictions\n                 .emplace_back(new_feed_producers_index, FUNC(has_none), untrusted_producers);\n         //[\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 9,\n         //    \"argument\": [\n         //      26,\n         //      [\n         //        \"1.2.24\",\n         //        \"1.2.25\",\n         //        \"1.2.26\"\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {authorize_to_update_feed_producers};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the same transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN\n         // This should succeed because Bob is now authorized to update the feed producers\n         // and because the selected feed producers are acceptable\n         //////\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with 1 trusted and 1 untrusted account\n         // This should fail because Bob is not authorized to update the feed producers\n         // when an untrusted account is included\n         //////\n         new_producers = {trusted4.get_id(), untrusted1.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with 1 untrusted account\n         // This should fail because Bob is not authorized to update the feed producers\n         // when an untrusted account is included\n         //////\n         new_producers = {trusted4.get_id(), untrusted1.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with two untrusted accounts\n         // This should fail because Bob is not authorized to update the feed producers\n         // when an untrusted account is included\n         //////\n         new_producers = {untrusted2.get_id(), untrusted3.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of has all (has_all) restriction on a container field\n    * Test of CAA for asset_update_feed_producers_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to update an asset's feed producers as long as the list\n    * always includes trusted producers (trusted1, trusted2, trusted3)\n    */\n   BOOST_AUTO_TEST_CASE(authorized_feed_producers_2) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob));\n         ACTORS((trusted1)(trusted2)(trusted3));\n         ACTORS((unknown1)(unknown2)(unknown3)(unknown4)(unknown5)(unknown6)(unknown7)(unknown8)(unknown9));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         // Lambda for update asset feed producers\n         auto create_producers_op = [&](const account_id_type &issuer, const asset_id_type &asset, const flat_set<account_id_type> &new_producers) {\n            asset_update_feed_producers_operation op;\n\n            op.issuer = issuer;\n            op.asset_to_update = asset;\n            op.new_feed_producers = new_producers;\n\n            return op;\n         };\n\n\n         //////\n         // Create user-issued assets\n         //////\n         upgrade_to_lifetime_member(alice);\n         create_bitasset(\"ALICECOIN\", alice.get_id());\n         generate_blocks(1);\n         const asset_object &alicecoin = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"ALICECOIN\");\n\n\n         //////\n         // Alice attempts to update the feed producers for ALICECOIN\n         // This should succeed because Alice can update her own asset\n         //////\n         flat_set<account_id_type> new_producers = {trusted1.get_id(), trusted2.get_id(), trusted3.get_id()};\n         asset_update_feed_producers_operation producers_op\n                 = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the same transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with the required feed producers\n         // and an extra account\n         // This should fail because Bob is not authorized to update feed producers for ALICECOIN\n         //////\n         new_producers = {trusted1.get_id(), trusted2.get_id(), trusted3.get_id(), unknown1.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n         // \"rejected_custom_auths\":[]\n         EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Alice authorizes Bob to update the feed producers\n         // but must not select untrusted1, untrusted2, untrusted3\n         //////\n         custom_authority_create_operation authorize_to_update_feed_producers;\n         authorize_to_update_feed_producers.account = alice.get_id();\n         authorize_to_update_feed_producers.auth.add_authority(bob.get_id(), 1);\n         authorize_to_update_feed_producers.auth.weight_threshold = 1;\n         authorize_to_update_feed_producers.enabled = true;\n         authorize_to_update_feed_producers.valid_to = db.head_block_time() + 1000;\n\n         authorize_to_update_feed_producers.operation_type = operation::tag<asset_update_feed_producers_operation>::value;\n         flat_set<account_id_type> trusted_producers = {trusted1.get_id(), trusted2.get_id(), trusted3.get_id()};\n         auto new_feed_producers_index = member_index<asset_update_feed_producers_operation>(\"new_feed_producers\");\n         authorize_to_update_feed_producers.restrictions\n                 .emplace_back(new_feed_producers_index, FUNC(has_all), trusted_producers);\n         //[\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 8,\n         //    \"argument\": [\n         //      26,\n         //      [\n         //        \"1.2.18\",\n         //        \"1.2.19\",\n         //        \"1.2.20\"\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n         trx.clear();\n         trx.operations = {authorize_to_update_feed_producers};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate a distinctive hash ID for the same transaction\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with the required feed producers\n         // and an extra account\n         // This should succeed because Bob is now authorized to update the feed producers\n         // and because the all of the required feed producers are included\n         //////\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with none of the required feed producers\n         // This should fail not all of the required feed producers are included\n         //////\n         new_producers = {unknown2.get_id(), unknown3.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with only 1 of the required feed producers\n         // and extra accounts\n         // This should fail not all of the required feed producers are included\n         //////\n         new_producers = {trusted1.get_id(), unknown2.get_id(), unknown3.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with only 2 of the required feed producers\n         // and extra accounts\n         // This should fail not all of the required feed producers are included\n         //////\n         new_producers = {trusted1.get_id(), unknown2.get_id(), unknown3.get_id(), trusted2.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n         // The failure should indicate the rejection path\n         // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n         // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n         // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n         EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with all of the required feed producers\n         // and extra accounts\n         // This should succeed because Bob is now authorized to update the feed producers\n         // and because the all of the required feed producers are included\n         //////\n         new_producers = {trusted1.get_id(), unknown2.get_id(), unknown3.get_id(), trusted2.get_id(), trusted3.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n         //////\n         // Bob attempts to update the feed producers for ALICECOIN with all of the required feed producers\n         // in a different order\n         // This should succeed because Bob is now authorized to update the feed producers\n         // and because the all of the required feed producers are included\n         //////\n         new_producers = {trusted3.get_id(), trusted2.get_id(), trusted1.get_id()};\n         producers_op = create_producers_op(alice.get_id(), alicecoin.get_id(), new_producers);\n         trx.clear();\n         trx.operations = {producers_op};\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Generate a random pre-image for HTLC-related tests\n    */\n   void generate_random_preimage(uint16_t key_size, std::vector<char>& vec)\n   {\n      std::independent_bits_engine<std::default_random_engine, sizeof(unsigned), unsigned int> rbe;\n      std::generate(begin(vec), end(vec), std::ref(rbe));\n      return;\n   }\n\n\n   /**\n    * Test of greater than or equal to (ge) restriction on a field\n    * Test of CAA for htlc_create_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to create an HTLC operation as long as the pre-image size is greater than or equal to a specified size\n    *\n    * This test is similar to the HTLC test called \"other_peoples_money\"\n    */\n   BOOST_AUTO_TEST_CASE(authorized_htlc_creation) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         time_point_sec LATER_HF_TIME\n                 = (HARDFORK_BSIP_40_TIME > HARDFORK_CORE_1468_TIME) ? HARDFORK_BSIP_40_TIME : HARDFORK_CORE_1468_TIME;\n         generate_blocks(LATER_HF_TIME);\n         generate_blocks(5);\n         set_expiration(db, trx);\n\n         // Initialize HTLC blockchain parameters\n         trx.clear();\n         set_htlc_committee_parameters();\n         generate_blocks(5);\n\n         // Initialize CAA blockchain parameters\n         trx.clear();\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(gateway));\n         int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n\n\n         //////\n         // Initialize: Pre-image sizes and pre-images to reduce the test variability\n         //////\n         uint16_t pre_image_size_256 = 256;\n         std::vector<char> pre_image_256(pre_image_size_256);\n         generate_random_preimage(pre_image_size_256, pre_image_256);\n\n         // The minimum pre-image size that will be authorized by Alice\n         uint16_t authorized_minimum_pre_image_size_512 = 512;\n\n         int64_t pre_image_size_512 = int64_t(authorized_minimum_pre_image_size_512 + 0);\n         std::vector<char> pre_image_512(pre_image_size_512);\n         generate_random_preimage(pre_image_size_512, pre_image_512);\n\n         int64_t pre_image_size_600 = int64_t(authorized_minimum_pre_image_size_512 + 88);\n         std::vector<char> pre_image_600(pre_image_size_600);\n         generate_random_preimage(pre_image_size_600, pre_image_600);\n\n\n         //////\n         // Alice attempts to put a contract on the blockchain using Alice's funds\n         // This should succeed because Alice is authorized to create HTLC for her own account\n         //////\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 3;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_256 );\n            create_operation.preimage_size = pre_image_size_256;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, alice_private_key);\n            PUSH_TX( db, trx );\n         }\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the similar transactions\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to put a contract on the blockchain using Alice's funds\n         // This should fail because Bob is not authorized to create HTLC on behalf of Alice\n         //////\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 3;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_256 );\n            create_operation.preimage_size = pre_image_size_256;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Alice authorizes Bob to create HTLC only to an account (gateway)\n         // and if the pre-image size is greater than or equal to 512 bytes\n         //////\n         custom_authority_create_operation authorize_htlc_create;\n         authorize_htlc_create.account = alice.get_id();\n         authorize_htlc_create.auth.add_authority(bob.get_id(), 1);\n         authorize_htlc_create.auth.weight_threshold = 1;\n         authorize_htlc_create.enabled = true;\n         authorize_htlc_create.valid_to = db.head_block_time() + 1000;\n         authorize_htlc_create.operation_type = operation::tag<htlc_create_operation>::value;\n\n         auto to_index = member_index<htlc_create_operation>(\"to\");\n         authorize_htlc_create.restrictions.emplace_back(to_index, FUNC(eq), gateway.get_id());\n\n         auto preimage_size_index = member_index<htlc_create_operation>(\"preimage_size\");\n         authorize_htlc_create.restrictions.emplace_back(restriction(preimage_size_index, FUNC(ge), pre_image_size_512));\n         //[\n         //  {\n         //    \"member_index\": 2,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      7,\n         //      \"1.2.18\"\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 5,\n         //    \"restriction_type\": 5,\n         //    \"argument\": [\n         //      2,\n         //      512\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n         trx.clear();\n         trx.operations = {authorize_htlc_create};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the similar transactions\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to put a contract on the blockchain using Alice's funds\n         // with a preimage size of 256.\n         // This should fail because Bob is not authorized to create HTLC on behalf of Alice\n         // if the preimage size is below the minimum value restriction.\n         //////\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 3;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_256 );\n            create_operation.preimage_size = pre_image_size_256;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n            // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n         //////\n         // Bob attempts to put a contract on the blockchain using Alice's funds\n         // with a preimage size of 512.\n         // This should succeed because Bob is authorized to create HTLC on behalf of Alice\n         // and the preimage size equals the minimum value restriction.\n         //////\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 3;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_512 );\n            create_operation.preimage_size = pre_image_size_512;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, bob_private_key);\n            PUSH_TX( db, trx );\n\n         }\n\n\n         //////\n         // Bob attempts to put a contract on the blockchain using Alice's funds\n         // with a preimage size of 600.\n         // This should succeed because Bob is authorized to create HTLC on behalf of Alice\n         // and the preimage size is greater than the minimum value restriction.\n         //////\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 3;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_600 );\n            create_operation.preimage_size = pre_image_size_600;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, bob_private_key);\n            PUSH_TX( db, trx );\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of vector field size comparison\n    * Test of CAA for htlc_redeem_operation\n    *\n    * Scenario: Test of authorization of one account (gateway) authorizing another account (bob)\n    * to redeem an HTLC operation\n    */\n   BOOST_AUTO_TEST_CASE(authorized_htlc_redeem) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         time_point_sec LATER_HF_TIME\n                 = (HARDFORK_BSIP_40_TIME > HARDFORK_CORE_1468_TIME) ? HARDFORK_BSIP_40_TIME : HARDFORK_CORE_1468_TIME;\n         generate_blocks(LATER_HF_TIME);\n         generate_blocks(5);\n         set_expiration(db, trx);\n\n         // Initialize HTLC blockchain parameters\n         trx.clear();\n         set_htlc_committee_parameters();\n         generate_blocks(5);\n\n         // Initialize CAA blockchain parameters\n         trx.clear();\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n\n         // Update the expiration of the re-usable trx relative to the head block time\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(gateway));\n         int64_t init_balance(1000 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n         int64_t init_gateway_balance(50 * GRAPHENE_BLOCKCHAIN_PRECISION);\n         transfer( committee_account, gateway_id, graphene::chain::asset(init_gateway_balance) );\n\n\n         //////\n         // Initialize: Pre-image sizes and pre-images to reduce the test variability\n         //////\n         uint16_t pre_image_size_256 = 256;\n         std::vector<char> pre_image_256(pre_image_size_256);\n         generate_random_preimage(pre_image_size_256, pre_image_256);\n\n\n         //////\n         // Gateway puts a contract on the blockchain\n         // This should succeed because the gateway is authorized to create HTLC for its own account\n         //////\n         share_type htlc_amount = 25 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( htlc_amount );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 86400;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_256 );\n            create_operation.preimage_size = pre_image_size_256;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, alice_private_key);\n            PUSH_TX( db, trx );\n         }\n\n\n         //////\n         // Advance the blockchain to get the finalized HTLC ID\n         //////\n         generate_blocks(1);\n         graphene::chain::htlc_id_type alice_htlc_id =\n                 db.get_index_type<htlc_index>().indices().get<by_from_id>().find(alice.get_id())->get_id();\n\n\n         //////\n         // Bob attempts to redeem the HTLC on behalf of the gateway\n         // This should fail because Bob is not authorized to redeem on behalf of the gateway\n         //////\n         graphene::chain::htlc_redeem_operation redeem_operation;\n         {\n            redeem_operation.redeemer = gateway_id;\n            redeem_operation.htlc_id = alice_htlc_id;\n            redeem_operation.preimage = pre_image_256;\n            redeem_operation.fee = db.current_fee_schedule().calculate_fee( redeem_operation );\n            trx.clear();\n            trx.operations.push_back(redeem_operation);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Gateway authorizes Bob to redeem an HTLC\n         // only if the preimage length equals 200 bytes\n         // This length is incompatible with the HTLC pre-image that is already on the blockchain\n         //////\n         custom_authority_create_operation authorize_htlc_redeem;\n         authorize_htlc_redeem.account = gateway.get_id();\n         authorize_htlc_redeem.auth.add_authority(bob.get_id(), 1);\n         authorize_htlc_redeem.auth.weight_threshold = 1;\n         authorize_htlc_redeem.enabled = true;\n         authorize_htlc_redeem.valid_to = db.head_block_time() + 1000;\n         authorize_htlc_redeem.operation_type = operation::tag<htlc_redeem_operation>::value;\n\n         auto preimage_index = member_index<htlc_redeem_operation>(\"preimage\");\n         authorize_htlc_redeem.restrictions.emplace_back(preimage_index, FUNC(eq), int64_t(200));\n         //[\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 0,\n         //    \"argument\": [\n         //      2,\n         //      200\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n\n         trx.clear();\n         trx.operations = {authorize_htlc_redeem};\n         sign(trx, gateway_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to get the finalized CAA ID\n         //////\n         generate_blocks(1);\n         auto caa = db.get_index_type<custom_authority_index>().indices().get<by_account_custom>().find(gateway.get_id());\n         custom_authority_id_type caa_id = caa->get_id();\n\n\n         //////\n         // Bob attempts to redeem the HTLC\n         // This should fail because the authorization's restriction prohibits the redemption of this HTLC\n         //////\n         {\n            trx.clear();\n            trx.operations.push_back(redeem_operation);\n            sign(trx, bob_private_key);\n\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the similar transactions\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Gateway updates the authorization for to redeem an HTLC\n         // only if the preimage length equals 256 bytes\n         // This length is compatible with the HTLC pre-image that is already on the blockchain\n         //////\n         custom_authority_update_operation update_authorization;\n         update_authorization.account = gateway.get_id();\n         update_authorization.authority_to_update = caa_id;\n         uint16_t existing_restriction_index = 0; // The 0-based index of the first and only existing restriction\n         update_authorization.restrictions_to_remove = {existing_restriction_index};\n         update_authorization.restrictions_to_add =\n                 {restriction(preimage_index, FUNC(eq), int64_t(pre_image_size_256))};\n         trx.clear();\n         trx.operations = {update_authorization};\n         sign(trx, gateway_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Bob attempts to redeem the HTLC\n         // This should succeed because the redemption satisfies the authorization\n         //////\n         {\n            trx.clear();\n            trx.operations.push_back(redeem_operation);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of greater than (gt) and less than or equal to (le) restriction on a field\n    * Test of CAA for htlc_extend_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to extend an HTLC operation as long as the extension is within a specified duration\n    */\n   BOOST_AUTO_TEST_CASE(authorized_htlc_extension) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         time_point_sec LATER_HF_TIME\n                 = (HARDFORK_BSIP_40_TIME > HARDFORK_CORE_1468_TIME) ? HARDFORK_BSIP_40_TIME : HARDFORK_CORE_1468_TIME;\n         generate_blocks(LATER_HF_TIME);\n         generate_blocks(5);\n         set_expiration(db, trx);\n\n         // Initialize HTLC blockchain parameters\n         trx.clear();\n         set_htlc_committee_parameters();\n         generate_blocks(5);\n\n         // Initialize CAA blockchain parameters\n         trx.clear();\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(gateway));\n         int64_t init_balance(1000 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n         int64_t init_gateway_balance(50 * GRAPHENE_BLOCKCHAIN_PRECISION);\n         transfer( committee_account, gateway_id, graphene::chain::asset(init_gateway_balance) );\n\n\n         //////\n         // Initialize: Pre-image sizes and pre-images to reduce the test variability\n         //////\n         uint16_t pre_image_size_256 = 256;\n         std::vector<char> pre_image_256(pre_image_size_256);\n         generate_random_preimage(pre_image_size_256, pre_image_256);\n\n\n         //////\n         // Gateway puts a contract on the blockchain\n         // This should succeed because the gateway is authorized to create HTLC for its own account\n         //////\n         share_type htlc_amount = 25 * GRAPHENE_BLOCKCHAIN_PRECISION;\n         {\n            graphene::chain::htlc_create_operation create_operation;\n            create_operation.amount = graphene::chain::asset( htlc_amount );\n            create_operation.from = alice_id;\n            create_operation.to = gateway_id;\n            create_operation.claim_period_seconds = 86400;\n            create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image_256 );\n            create_operation.preimage_size = pre_image_size_256;\n            create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n            trx.clear();\n            trx.operations.push_back(create_operation);\n            sign(trx, alice_private_key);\n            PUSH_TX( db, trx );\n         }\n\n\n         //////\n         // Advance the blockchain to get the finalized HTLC ID\n         //////\n         generate_blocks(1);\n         graphene::chain::htlc_id_type alice_htlc_id =\n                 db.get_index_type<htlc_index>().indices().get<by_from_id>().find(alice.get_id())->get_id();\n\n\n         //////\n         // Bob attempts to extend the HTLC\n         // This should fail because Bob is not authorized to extend an HTLC on behalf of Alice\n         //////\n         graphene::chain::htlc_extend_operation extend_operation;\n         {\n            extend_operation.update_issuer = alice_id;\n            extend_operation.htlc_id = alice_htlc_id;\n            extend_operation.seconds_to_add = int64_t(24 * 3600);\n            extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation );\n            trx.clear();\n            trx.operations.push_back(extend_operation);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Alice authorizes Bob to extend an HTLC\n         // by greater than 1 hour and less than or equal to 24 hours\n         //////\n         custom_authority_create_operation authorize_htlc_extension;\n         authorize_htlc_extension.account = alice.get_id();\n         authorize_htlc_extension.auth.add_authority(bob.get_id(), 1);\n         authorize_htlc_extension.auth.weight_threshold = 1;\n         authorize_htlc_extension.enabled = true;\n         authorize_htlc_extension.valid_to = db.head_block_time() + 1000;\n         authorize_htlc_extension.operation_type = operation::tag<htlc_extend_operation>::value;\n\n         // Authorization to extend is restricted to greater than 1 hour and less than or equal to 24 hours\n         vector<restriction> restrictions;\n         auto extension_duration_index = member_index<htlc_extend_operation>(\"seconds_to_add\");\n         // Duration extension greater than one hour\n         restriction restriction_gt_duration = restriction(extension_duration_index, FUNC(gt), int64_t(1 * 3600));\n         restrictions.emplace_back(restriction_gt_duration);\n         // Duration extension less than or equal to 24 hours\n         restriction restriction_le_duration = restriction(extension_duration_index, FUNC(le), int64_t(24 * 3600));\n         restrictions.emplace_back(restriction_le_duration);\n         authorize_htlc_extension.restrictions = restrictions;\n         //[\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 4,\n         //    \"argument\": [\n         //      2,\n         //      3600\n         //    ],\n         //    \"extensions\": []\n         //  },\n         //  {\n         //    \"member_index\": 3,\n         //    \"restriction_type\": 3,\n         //    \"argument\": [\n         //      2,\n         //      86400\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n         trx.clear();\n         trx.operations = {authorize_htlc_extension};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the similar transactions\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to extend the HTLC\n         // This should succeed because Bob is conditionally authorized to extend\n         //////\n         {\n            trx.clear();\n            trx.operations.push_back(extend_operation);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n         }\n\n\n         //////\n         // Bob attempts to extend the HTLC by exactly 10 hours\n         // This should succeed because Bob is authorized to extend the HTLC\n         // if greater than 1 hour and less than or equal to 24 hours\n         //////\n         {\n            extend_operation = htlc_extend_operation();\n            extend_operation.update_issuer = alice_id;\n            extend_operation.htlc_id = alice_htlc_id;\n            extend_operation.seconds_to_add = int64_t(10 * 3600);\n            extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation );\n            trx.clear();\n            trx.operations.push_back(extend_operation);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n         }\n\n\n         //////\n         // Bob attempts to extend the HTLC by exactly 1 hour\n         // This should fail because Bob is authorized to extend the HTLC\n         // if greater than 1 hour and less than or equal to 24 hours\n         //////\n         {\n            extend_operation = htlc_extend_operation();\n            extend_operation.update_issuer = alice_id;\n            extend_operation.htlc_id = alice_htlc_id;\n            extend_operation.seconds_to_add = int64_t(1 * 3600);\n            extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation );\n            trx.clear();\n            trx.operations.push_back(extend_operation);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,0],[2,\"predicate_was_false\"]\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Bob attempts to extend the HTLC by 24 hours plus 1 second\n         // This should fail because Bob is authorized to extend the HTLC\n         // if greater than 1 hour and less than or equal to 24 hours\n         //////\n         {\n            extend_operation = htlc_extend_operation();\n            extend_operation.update_issuer = alice_id;\n            extend_operation.htlc_id = alice_htlc_id;\n            extend_operation.seconds_to_add = int64_t( (24 * 3600) + 1);\n            extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation );\n            trx.clear();\n            trx.operations.push_back(extend_operation);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,1],[2,\"predicate_was_false\"]\n            // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of variant assert (variant_assert) restriction on a field\n    * Test of CAA for vesting_balance_create_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to create a coins-day vesting balance with a vesting duration of 800,000 seconds\n    */\n   BOOST_AUTO_TEST_CASE(authorized_vesting_balance_create) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(charlie));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         //////\n         // Bob attempts to create a coins-day vesting balance for Alice\n         // This attempt should fail because Alice has not authorized Bob to create a vesting balance\n         //////\n         vesting_balance_create_operation original_vb_op;\n         time_point_sec policy_start_time = db.head_block_time() + 86400;\n         {\n            vesting_balance_create_operation vb_op;\n            vb_op.creator = alice_id;\n            vb_op.owner = charlie_id;\n            vb_op.amount = graphene::chain::asset(60000);\n            vb_op.policy = cdd_vesting_policy_initializer(800000, policy_start_time);\n            vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n            trx.clear();\n            trx.operations.push_back(vb_op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n            original_vb_op = vb_op;\n         }\n\n\n         //////\n         // Alice authorizes Bob to create a coins-day vesting balance from her funds\n         // only if the vesting duration equals 800,000 seconds\n         //////\n         custom_authority_create_operation authorize_create_vesting;\n         authorize_create_vesting.account = alice.get_id();\n         authorize_create_vesting.auth.add_authority(bob.get_id(), 1);\n         authorize_create_vesting.auth.weight_threshold = 1;\n         authorize_create_vesting.enabled = true;\n         authorize_create_vesting.valid_to = db.head_block_time() + 1000;\n         authorize_create_vesting.operation_type = operation::tag<vesting_balance_create_operation>::value;\n\n         // Restrict authorization to a coin-days vesting policy with a vesting duration of 800000 seconds\n         auto policy_index = member_index<vesting_balance_create_operation>(\"policy\");\n         int64_t policy_tag = vesting_policy_initializer::tag<cdd_vesting_policy_initializer>::value;\n         auto vesting_seconds_index = member_index<cdd_vesting_policy_initializer>(\"vesting_seconds\");\n         vector<restriction> policy_restrictions = {restriction(vesting_seconds_index, FUNC(eq), int64_t(800000))};\n         pair<int64_t, vector<restriction>> policy_argument(policy_tag, policy_restrictions);\n         authorize_create_vesting.restrictions = {restriction(policy_index, FUNC(variant_assert), policy_argument)};\n         //[\n         //  {\n         //    \"member_index\": 4,\n         //    \"restriction_type\": 12,\n         //    \"argument\": [\n         //      41,\n         //      [\n         //        1,\n         //        [\n         //          {\n         //            \"member_index\": 1,\n         //            \"restriction_type\": 0,\n         //            \"argument\": [\n         //              2,\n         //              800000\n         //            ],\n         //            \"extensions\": []\n         //          }\n         //        ]\n         //      ]\n         //    ],\n         //    \"extensions\": []\n         //  }\n         //]\n         trx.clear();\n         trx.operations = {authorize_create_vesting};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to generate distinctive hash IDs for the similar transactions\n         //////\n         generate_blocks(1);\n\n\n         //////\n         // Bob attempts to create a coins-day vesting balance for Alice with a vesting duration of 86400 seconds\n         // This attempt should fail because Alice has not authorized this duration\n         //////\n         {\n            vesting_balance_create_operation vb_op;\n            vb_op.creator = alice_id;\n            vb_op.owner = charlie_id;\n            vb_op.amount = graphene::chain::asset(60000);\n            vb_op.policy = cdd_vesting_policy_initializer(86400, policy_start_time);\n            vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n            trx.clear();\n            trx.operations.push_back(vb_op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[0,0],[2,\\\"predicate_was_false\\\"]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Bob attempts to create a linear vesting balance for Alice\n         // This attempt should fail because Alice has not authorized this type of vesting balance creation\n         //////\n         {\n            vesting_balance_create_operation vb_op;\n            vb_op.creator = alice_id;\n            vb_op.owner = charlie_id;\n            vb_op.amount = graphene::chain::asset(60000);\n            linear_vesting_policy_initializer policy;\n            policy.begin_timestamp = policy_start_time;\n            policy.vesting_cliff_seconds = 800000;\n            policy.vesting_duration_seconds = 40000;\n            vb_op.policy = policy;\n            vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n            trx.clear();\n            trx.operations.push_back(vb_op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,0],[2,\"incorrect_variant_type\"]\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n            // [2,\"incorrect_variant_type\"]: 0 is the rejection_indicator for rejection_reason; \"incorrect_variant_type\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"incorrect_variant_type\\\"]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Bob attempts to create a coins-day vesting balance for Alice with a vesting duration of 800000 seconds\n         // This attempt should succeed because Alice has authorized authorized this type of vesting balance creation\n         // with this duration\n         //////\n         {\n            trx.clear();\n            trx.operations.push_back(original_vb_op);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of time restrictions on CAA\n    * Test of CAA for vesting_balance_withdraw_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to withdraw vesting for a limited duration\n    */\n   BOOST_AUTO_TEST_CASE(authorized_time_restrictions_1) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob)(charlie));\n         fund(charlie, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         //////\n         // Charlie creates an instant vesting balance for Alice\n         //////\n         vesting_balance_create_operation original_vb_op;\n         time_point_sec policy_start_time = db.head_block_time() + 86400;\n         vesting_balance_create_operation vb_op;\n         vb_op.creator = charlie_id;\n         vb_op.owner = alice_id;\n         vb_op.amount = graphene::chain::asset(60000);\n         vb_op.policy = instant_vesting_policy_initializer();\n         vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n         trx.clear();\n         trx.operations.push_back(vb_op);\n         sign(trx, charlie_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to before withdrawal of vesting balance can start\n         //////\n         generate_blocks(1);\n         set_expiration(db, trx);\n         vesting_balance_id_type vesting_balance_id {\n                 db.get_index_type<vesting_balance_index>().indices().get<by_account>().find(alice.get_id())->id };\n\n\n         //////\n         // Bob attempts to withdraw some of the vesting balance on behalf of Alice\n         // This attempt should fail because Alice has not authorized Bob\n         //////\n         {\n            asset partial_amount = asset(10000);\n\n            vesting_balance_withdraw_operation vb_op;\n            vb_op.vesting_balance = vesting_balance_id;\n            vb_op.owner = alice_id;\n            vb_op.amount = partial_amount;\n            vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n            trx.clear();\n            trx.operations.push_back(vb_op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n\n         //////\n         // Alice authorizes Bob to withdraw her vesting balance\n         //////\n         custom_authority_create_operation authorize_create_vesting;\n         authorize_create_vesting.account = alice.get_id();\n         authorize_create_vesting.auth.add_authority(bob.get_id(), 1);\n         authorize_create_vesting.auth.weight_threshold = 1;\n         authorize_create_vesting.enabled = true;\n         // Authorization is valid only for 3/5 of the maximum duration of a custom authority\n         time_point_sec authorization_end_time = policy_start_time + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 3 / 5);\n         time_point_sec authorization_before_end_time = policy_start_time + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 1 / 5);\n         authorize_create_vesting.valid_to = authorization_end_time;\n         authorize_create_vesting.operation_type = operation::tag<vesting_balance_withdraw_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_create_vesting};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to before the authorization expires\n         //////\n         generate_blocks(authorization_before_end_time);\n         set_expiration(db, trx);\n\n\n         //////\n         // Bob attempts to withdraw the available vesting balance for Alice\n         // This attempt should succeed because the authorization is active\n         //////\n         {\n            asset partial_amount = asset(10000);\n\n            vesting_balance_withdraw_operation vb_op;\n            vb_op.vesting_balance = vesting_balance_id;\n            vb_op.owner = alice_id;\n            vb_op.amount = partial_amount;\n            vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n            trx.clear();\n            trx.operations.push_back(vb_op);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n         }\n\n\n         //////\n         // Advance the blockchain to after the authorization expires\n         //////\n         time_point_sec after_authorization_end_time = authorization_end_time + 86400;\n         generate_blocks(after_authorization_end_time);\n         set_expiration(db, trx);\n\n\n         //////\n         // Bob attempts to withdraw the available vesting balance for Alice\n         // This attempt should fail because the authorization has expired\n         //////\n         {\n            asset partial_amount = asset(10000);\n\n            vesting_balance_withdraw_operation vb_op;\n            vb_op.vesting_balance = vesting_balance_id;\n            vb_op.owner = alice_id;\n            vb_op.amount = partial_amount;\n            vb_op.fee = db.current_fee_schedule().calculate_fee(vb_op);\n            trx.clear();\n            trx.operations.push_back(vb_op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of time restrictions on CAA\n    * Test of CAA for call_order_update_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to update a call order only during a specfied time interval\n    */\n   BOOST_AUTO_TEST_CASE(authorized_time_restrictions_2) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((feedproducer)(alice)(bob));\n         int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n\n         //////\n         // Initialize: Define a market-issued asset called USDBIT\n         //////\n         // Define core asset\n         const auto &core = asset_id_type()(db);\n         asset_id_type core_id = core.get_id();\n\n         // Create a smart asset\n         create_bitasset(\"USDBIT\", feedproducer_id);\n         generate_blocks(1);\n         const asset_object &bitusd\n                 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"USDBIT\");\n         asset_id_type usd_id = bitusd.get_id();\n\n         // Configure the smart asset\n         update_feed_producers(bitusd, {feedproducer.get_id()});\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(5);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         //////\n         // Fund alice with core asset\n         //////\n         fund(alice, asset(init_balance));\n         // alice will borrow 1000 bitUSD\n         borrow(alice, bitusd.amount(1000), asset(15000));\n         int64_t alice_balance_usd_before_offer = get_balance(alice_id, usd_id);\n         BOOST_CHECK_EQUAL( 1000,  alice_balance_usd_before_offer);\n         int64_t alice_balance_core_before_offer = get_balance(alice_id, core_id);\n         BOOST_CHECK_EQUAL( init_balance - 15000, alice_balance_core_before_offer );\n\n\n         //////\n         // Alice updates the collateral for the Alice debt position\n         //////\n         {\n            call_order_update_operation op;\n            op.funding_account = alice_id;\n            op.delta_collateral = asset(1000);\n            op.delta_debt = asset(0, usd_id);\n            trx.clear();\n            trx.operations.push_back(op);\n            sign(trx, alice_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This attempt should fail because Bob is not authorized by Alice\n         //////\n         {\n            call_order_update_operation op;\n            op.funding_account = alice_id;\n            op.delta_collateral = asset(2000);\n            op.delta_debt = asset(0, usd_id);\n            trx.clear();\n            trx.operations.push_back(op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // Alice authorizes Bob to update her call order\n         //////\n         custom_authority_create_operation authorize_call_order_update;\n         authorize_call_order_update.account = alice.get_id();\n         authorize_call_order_update.auth.add_authority(bob.get_id(), 1);\n         authorize_call_order_update.auth.weight_threshold = 1;\n         authorize_call_order_update.enabled = true;\n         // Authorization is valid only for 2/5 of the maximum duration of a custom authority\n         time_point_sec before_authorization_start_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 1 / 5);\n         time_point_sec authorization_start_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 2 / 5);\n         time_point_sec authorization_middle_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 3 / 5);\n         time_point_sec authorization_end_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 4 / 5);\n         time_point_sec after_authorization_end_time = authorization_end_time + 86400;\n         authorize_call_order_update.valid_from = authorization_start_time;\n         authorize_call_order_update.valid_to = authorization_end_time;\n         authorize_call_order_update.operation_type = operation::tag<call_order_update_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_call_order_update};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to before the authorization starts\n         //////\n         generate_blocks(before_authorization_start_time);\n         set_expiration(db, trx);\n         publish_feed(bitusd, feedproducer, current_feed); // Update the price feed\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This attempt should fail because authorization is not yet active\n         //////\n         {\n            call_order_update_operation op;\n            op.funding_account = alice_id;\n            op.delta_collateral = asset(3000);\n            op.delta_debt = asset(0, usd_id);\n            trx.clear();\n            trx.operations.push_back(op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because the CAA is not yet active\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // Advance the blockchain to the start of the authorization period\n         //////\n         generate_blocks(authorization_start_time);\n         set_expiration(db, trx);\n         publish_feed(bitusd, feedproducer, current_feed); // Update the price feed\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This attempt should succeed because the Alice authorization is active\n         //////\n         {\n            call_order_update_operation op;\n            op.funding_account = alice_id;\n            op.delta_collateral = asset(4000);\n            op.delta_debt = asset(0, usd_id);\n            trx.clear();\n            trx.operations.push_back(op);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Advance the blockchain to the end of the authorization period\n         //////\n         generate_blocks(authorization_middle_time);\n         set_expiration(db, trx);\n         publish_feed(bitusd, feedproducer, current_feed); // Update the price feed\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This attempt should succeed because the Alice authorization is active\n         //////\n         {\n            call_order_update_operation op;\n            op.funding_account = alice_id;\n            op.delta_collateral = asset(5000);\n            op.delta_debt = asset(0, usd_id);\n            trx.clear();\n            trx.operations.push_back(op);\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Advance the blockchain to after the authorization expires\n         //////\n         generate_blocks(after_authorization_end_time);\n         set_expiration(db, trx);\n         publish_feed(bitusd, feedproducer, current_feed); // Update the price feed\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This attempt should fail because the authorization has expired\n         //////\n         {\n            call_order_update_operation op;\n            op.funding_account = alice_id;\n            op.delta_collateral = asset(6000);\n            op.delta_debt = asset(0, usd_id);\n            trx.clear();\n            trx.operations.push_back(op);\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of time restrictions on CAA\n    * Test of CAA for asset_reserve_operation\n    * Test of CAA in a proposed operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to reserve (burn) an asset only during a specfied timespan\n    */\n   BOOST_AUTO_TEST_CASE(authorized_time_restrictions_3) {\n      try {\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((assetissuer)(feedproducer)(alice)(bob)(charlie));\n         int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n         fund(alice, asset(init_balance));\n\n\n         // Lambda for issuing an asset to an account\n         auto issue_amount_to = [&](const account_id_type &issuer, const asset &amount, const account_id_type &to) {\n            asset_issue_operation op;\n            op.issuer = issuer;\n            op.asset_to_issue = amount;\n            op.issue_to_account = to;\n\n            return op;\n         };\n\n         // Lambda for reserving an asset from an account\n         auto reserve_asset = [&](const account_id_type &reserver, const asset &amount) {\n            asset_reserve_operation op;\n            op.payer = reserver;\n            op.amount_to_reserve = amount;\n\n            return op;\n         };\n\n\n         //////\n         // Initialize: Create user-issued assets\n         //////\n         upgrade_to_lifetime_member(assetissuer);\n         create_user_issued_asset(\"SPECIALCOIN\", assetissuer,  DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n         generate_blocks(1);\n         const asset_object &specialcoin\n                 = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(\"SPECIALCOIN\");\n\n\n         //////\n         // Initialize: assetissuer issues SPECIALCOIN to different accounts\n         //////\n         asset_issue_operation issue_special_to_alice_op\n                 = issue_amount_to(assetissuer.get_id(), asset(1000, specialcoin.get_id()), alice.get_id());\n         asset_issue_operation issue_special_to_charlie_op\n                 = issue_amount_to(assetissuer.get_id(), asset(2000, specialcoin.get_id()), charlie.get_id());\n         trx.clear();\n         trx.operations = {issue_special_to_alice_op, issue_special_to_charlie_op};\n         sign(trx, assetissuer_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Alice reserves some SPECIALCOIN from her account\n         //////\n         asset_reserve_operation reserve_op = reserve_asset(alice.get_id(), asset(200, specialcoin.get_id()));\n         trx.clear();\n         trx.operations = {reserve_op};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n         int64_t allowed1_balance_specialcoin_after_override1 = get_balance(alice.get_id(), specialcoin.get_id());\n         BOOST_CHECK_EQUAL(allowed1_balance_specialcoin_after_override1, 800);\n\n\n         //////\n         // Charlie reserves some SPECIALCOIN from his account\n         //////\n         reserve_op = reserve_asset(charlie.get_id(), asset(200, specialcoin.get_id()));\n         trx.clear();\n         trx.operations = {reserve_op};\n         sign(trx, charlie_private_key);\n         PUSH_TX(db, trx);\n         int64_t charlie_balance_specialcoin_after_override1 = get_balance(charlie.get_id(), specialcoin.get_id());\n         BOOST_CHECK_EQUAL(charlie_balance_specialcoin_after_override1, 1800);\n\n\n         //////\n         // Alice authorizes Bob to reserve her SPECIALCOIN\n         // This attempt should fail because the blockchain has not yet been initialized for CAA\n         //////\n         custom_authority_create_operation authorize_reserve;\n         authorize_reserve.account = alice.get_id();\n         authorize_reserve.auth.add_authority(bob.get_id(), 1);\n         authorize_reserve.auth.weight_threshold = 1;\n         authorize_reserve.enabled = true;\n         // Authorization is valid only for 2/5 of the maximum duration of a custom authority\n         time_point_sec before_authorization_start_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 1 / 5);\n         time_point_sec authorization_start_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 2 / 5);\n         time_point_sec authorization_middle_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 3 / 5);\n         time_point_sec authorization_end_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 4 / 5);\n         time_point_sec after_authorization_end_time = authorization_end_time + 86400;\n         authorize_reserve.valid_from = authorization_start_time;\n         authorize_reserve.valid_to = authorization_end_time;\n         authorize_reserve.operation_type = operation::tag<asset_reserve_operation>::value;\n         trx.clear();\n         trx.operations = {authorize_reserve};\n         sign(trx, alice_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::assert_exception);\n\n\n         //////\n         // Alice creates a PROPOSAL to authorize Bob to reserve her SPECIALCOIN\n         // This attempt should fail because the blockchain has not yet been initialized for CAA\n         //////\n         proposal_create_operation proposal;\n         proposal.fee_paying_account = alice.get_id();\n         proposal.proposed_ops = {op_wrapper(authorize_reserve)};\n         proposal.expiration_time = db.head_block_time() + 86400;\n         trx.clear();\n         trx.operations = {authorize_reserve};\n         sign(trx, alice_private_key);\n         BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::assert_exception);\n\n\n         //////\n         // Initialize the blockchain for CAA\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Alice creates a PROPOSAL to authorize Bob to reserve her SPECIALCOIN\n         // Authorization is valid only for 2/5 of the maximum duration of a custom authority\n         // This attempt should succeed because the blockchain is initialized for CAA\n         //////\n         before_authorization_start_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 1 / 5);\n         authorization_start_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 2 / 5);\n         authorization_middle_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 3 / 5);\n         authorization_end_time =\n                 db.head_block_time() + (GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS * 4 / 5);\n         after_authorization_end_time = authorization_end_time + 86400;\n         authorize_reserve.valid_from = authorization_start_time;\n         authorize_reserve.valid_to = authorization_end_time;\n\n         proposal.fee_paying_account = alice.get_id();\n         proposal.proposed_ops = {op_wrapper(authorize_reserve)};\n         proposal.expiration_time = db.head_block_time() + 86400;\n         trx.clear();\n         trx.operations = {proposal};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to get the finalized proposal ID\n         //////\n         generate_blocks(1);\n         const proposal_object& prop = *db.get_index_type<proposal_index>().indices().begin();\n         proposal_id_type proposal_id = prop.get_id();\n\n         // Alice approves the proposal\n         proposal_update_operation approve_proposal;\n         approve_proposal.proposal = proposal_id;\n         approve_proposal.fee_paying_account = alice.get_id();\n         approve_proposal.active_approvals_to_add = {alice.get_id()};\n         trx.clear();\n         trx.operations = {approve_proposal};\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx);\n\n\n         //////\n         // Advance the blockchain to before the authorization starts\n         //////\n         generate_blocks(before_authorization_start_time);\n         set_expiration(db, trx);\n\n\n         //////\n         // Bob attempts to reserve some of Alice's SPECIALCOIN\n         // This attempt should fail because Bob the Alice authorization is not yet active\n         //////\n         {\n            asset_reserve_operation reserve_op = reserve_asset(alice.get_id(), asset(200, specialcoin.get_id()));\n            trx.clear();\n            trx.operations = {reserve_op};\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because the CAA is not yet active\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // Advance the blockchain to the start of the authorization period\n         //////\n         generate_blocks(authorization_start_time);\n         set_expiration(db, trx);\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This should succeed because the authorization is active\n         //////\n         {\n            asset_reserve_operation reserve_op = reserve_asset(alice.get_id(), asset(200, specialcoin.get_id()));\n            trx.clear();\n            trx.operations = {reserve_op};\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Advance the blockchain to the end of the authorization period\n         //////\n         generate_blocks(authorization_middle_time);\n         set_expiration(db, trx);\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This should succeed because the authorization is active\n         //////\n         {\n            asset_reserve_operation reserve_op = reserve_asset(alice.get_id(), asset(200, specialcoin.get_id()));\n            trx.clear();\n            trx.operations = {reserve_op};\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Advance the blockchain to after the authorization expires\n         //////\n         generate_blocks(after_authorization_end_time);\n         set_expiration(db, trx);\n\n\n         //////\n         // Bob attempts to update the collateral for Alice's debt position\n         // This should fail because Bob the authorization has expired\n         //////\n         {\n            asset_reserve_operation reserve_op = reserve_asset(alice.get_id(), asset(200, specialcoin.get_id()));\n            trx.clear();\n            trx.operations = {reserve_op};\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of string field restriction\n    * Test of CAA for asset_create_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing another account (bob)\n    * to create an asset with a description that starts with the literal string \"ACOIN.\"\n    */\n   BOOST_AUTO_TEST_CASE(authorized_asset_creation) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice)(bob));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n         upgrade_to_lifetime_member(alice);\n         fund(bob, asset(200000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n\n         // Lambda for issuing an asset to an account\n         auto create_uia = [&](const string& name,\n                                    const account_object& issuer,\n                                    uint16_t flags,\n                                    const additional_asset_options_t& options = additional_asset_options_t(),\n                                    const price& core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)),\n                                    uint8_t precision = 2 /* traditional precision for tests */,\n                                    uint16_t market_fee_percent = 0) {\n\n            asset_create_operation op;\n\n            op.issuer = issuer.id;\n            op.fee = asset();\n            op.symbol = name;\n            op.common_options.max_supply = 0;\n            op.precision = precision;\n            op.common_options.core_exchange_rate = core_exchange_rate;\n            op.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n            op.common_options.flags = flags;\n            op.common_options.issuer_permissions = flags;\n            op.common_options.market_fee_percent = market_fee_percent;\n            op.common_options.extensions = std::move(options);\n\n            return op;\n         };\n\n\n         //////\n         // Alice creates a UIA\n         //////\n         {\n            asset_create_operation create_uia_op = create_uia(\"ACOIN\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, alice_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Bob attempts to create a UIA\n         // This should fail because Bob is not authorized by Alice to create any coin with Alice as the issuer\n         //////\n         {\n            asset_create_operation create_uia_op = create_uia(\"ACOIN.BOB\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // Alice authorizes Bob to create sub-token UIAs below ACOIN\n         //////\n         {\n            custom_authority_create_operation authorize_uia_creation;\n            authorize_uia_creation.account = alice.get_id();\n            authorize_uia_creation.auth.add_authority(bob.get_id(), 1);\n            authorize_uia_creation.auth.weight_threshold = 1;\n            authorize_uia_creation.enabled = true;\n            authorize_uia_creation.valid_to = db.head_block_time() + 86400;\n            authorize_uia_creation.operation_type = operation::tag<asset_create_operation>::value;\n\n            auto symbol_index = member_index<asset_create_operation>(\"symbol\");\n            authorize_uia_creation.restrictions.emplace_back(symbol_index, FUNC(gt), string(\"ACOIN.\"));\n            authorize_uia_creation.restrictions.emplace_back(symbol_index, FUNC(le), string(\"ACOIN.ZZZZZZZZZZZZZZZZ\"));\n            //[\n            //  {\n            //    \"member_index\": 2,\n            //    \"restriction_type\": 4,\n            //    \"argument\": [\n            //      3,\n            //      \"ACOIN.\"\n            //    ],\n            //    \"extensions\": []\n            //  },\n            //  {\n            //    \"member_index\": 2,\n            //    \"restriction_type\": 3,\n            //    \"argument\": [\n            //      3,\n            //      \"ACOIN.ZZZZZZZZZZZZZZZZ\"\n            //    ],\n            //    \"extensions\": []\n            //  }\n            //]\n\n            trx.clear();\n            trx.operations = {authorize_uia_creation};\n            sign(trx, alice_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Bob attempts to create a UIA with a symbol name below the authorized textual range\n         // This should fail because it violates Restriction 1\n         //////\n         {\n            asset_create_operation create_uia_op = create_uia(\"ABCOIN\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // Bob attempts to create a UIA with a symobl name above the authorized textual range\n         // This should fail because it violates Restriction 2\n         //////\n         {\n            asset_create_operation create_uia_op = create_uia(\"BOB\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // [0,1]: 0 is the rejection_indicator for an index to a sub-restriction; 1 is the index value for Restriction 2\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // Bob attempts to create a sub-token of ACOIN\n         // This should succeed because this satisfies the sub-token restriction by Alice\n         //////\n         {\n            asset_create_operation create_uia_op = create_uia(\"ACOIN.BOB\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n            create_uia_op = create_uia(\"ACOIN.CHARLIE\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n\n            create_uia_op = create_uia(\"ACOIN.DIANA\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Bob creates his own UIA that is similar to ACOIN\n         //////\n         {\n            upgrade_to_lifetime_member(bob);\n\n            asset_create_operation create_uia_op = create_uia(\"AACOIN\", bob, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n\n            PUSH_TX(db, trx);\n\n\n            create_uia_op = create_uia(\"AACOIN.TEST\", bob, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // Bob attempts to create a sub-token of AACOIN but with Alice as the issuer\n         // This should fail because it violates Restriction 1\n         //////\n         {\n            asset_create_operation create_uia_op = create_uia(\"AACOIN.BOB\", alice, white_list);\n            trx.clear();\n            trx.operations = {create_uia_op};\n            sign(trx, bob_private_key);\n\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for Restriction 1\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n   /**\n    * Test of CAA for account_update_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing a key\n    * to ONLY update the voting slate of an account\n    */\n   BOOST_AUTO_TEST_CASE(authorized_voting_key) {\n      try {\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         ACTORS((alice));\n         fund(alice, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n         upgrade_to_lifetime_member(alice);\n\n         // Arbitrarily identify one of the active witnesses\n         flat_set<witness_id_type> witnesses = db.get_global_properties().active_witnesses;\n         auto itr_witnesses = witnesses.begin();\n         witness_id_type witness0_id = itr_witnesses[0];\n         const auto& idx = db.get_index_type<witness_index>().indices().get<by_id>();\n         witness_object witness0_obj = *idx.find(object_id_type(witness0_id));\n\n\n         //////\n         // Define a key that can be authorized\n         // This can be a new key or an existing key. The existing key may even be the active key of an account.\n         //////\n         fc::ecc::private_key some_private_key = generate_private_key(\"some key\");\n         public_key_type some_public_key = public_key_type(some_private_key.get_public_key());\n\n\n         //////\n         // The key attempts to update the voting slate of Alice\n         // This should fail because the key is not authorized by Alice to update any part of her account\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n            account_options alice_options = alice.options;\n            auto insert_result = alice_options.votes.insert(witness0_obj.vote_id);\n            if (!insert_result.second)\n               FC_THROW(\"Account ${account} was already voting for witness ${witness}\",\n                        (\"account\", alice)(\"witness\", \"init0\"));\n            uop.new_options = alice_options;\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for Bob's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] { PUSH_TX(db, trx); });\n         }\n\n\n         //////\n         // Alice authorizes the key to update her voting slate\n         // by authorizing account updates EXCEPT for\n         // updating the owner key\n         // updating the active key\n         // updating the memo key\n         // updating the special owner authority\n         // updating the special active authority\n         //////\n         {\n            custom_authority_create_operation authorize_account_update;\n            authorize_account_update.account = alice.get_id();\n            authorize_account_update.auth.add_authority(some_public_key, 1);\n            authorize_account_update.auth.weight_threshold = 1;\n            authorize_account_update.enabled = true;\n            authorize_account_update.valid_to = db.head_block_time() + 86400;\n            authorize_account_update.operation_type = operation::tag<account_update_operation>::value;\n\n            // Shall not update the owner key member\n            auto owner_index = member_index<account_update_operation>(\"owner\");\n            restriction no_owner = restriction(owner_index, FUNC(eq), void_t());\n\n            // Shall not update the active key member\n            auto active_index = member_index<account_update_operation>(\"active\");\n            restriction no_active = restriction(active_index, FUNC(eq), void_t());\n\n            // Shall not update the memo key member of the new_options member\n            auto new_options_index = member_index<account_update_operation>(\"new_options\");\n            auto memo_index = member_index<account_options>(\"memo_key\");\n            restriction same_memo = restriction(new_options_index, FUNC(attr),\n                                                vector<restriction>{\n                                                        restriction(memo_index, FUNC(eq), alice.options.memo_key)});\n\n            // Shall not update the extensions member\n            auto ext_index = member_index<account_update_operation>(\"extensions\");\n            restriction no_ext = restriction(ext_index, FUNC(eq), void_t());\n\n            auto owner_special_index = member_index<account_update_operation::ext>(\"owner_special_authority\");\n            restriction no_special_owner = restriction(ext_index, FUNC(attr),\n                                                       vector<restriction>{\n                                                               restriction(owner_special_index, FUNC(eq), void_t())});\n\n            auto active_special_index = member_index<account_update_operation::ext>(\"active_special_authority\");\n            restriction no_special_active = restriction(ext_index, FUNC(attr),\n                                                        vector<restriction>{\n                                                                restriction(active_special_index, FUNC(eq), void_t())});\n\n            // Shall not update the extensions member of the new_options member\n            auto new_options_ext_index = member_index<account_options>(\"extensions\");\n            restriction no_new_options_ext = restriction(new_options_index, FUNC(attr), vector<restriction>{\n                    restriction(new_options_ext_index, FUNC(eq), void_t())});\n\n            // Combine all of the shall not restrictions\n            vector<restriction> shall_not_restrictions = {no_owner, no_active, no_special_owner, no_special_active,\n                                                          same_memo};\n            authorize_account_update.restrictions = shall_not_restrictions;\n            //[\n            //  {\n            //    \"member_index\": 2,\n            //    \"restriction_type\": 0,\n            //    \"argument\": [\n            //      0,\n            //      {}\n            //    ],\n            //    \"extensions\": []\n            //  },\n            //  {\n            //    \"member_index\": 3,\n            //    \"restriction_type\": 0,\n            //    \"argument\": [\n            //      0,\n            //      {}\n            //    ],\n            //    \"extensions\": []\n            //  },\n            //  {\n            //    \"member_index\": 5,\n            //    \"restriction_type\": 10,\n            //    \"argument\": [\n            //      39,\n            //      [\n            //        {\n            //          \"member_index\": 1,\n            //          \"restriction_type\": 0,\n            //          \"argument\": [\n            //            0,\n            //            {}\n            //          ],\n            //          \"extensions\": []\n            //        }\n            //      ]\n            //    ],\n            //    \"extensions\": []\n            //  },\n            //  {\n            //    \"member_index\": 5,\n            //    \"restriction_type\": 10,\n            //    \"argument\": [\n            //      39,\n            //      [\n            //        {\n            //          \"member_index\": 2,\n            //          \"restriction_type\": 0,\n            //          \"argument\": [\n            //            0,\n            //            {}\n            //          ],\n            //          \"extensions\": []\n            //        }\n            //      ]\n            //    ],\n            //    \"extensions\": []\n            //  },\n            //  {\n            //    \"member_index\": 4,\n            //    \"restriction_type\": 10,\n            //    \"argument\": [\n            //      39,\n            //      [\n            //        {\n            //          \"member_index\": 0,\n            //          \"restriction_type\": 0,\n            //          \"argument\": [\n            //            5,\n            //            \"BTS7zsqi7QUAjTAdyynd6DVe8uv4K8gCTRHnAoMN9w9CA1xLCTDVv\"\n            //          ],\n            //          \"extensions\": []\n            //        }\n            //      ]\n            //    ],\n            //    \"extensions\": []\n            //  }\n            //]\n\n            // Broadcast the transaction\n            trx.clear();\n            trx.operations = {authorize_account_update};\n            sign(trx, alice_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // The key attempts to update the owner key for alice\n         // This should fail because it is NOT authorized by alice\n         // It violates Restriction 1 (index-0)\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n\n            uop.owner = authority(1, some_public_key, 1);\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_owner_auth);\n            // The failure should indicate the rejection path\n            // {\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // The key attempts to update the active key for alice\n         // This should fail because it is NOT authorized by alice\n         // It violates Restriction 2 (index-1)\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n\n            uop.active = authority(1, some_public_key, 1);\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // {\"success\":false,\"rejection_path\":[[0,1],[2,\"predicate_was_false\"]]}\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,1],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // The key attempts to update the special owner key for alice\n         // This should fail because it is NOT authorized by alice\n         // It violates Restriction 3 (index-2)\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n\n            uop.extensions.value.owner_special_authority = no_special_authority();\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_owner_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,2],[0,0],[2,\"predicate_was_false\"]\n            // [0,2]: 0 is the rejection_indicator for an index to a sub-restriction; 2 is the index value for Restriction 3\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,2],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // The key attempts to update the special active key for alice\n         // This should fail because it is NOT authorized by alice\n         // It violates Restriction 4 (index-3)\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n\n            uop.extensions.value.active_special_authority = no_special_authority();\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,3],[0,0],[2,\"predicate_was_false\"]\n            // [0,3]: 0 is the rejection_indicator for an index to a sub-restriction; 3 is the index value for Restriction 4\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,3],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // The key attempts to update the memo key for alice\n         // This should fail because it is NOT authorized by alice\n         // It violates Restriction 5 (index-4)\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n\n            account_options alice_options = alice.options;\n            alice_options.memo_key = some_public_key;\n            uop.new_options = alice_options;\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // \"rejection_path\":[[0,4],[0,0],[2,\"predicate_was_false\"]\n            // [0,4]: 0 is the rejection_indicator for an index to a sub-restriction; 4 is the index value for Restriction 5\n            // [0,0]: 0 is the rejection_indicator for an index to a sub-restriction; 0 is the index value for the only argument\n            // [2,\"predicate_was_false\"]: 0 is the rejection_indicator for rejection_reason; \"predicate_was_false\" is the reason\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,4],[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // The key attempts to update the voting slate for alice\n         // This should succeed because the key is authorized by alice\n         //////\n         {\n            account_update_operation uop;\n            uop.account = alice.get_id();\n            account_options alice_options = alice.options;\n            auto insert_result = alice_options.votes.insert(witness0_obj.vote_id);\n            if (!insert_result.second)\n               FC_THROW(\"Account ${account} was already voting for witness ${witness}\",\n                        (\"account\", alice)(\"witness\", \"init0\"));\n            uop.new_options = alice_options;\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(uop));\n            sign(trx, some_private_key);\n            PUSH_TX(db, trx);\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of CAA for witness_update_operation\n    *\n    * Scenario: Test of authorization of one account (alice) authorizing a key\n    * to ONLY change the signing key of a witness account\n    */\n   BOOST_AUTO_TEST_CASE(authorized_change_witness_signing_key) {\n      try {\n\n         //////\n         // Initialize the blockchain\n         //////\n         generate_blocks(HARDFORK_BSIP_40_TIME);\n         generate_blocks(5);\n         db.modify(global_property_id_type()(db), [](global_property_object &gpo) {\n            gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type();\n         });\n         set_expiration(db, trx);\n\n\n         //////\n         // Initialize: Accounts\n         //////\n         // Create a new witness account (witness0)\n         ACTORS((witness0));\n         // Upgrade witness account to LTM\n         upgrade_to_lifetime_member(witness0.get_id());\n         generate_block();\n\n         // Create the witnesses\n         // Get the witness0 identifier after a block has been generated\n         // to be sure of using the most up-to-date identifier for the account\n         const account_id_type witness0_identifier = get_account(\"witness0\").get_id();\n         create_witness(witness0_identifier, witness0_private_key);\n\n         generate_block();\n\n         // Find the witness ID for witness0\n         const auto& idx = db.get_index_type<witness_index>().indices().get<by_account>();\n         witness_object witness0_obj = *idx.find(witness0_identifier);\n         BOOST_CHECK(witness0_obj.witness_account == witness0_identifier);\n\n\n         //////\n         // Define a key that can be authorized\n         // This can be a new key or an existing key. The existing key may even be the active key of an account.\n         //////\n         fc::ecc::private_key some_private_key = generate_private_key(\"some key\");\n         public_key_type some_public_key = public_key_type(some_private_key.get_public_key());\n\n\n         //////\n         // Define an alternate witness signing key\n         //////\n         fc::ecc::private_key alternate_signing_private_key = generate_private_key(\"some signing key\");\n         public_key_type alternate_signing_public_key = public_key_type(alternate_signing_private_key.get_public_key());\n         // The current signing key should be different than the alternate signing public key\n         BOOST_CHECK(witness0_obj.signing_key != alternate_signing_public_key);\n\n\n         //////\n         // The key attempts to update the signing key of witness0\n         // This should fail because the key is NOT authorized by witness0 to update the signing key\n         //////\n         {\n            witness_update_operation wop;\n            wop.witness = witness0_obj.id;\n            wop.witness_account = witness0_obj.witness_account;\n\n            wop.new_signing_key = alternate_signing_public_key;\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(wop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should not indicate any rejected custom auths because no CAA applies for the key's attempt\n            // \"rejected_custom_auths\":[]\n            EXPECT_EXCEPTION_STRING(\"\\\"rejected_custom_auths\\\":[]\", [&] { PUSH_TX(db, trx); });\n         }\n\n\n         //////\n         // Alice authorizes the key to only update the witness signing key\n         //////\n         {\n            custom_authority_create_operation authorize_update_signing_key;\n            authorize_update_signing_key.account = witness0.get_id();\n            authorize_update_signing_key.auth.add_authority(some_public_key, 1);\n            authorize_update_signing_key.auth.weight_threshold = 1;\n            authorize_update_signing_key.enabled = true;\n            authorize_update_signing_key.valid_to = db.head_block_time() + 86400;\n            authorize_update_signing_key.operation_type = operation::tag<witness_update_operation>::value;\n            auto url_index = member_index<witness_update_operation>(\"new_url\");\n            restriction no_url = restriction(url_index, FUNC(eq), void_t());\n            authorize_update_signing_key.restrictions = {no_url};\n            //[\n            //  {\n            //    \"member_index\": 3,\n            //    \"restriction_type\": 0,\n            //    \"argument\": [\n            //      0,\n            //      {}\n            //    ]\n            //  }\n            //]\n\n            // Broadcast the transaction\n            trx.clear();\n            trx.operations = {authorize_update_signing_key};\n            sign(trx, witness0_private_key);\n            PUSH_TX(db, trx);\n         }\n\n\n         //////\n         // The key attempts to update the URL of witness0\n         // This should fail because the key is NOT authorized by witness0 to update the URL\n         //////\n         {\n            witness_update_operation wop;\n            wop.witness = witness0_obj.id;\n            wop.witness_account = witness0_obj.witness_account;\n\n            wop.new_url = \"NEW_URL\";\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(wop));\n            sign(trx, some_private_key);\n            BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth);\n            // The failure should indicate the rejection path\n            // {\"success\":false,\"rejection_path\":[[0,0],[2,\"predicate_was_false\"]]}\n            EXPECT_EXCEPTION_STRING(\"\\\"rejection_path\\\":[[0,0],[2,\\\"predicate_was_false\\\"]]\", [&] {PUSH_TX(db, trx);});\n         }\n\n\n         //////\n         // The key attempts to update the signing key of witness0\n         // This should succeed because the key is authorized by witness0 to update the signing key\n         //////\n         {\n            witness_update_operation wop;\n            wop.witness = witness0_obj.id;\n            wop.witness_account = witness0_obj.witness_account;\n\n            wop.new_signing_key = alternate_signing_public_key;\n\n            trx.clear();\n            trx.operations.emplace_back(std::move(wop));\n            sign(trx, some_private_key);\n            PUSH_TX(db, trx);\n\n            // Check the current signing key for witness0\n            witness_object updated_witness0_obj = *idx.find(witness0_obj.witness_account);\n            BOOST_CHECK(updated_witness0_obj.witness_account == witness0_obj.witness_account);\n            BOOST_CHECK(updated_witness0_obj.signing_key == alternate_signing_public_key);\n         }\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/custom_operations.cpp",
    "content": "/*\n * Copyright (c) 2019 oxarbitrage and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/api.hpp>\n#include <graphene/utilities/tempdir.hpp>\n#include <fc/crypto/digest.hpp>\n\n#include <graphene/custom_operations/custom_operations_plugin.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\n#define BOOST_TEST_MODULE Custom operations plugin tests\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::app;\nusing namespace graphene::custom_operations;\n\nBOOST_FIXTURE_TEST_SUITE( custom_operation_tests, database_fixture )\n\nvoid map_operation(flat_map<string, optional<string>>& pairs, bool remove, string& catalog, account_id_type& account,\n      fc::ecc::private_key& pk, database& db)\n{\n   signed_transaction trx;\n   set_expiration(db, trx);\n\n   custom_operation op;\n   account_storage_map store;\n\n   store.key_values = pairs;\n   store.remove = remove;\n   store.catalog = catalog;\n\n   auto packed = fc::raw::pack(store);\n   packed.insert(packed.begin(), account_storage_object::type_id);\n\n   op.payer = account;\n   op.data = packed;\n   op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op);\n   trx.operations.push_back(op);\n   trx.sign(pk, db.get_chain_id());\n   PUSH_TX(db, trx, ~0);\n   trx.clear();\n}\n\nBOOST_AUTO_TEST_CASE(custom_operations_account_storage_map_test)\n{\ntry {\n   ACTORS((nathan)(alice)(robert)(patty));\n\n   app.enable_plugin(\"custom_operations\");\n   custom_operations_api custom_operations_api(app);\n\n   generate_block();\n   enable_fees();\n\n   int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   transfer(committee_account, nathan_id, asset(init_balance));\n   transfer(committee_account, alice_id, asset(init_balance));\n\n   // catalog is indexed so cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed)\n   std::string catalog(201, 'a');\n   flat_map<string, optional<string>> pairs;\n   pairs[\"key\"] = fc::json::to_string(\"value\");\n   map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   auto storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", catalog);\n   BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 );\n\n   // keys are indexed so they cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed)\n   catalog = \"whatever\";\n   std::string key(201, 'a');\n   pairs.clear();\n   pairs[key] = fc::json::to_string(\"value\");\n   map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", catalog);\n   BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 );\n\n   // creating a map with bad json as value is not allowed\n   catalog = \"whatever\";\n   pairs.clear();\n   pairs[\"key\"] = \"value\";\n   map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", catalog);\n   BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 );\n\n   // nathan adds key-value data via custom operation to a settings catalog\n   catalog = \"settings\";\n   pairs.clear();\n   pairs[\"language\"] = fc::json::to_string(\"en\");\n   pairs[\"image_url\"] = fc::json::to_string(\"http://some.image.url/img.jpg\");\n   map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // check nathan stored data with the api\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"settings\");\n   BOOST_REQUIRE_EQUAL(storage_results_nathan.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), \"http://some.image.url/img.jpg\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), \"en\");\n\n   // edit some stuff and add new stuff\n   pairs.clear();\n   pairs[\"image_url\"] = fc::json::to_string(\"http://new.image.url/newimg.jpg\");\n   pairs[\"theme\"] = fc::json::to_string(\"dark\");\n   map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // check old and new stuff\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"settings\");\n   BOOST_REQUIRE_EQUAL(storage_results_nathan.size(), 3U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), \"en\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[2].key, \"theme\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[2].value->as_string(), \"dark\");\n\n   // delete stuff from the storage\n   pairs.clear();\n   pairs[\"theme\"] = fc::json::to_string(\"dark\");\n   map_operation(pairs, true, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // theme is removed from the storage\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"settings\");\n   BOOST_REQUIRE_EQUAL(storage_results_nathan.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), \"en\");\n\n   // delete stuff that it is not there\n   pairs.clear();\n   pairs[\"nothere\"] = fc::json::to_string(\"nothere\");\n   map_operation(pairs, true, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // nothing changes\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"settings\");\n   BOOST_REQUIRE_EQUAL(storage_results_nathan.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), \"en\");\n\n   // alice, duplicated keys in storage, only second value will be added\n   pairs.clear();\n   catalog = \"random\";\n   pairs[\"key1\"] = fc::json::to_string(\"value1\");\n   pairs[\"key1\"] = fc::json::to_string(\"value2\");\n   map_operation(pairs, false, catalog, alice_id, alice_private_key, db);\n   generate_block();\n\n   vector<account_storage_object> storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"random\");\n   BOOST_REQUIRE_EQUAL(storage_results_alice.size(), 1U );\n   BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, \"key1\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].value->as_string(), \"value2\");\n\n   // add an object\n   pairs.clear();\n   catalog = \"account_object\";\n   pairs[\"nathan\"] = fc::json::to_string(nathan);\n   map_operation(pairs, false, catalog, alice_id, alice_private_key, db);\n   generate_block();\n\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"account_object\");\n   BOOST_REQUIRE_EQUAL(storage_results_alice.size(), 1U);\n   BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17);\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].value->as<account_object>(20).name, \"nathan\");\n\n   // add 2 more objects\n   pairs.clear();\n   catalog = \"account_object\";\n   pairs[\"robert\"] = fc::json::to_string(robert);\n   pairs[\"patty\"] = fc::json::to_string(patty);\n   map_operation(pairs, false, catalog, alice_id, alice_private_key, db);\n   generate_block();\n\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"account_object\");\n   BOOST_REQUIRE_EQUAL(storage_results_alice.size(), 3U);\n   BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17);\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].value->as<account_object>(20).name, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].account.instance.value, 17);\n   BOOST_CHECK_EQUAL(storage_results_alice[1].key, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].value->as<account_object>(20).name, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].key, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].value->as<account_object>(20).name, \"robert\");\n\n   // alice adds key-value data via custom operation to a settings catalog\n   catalog = \"settings\";\n   pairs.clear();\n   pairs[\"image_url\"] = fc::json::to_string(\"http://some.other.image.url/img.jpg\");\n   map_operation(pairs, false, catalog, alice_id, alice_private_key, db);\n   generate_block();\n\n   // test API limit config\n   BOOST_CHECK_THROW( custom_operations_api.get_storage_info(\"alice\", \"account_object\", {}, 7), fc::exception );\n\n   // This does not throw\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"account_object\", {}, 6);\n   BOOST_REQUIRE_EQUAL(storage_results_alice.size(), 3U);\n   BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17);\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].value->as<account_object>(20).name, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].account.instance.value, 17);\n   BOOST_CHECK_EQUAL(storage_results_alice[1].key, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].value->as<account_object>(20).name, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].key, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].value->as<account_object>(20).name, \"robert\");\n\n   // query by a wrong account\n   BOOST_CHECK_THROW( custom_operations_api.get_storage_info(\"alice1\", \"account_object\" ), fc::exception );\n\n   // query by account and key\n   BOOST_CHECK_THROW( custom_operations_api.get_storage_info(\"alice\", {}, \"patty\" ), fc::exception );\n\n   // query by key only\n   BOOST_CHECK_THROW( custom_operations_api.get_storage_info({}, {}, \"patty\" ), fc::exception );\n\n   // query by account, catalog and key\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"account_object\", \"alice1\");\n   BOOST_CHECK_EQUAL(storage_results_alice.size(), 0 );\n\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"account_object1\", \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice.size(), 0 );\n\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"account_object\", \"patty\");\n   BOOST_REQUIRE_EQUAL(storage_results_alice.size(), 1U );\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].value->as<account_object>(20).name, \"patty\");\n\n   // query by account only\n   storage_results_alice = custom_operations_api.get_storage_info(\"alice\");\n   BOOST_REQUIRE_EQUAL(storage_results_alice.size(), 5U );\n   BOOST_CHECK_EQUAL(storage_results_alice[0].catalog, \"random\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, \"key1\");\n   BOOST_CHECK_EQUAL(storage_results_alice[0].value->as_string(), \"value2\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].key, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[1].value->as<account_object>(20).name, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].key, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[2].value->as<account_object>(20).name, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results_alice[3].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results_alice[3].key, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results_alice[3].value->as<account_object>(20).name, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results_alice[4].catalog, \"settings\");\n   BOOST_CHECK_EQUAL(storage_results_alice[4].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results_alice[4].value->as_string(), \"http://some.other.image.url/img.jpg\");\n\n   // query by catalog only\n   auto storage_results = custom_operations_api.get_storage_info({}, \"settings1\");\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 0 );\n\n   storage_results = custom_operations_api.get_storage_info({}, \"settings\");\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 3U );\n   BOOST_CHECK_EQUAL(storage_results[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results[1].value->as_string(), \"en\");\n   BOOST_CHECK_EQUAL(storage_results[2].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[2].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[2].value->as_string(), \"http://some.other.image.url/img.jpg\");\n\n   // Pagination\n   storage_results = custom_operations_api.get_storage_info({}, \"settings\", {}, 2);\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results[1].value->as_string(), \"en\");\n\n   account_storage_id_type storage_id { storage_results[1].id };\n\n   storage_results = custom_operations_api.get_storage_info({}, \"settings\", {}, 2, storage_id);\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[0].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results[0].value->as_string(), \"en\");\n   BOOST_CHECK_EQUAL(storage_results[1].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[1].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[1].value->as_string(), \"http://some.other.image.url/img.jpg\");\n\n   // query by catalog and key\n   storage_results = custom_operations_api.get_storage_info({}, \"settings\", \"test\");\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 0 );\n\n   storage_results = custom_operations_api.get_storage_info({}, \"settings1\", \"image_url\");\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 0 );\n\n   storage_results = custom_operations_api.get_storage_info({}, \"settings\", \"image_url\");\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results[1].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[1].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[1].value->as_string(), \"http://some.other.image.url/img.jpg\");\n\n   // query all\n   storage_results = custom_operations_api.get_storage_info();\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 6U ); // the configured limit, the first page\n   BOOST_CHECK_EQUAL(storage_results[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[0].catalog, \"settings\");\n   BOOST_CHECK_EQUAL(storage_results[0].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[0].value->as_string(), \"http://new.image.url/newimg.jpg\");\n   BOOST_CHECK_EQUAL(storage_results[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results[1].catalog, \"settings\");\n   BOOST_CHECK_EQUAL(storage_results[1].key, \"language\");\n   BOOST_CHECK_EQUAL(storage_results[1].value->as_string(), \"en\");\n   BOOST_CHECK_EQUAL(storage_results[2].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[2].catalog, \"random\");\n   BOOST_CHECK_EQUAL(storage_results[2].key, \"key1\");\n   BOOST_CHECK_EQUAL(storage_results[2].value->as_string(), \"value2\");\n   BOOST_CHECK_EQUAL(storage_results[3].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[3].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results[3].key, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results[3].value->as<account_object>(20).name, \"nathan\");\n   BOOST_CHECK_EQUAL(storage_results[4].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[4].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results[4].key, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results[4].value->as<account_object>(20).name, \"patty\");\n   BOOST_CHECK_EQUAL(storage_results[5].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[5].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results[5].key, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results[5].value->as<account_object>(20).name, \"robert\");\n\n   storage_id = storage_results[5].id;\n\n   storage_results = custom_operations_api.get_storage_info({},{},{},{},storage_id);\n   BOOST_REQUIRE_EQUAL(storage_results.size(), 2U ); // the 2nd page\n   BOOST_CHECK_EQUAL(storage_results[0].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[0].catalog, \"account_object\");\n   BOOST_CHECK_EQUAL(storage_results[0].key, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results[0].value->as<account_object>(20).name, \"robert\");\n   BOOST_CHECK_EQUAL(storage_results[1].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results[1].catalog, \"settings\");\n   BOOST_CHECK_EQUAL(storage_results[1].key, \"image_url\");\n   BOOST_CHECK_EQUAL(storage_results[1].value->as_string(), \"http://some.other.image.url/img.jpg\");\n\n}\ncatch (fc::exception &e) {\n   edump((e.to_detail_string()));\n   throw;\n} }\n\nBOOST_AUTO_TEST_CASE(custom_operations_account_storage_list_test)\n{\ntry {\n   ACTORS((nathan)(alice)(robert)(patty));\n\n   app.enable_plugin(\"custom_operations\");\n   custom_operations_api custom_operations_api(app);\n\n   generate_block();\n   enable_fees();\n\n   int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   transfer(committee_account, nathan_id, asset(init_balance));\n   transfer(committee_account, alice_id, asset(init_balance));\n\n   // catalog is indexed so cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed)\n   std::string catalog(201, 'a');\n   flat_map<string, optional<string>> accounts;\n   accounts[robert.name];\n   map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   auto storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", catalog);\n   BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 );\n\n   // This throws due to API limit\n   BOOST_CHECK_THROW( custom_operations_api.get_storage_info(\"nathan\", catalog, {}, 120), fc::exception );\n\n   // keys are indexed so they cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed)\n   catalog = \"whatever\";\n   std::string value(201, 'a');\n   accounts.clear();\n   accounts[value];\n   map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", catalog);\n   BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 );\n\n   // nathan add a list of accounts to storage\n   accounts.clear();\n   accounts[alice.name];\n   accounts[robert.name];\n   catalog = \"contact_list\";\n   map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // get the account list for nathan, check alice and robert are there\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"contact_list\");\n   BOOST_REQUIRE_EQUAL(storage_results_nathan.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, alice.name);\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].key, robert.name);\n\n   // add a value into account list already there\n   accounts.clear();\n   accounts[alice.name];\n   map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // nothing changes\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"contact_list\");\n   BOOST_REQUIRE_EQUAL(storage_results_nathan.size(), 2U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, alice.name);\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[1].key, robert.name);\n\n   // delete alice from the list\n   accounts.clear();\n   accounts[alice.name];\n   map_operation(accounts, true, catalog, nathan_id, nathan_private_key, db);\n   generate_block();\n\n   // alice gone\n   storage_results_nathan = custom_operations_api.get_storage_info(\"nathan\", \"contact_list\");\n   BOOST_CHECK_EQUAL(storage_results_nathan.size(), 1U );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 );\n   BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name);\n\n   // duplicated accounts in the list, only 1 will be inserted\n   accounts.clear();\n   accounts[robert.name];\n   accounts[robert.name];\n   map_operation(accounts, false, catalog, alice_id, alice_private_key, db);\n   generate_block();\n\n   auto storage_results_alice = custom_operations_api.get_storage_info(\"alice\", \"contact_list\");\n   BOOST_CHECK_EQUAL(storage_results_alice.size(), 1U );\n   BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 );\n   BOOST_CHECK_EQUAL(storage_results_alice[0].key, robert.name);\n}\ncatch (fc::exception &e) {\n   edump((e.to_detail_string()));\n   throw;\n} }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/database_api_tests.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/database_api.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <fc/crypto/digest.hpp>\n#include <fc/crypto/hex.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\n#include <random>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(database_api_tests, database_fixture)\n\nBOOST_AUTO_TEST_CASE(is_registered)\n{\n   try {\n      /***\n       * Arrange\n       */\n      auto nathan_private_key = generate_private_key(\"nathan\");\n      public_key_type nathan_public = nathan_private_key.get_public_key();\n\n      auto dan_private_key = generate_private_key(\"dan\");\n      public_key_type dan_public = dan_private_key.get_public_key();\n\n      auto unregistered_private_key = generate_private_key(\"unregistered\");\n      public_key_type unregistered_public = unregistered_private_key.get_public_key();\n\n\n      /***\n       * Act\n       */\n      create_account(\"dan\", dan_private_key.get_public_key());\n      create_account(\"nathan\", nathan_private_key.get_public_key());\n      // Unregistered key will not be registered with any account\n\n\n      /***\n       * Assert\n       */\n      graphene::app::database_api db_api1(db);\n      BOOST_CHECK_THROW( db_api1.is_public_key_registered((string) nathan_public), fc::exception );\n\n      graphene::app::application_options opt = app.get_options();\n      opt.has_api_helper_indexes_plugin = true;\n      graphene::app::database_api db_api( db, &opt );\n\n      BOOST_CHECK(db_api.is_public_key_registered((string) nathan_public));\n      BOOST_CHECK(db_api.is_public_key_registered((string) dan_public));\n      BOOST_CHECK(!db_api.is_public_key_registered((string) unregistered_public));\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active )\n{\n   try {\n      fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      public_key_type pub_key_active( nathan_key1.get_public_key() );\n      public_key_type pub_key_owner( nathan_key2.get_public_key() );\n      const account_object& nathan = create_account(\"nathan\", nathan_key1.get_public_key() );\n\n      try {\n         account_update_operation op;\n         op.account = nathan.id;\n         op.active = authority(1, pub_key_active, 1);\n         op.owner = authority(1, pub_key_owner, 1);\n         trx.operations.push_back(op);\n         sign(trx, nathan_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n      } FC_CAPTURE_AND_RETHROW ((nathan.active))\n\n      // this op requires active\n      transfer_operation op;\n      op.from = nathan.id;\n      op.to = account_id_type();\n      trx.operations.push_back(op);\n\n      graphene::app::database_api db_api(db);\n      set<public_key_type> pub_keys = db_api.get_potential_signatures( trx );\n\n      BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n\n      trx.operations.clear();\n\n      // this op requires owner\n      account_update_operation auop;\n      auop.account = nathan.id;\n      auop.owner = authority(1, pub_key_owner, 1);\n      trx.operations.push_back(auop);\n\n      pub_keys = db_api.get_potential_signatures( trx );\n\n      BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); // active key doesn't help in this case\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\n/// Testing get_potential_signatures and get_required_signatures for non-immediate owner authority issue.\n/// https://github.com/bitshares/bitshares-core/issues/584\nBOOST_AUTO_TEST_CASE( get_signatures_non_immediate_owner )\n{\n   try {\n      fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      fc::ecc::private_key ashley_key1 = fc::ecc::private_key::regenerate(fc::digest(\"akey1\"));\n      fc::ecc::private_key ashley_key2 = fc::ecc::private_key::regenerate(fc::digest(\"akey2\"));\n      fc::ecc::private_key oliver_key1 = fc::ecc::private_key::regenerate(fc::digest(\"okey1\"));\n      fc::ecc::private_key oliver_key2 = fc::ecc::private_key::regenerate(fc::digest(\"okey2\"));\n      public_key_type pub_key_active( nathan_key1.get_public_key() );\n      public_key_type pub_key_owner( nathan_key2.get_public_key() );\n      public_key_type a_pub_key_active( ashley_key1.get_public_key() );\n      public_key_type a_pub_key_owner( ashley_key2.get_public_key() );\n      public_key_type o_pub_key_active( oliver_key1.get_public_key() );\n      public_key_type o_pub_key_owner( oliver_key2.get_public_key() );\n      const account_object& nathan = create_account(\"nathan\", nathan_key1.get_public_key() );\n      const account_object& ashley = create_account(\"ashley\", ashley_key1.get_public_key() );\n      const account_object& oliver = create_account(\"oliver\", oliver_key1.get_public_key() );\n      account_id_type nathan_id = nathan.get_id();\n      account_id_type ashley_id = ashley.get_id();\n      account_id_type oliver_id = oliver.get_id();\n\n      try {\n         account_update_operation op;\n         op.account = nathan_id;\n         op.active = authority(1, pub_key_active, 1, ashley_id, 1);\n         op.owner = authority(1, pub_key_owner, 1, oliver_id, 1);\n         trx.operations.push_back(op);\n         sign(trx, nathan_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n\n         op.account = ashley_id;\n         op.active = authority(1, a_pub_key_active, 1);\n         op.owner = authority(1, a_pub_key_owner, 1);\n         trx.operations.push_back(op);\n         sign(trx, ashley_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n\n         op.account = oliver_id;\n         op.active = authority(1, o_pub_key_active, 1);\n         op.owner = authority(1, o_pub_key_owner, 1);\n         trx.operations.push_back(op);\n         sign(trx, oliver_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n      } FC_CAPTURE_AND_RETHROW ((nathan.active))\n\n      // this transaction requires active\n      signed_transaction trx_a;\n      transfer_operation op;\n      op.from = nathan_id;\n      op.to = account_id_type();\n      trx_a.operations.push_back(op);\n\n      // get potential signatures\n      graphene::app::database_api db_api(db);\n      set<public_key_type> pub_keys = db_api.get_potential_signatures( trx_a );\n\n      BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() );\n      // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584\n      BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() );\n      // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584\n      BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() );\n\n      // get required signatures\n      pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner, o_pub_key_owner } );\n      BOOST_CHECK( pub_keys.empty() );\n\n      // this op requires owner\n      signed_transaction trx_o;\n      account_update_operation auop;\n      auop.account = nathan_id;\n      auop.owner = authority(1, pub_key_owner, 1);\n      trx_o.operations.push_back(auop);\n\n      // get potential signatures\n      pub_keys = db_api.get_potential_signatures( trx_o );\n\n      // active authorities doesn't help in this case\n      BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() );\n\n      // owner authorities should be ok\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() );\n      // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584\n      BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() );\n\n      // get required signatures\n      pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } );\n      BOOST_CHECK( pub_keys.empty() );\n\n      // go beyond hard fork\n      generate_blocks( HARDFORK_CORE_584_TIME, true );\n\n      // for the transaction that requires active\n      // get potential signatures\n      pub_keys = db_api.get_potential_signatures( trx_a );\n\n      // all authorities should be ok\n      BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() );\n\n      // get required signatures\n      pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner } );\n      BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() );\n      pub_keys = db_api.get_required_signatures( trx_a, { o_pub_key_owner } );\n      BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() );\n\n      // for the transaction that requires owner\n      // get potential signatures\n      pub_keys = db_api.get_potential_signatures( trx_o );\n\n      // active authorities doesn't help in this case\n      BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() );\n\n      // owner authorities should help\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() );\n\n      // get required signatures\n      pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } );\n      BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( get_potential_signatures_other )\n{\n   try {\n      fc::ecc::private_key priv_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      public_key_type pub_key1( priv_key1.get_public_key() );\n\n      const account_object& nathan = create_account( \"nathan\" );\n\n      balance_claim_operation op;\n      op.deposit_to_account = nathan.id;\n      op.balance_owner_key = pub_key1;\n      trx.operations.push_back(op);\n\n      graphene::app::database_api db_api(db);\n      set<public_key_type> pub_keys = db_api.get_potential_signatures( trx );\n\n      BOOST_CHECK( pub_keys.find( pub_key1 ) != pub_keys.end() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active )\n{\n   try {\n      fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      public_key_type pub_key_active( nathan_key1.get_public_key() );\n      public_key_type pub_key_owner( nathan_key2.get_public_key() );\n      const account_object& nathan = create_account(\"nathan\", nathan_key1.get_public_key() );\n\n      try {\n         account_update_operation op;\n         op.account = nathan.id;\n         op.active = authority(1, pub_key_active, 1);\n         op.owner = authority(1, pub_key_owner, 1);\n         trx.operations.push_back(op);\n         sign(trx, nathan_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n      } FC_CAPTURE_AND_RETHROW ((nathan.active))\n\n      graphene::app::database_api db_api(db);\n\n      // prepare available keys sets\n      flat_set<public_key_type> avail_keys1, avail_keys2, avail_keys3;\n      avail_keys1.insert( pub_key_active );\n      avail_keys2.insert( pub_key_owner );\n      avail_keys3.insert( pub_key_active );\n      avail_keys3.insert( pub_key_owner );\n\n      set<public_key_type> pub_keys;\n\n      // this op requires active\n      transfer_operation op;\n      op.from = nathan.id;\n      op.to = account_id_type();\n      trx.operations.push_back(op);\n\n      // provides active, should be ok\n      pub_keys = db_api.get_required_signatures( trx, avail_keys1 );\n      BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() );\n\n      // provides owner, should be ok\n      pub_keys = db_api.get_required_signatures( trx, avail_keys2 );\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n\n      // provides both active and owner, should return one of them\n      pub_keys = db_api.get_required_signatures( trx, avail_keys3 );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() || pub_keys.find( pub_key_owner ) != pub_keys.end() );\n\n      trx.operations.clear();\n\n      // this op requires owner\n      account_update_operation auop;\n      auop.account = nathan.id;\n      auop.owner = authority(1, pub_key_owner, 1);\n      trx.operations.push_back(auop);\n\n      // provides active, should return an empty set\n      pub_keys = db_api.get_required_signatures( trx, avail_keys1 );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides owner, should return it\n      pub_keys = db_api.get_required_signatures( trx, avail_keys2 );\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n\n      // provides both active and owner, should return owner only\n      pub_keys = db_api.get_required_signatures( trx, avail_keys3 );\n      BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not )\n{\n   try {\n      fc::ecc::private_key morgan_key = fc::ecc::private_key::regenerate(fc::digest(\"morgan_key\"));\n      fc::ecc::private_key nathan_key = fc::ecc::private_key::regenerate(fc::digest(\"nathan_key\"));\n      fc::ecc::private_key oliver_key = fc::ecc::private_key::regenerate(fc::digest(\"oliver_key\"));\n      public_key_type pub_key_morgan( morgan_key.get_public_key() );\n      public_key_type pub_key_nathan( nathan_key.get_public_key() );\n      public_key_type pub_key_oliver( oliver_key.get_public_key() );\n      const account_object& morgan = create_account(\"morgan\", morgan_key.get_public_key() );\n      const account_object& nathan = create_account(\"nathan\", nathan_key.get_public_key() );\n      const account_object& oliver = create_account(\"oliver\", oliver_key.get_public_key() );\n\n      graphene::app::database_api db_api(db);\n\n      // prepare available keys sets\n      flat_set<public_key_type> avail_keys_empty, avail_keys_m, avail_keys_n, avail_keys_o;\n      flat_set<public_key_type> avail_keys_mn, avail_keys_mo, avail_keys_no, avail_keys_mno;\n      avail_keys_m.insert( pub_key_morgan );\n      avail_keys_mn.insert( pub_key_morgan );\n      avail_keys_mo.insert( pub_key_morgan );\n      avail_keys_mno.insert( pub_key_morgan );\n      avail_keys_n.insert( pub_key_nathan );\n      avail_keys_mn.insert( pub_key_nathan );\n      avail_keys_no.insert( pub_key_nathan );\n      avail_keys_mno.insert( pub_key_nathan );\n      avail_keys_o.insert( pub_key_oliver );\n      avail_keys_mo.insert( pub_key_oliver );\n      avail_keys_no.insert( pub_key_oliver );\n      avail_keys_mno.insert( pub_key_oliver );\n\n      // result set\n      set<public_key_type> pub_keys;\n\n      // make a transaction that require 1 signature (m)\n      transfer_operation op;\n      op.from = morgan.id;\n      op.to = oliver.id;\n      trx.operations.push_back(op);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n     // provides [m], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n       // provides [m,n], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // sign with n, but actually need m\n      sign(trx, nathan_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [m,o], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n,o], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // sign with m, should be enough\n      trx.clear_signatures();\n      sign(trx, morgan_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // sign with m+n, although m only should be enough, this API won't complain\n      sign(trx, nathan_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // make a transaction that require 2 signatures (m+n)\n      trx.clear_signatures();\n      op.from = nathan.id;\n      trx.operations.push_back(op);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return [m,n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 2 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,o], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n,o], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,n,o], should return [m,n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 2 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // sign with o, but actually need m+n\n      sign(trx, oliver_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return [m,n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 2 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,o], should return [m]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n\n      // provides [n,o], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,n,o], should return [m,n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 2 );\n      BOOST_CHECK( pub_keys.find( pub_key_morgan ) != pub_keys.end() );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // sign with m+o, but actually need m+n\n      sign(trx, morgan_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n,o], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,n,o], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // sign with m, but actually need m+n\n      trx.clear_signatures();\n      sign(trx, morgan_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n,o], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // provides [m,n,o], should return [n]\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 1 );\n      BOOST_CHECK( pub_keys.find( pub_key_nathan ) != pub_keys.end() );\n\n      // sign with m+n, should be enough\n      sign(trx, nathan_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // sign with m+n+o, should be enough as well\n      sign(trx, oliver_key);\n\n      // provides [], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_empty );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_m );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_n );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_o );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mn );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mo );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_no );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n      // provides [m,n,o], should return []\n      pub_keys = db_api.get_required_signatures( trx, avail_keys_mno );\n      BOOST_CHECK( pub_keys.size() == 0 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( subscription_key_collision_test )\n{ try {\n   object_id_type uia_object_id = create_user_issued_asset( \"UIATEST\" ).id;\n\n   uint32_t objects_changed = 0;\n   auto callback = [&]( const variant& v )\n   {\n      ++objects_changed;\n   };\n\n   graphene::app::database_api db_api(db);\n   db_api.set_subscribe_callback( callback, false );\n\n   // subscribe to an account which has same instance ID as UIATEST\n   vector<string> collision_ids;\n   collision_ids.push_back( string( object_id_type( account_id_type( uia_object_id.instance() ) ) ) );\n   db_api.get_accounts( collision_ids );\n\n   generate_block();\n   fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n   BOOST_CHECK_EQUAL( objects_changed, 0 ); // did not subscribe to UIATEST, so no notification\n\n   vector<string> asset_names;\n   asset_names.push_back( \"UIATEST\" );\n   db_api.get_assets( asset_names );\n\n   generate_block();\n   fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n   BOOST_CHECK_EQUAL( objects_changed, 0 ); // UIATEST did not change in this block, so no notification\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_CASE( subscription_notification_test )\n{\n   try {\n\n      generate_blocks(HARDFORK_CORE_1468_TIME);\n      set_expiration( db, trx );\n      set_htlc_committee_parameters();\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS( (alice)(bob) );\n\n      create_user_issued_asset( \"UIATEST\" );\n\n      // prepare data for get_htlc\n      {\n         int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n         transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n\n         uint16_t preimage_size = 256;\n         std::vector<char> pre_image(256);\n         std::independent_bits_engine<std::default_random_engine, sizeof(unsigned), unsigned int> rbe;\n         std::generate(begin(pre_image), end(pre_image), std::ref(rbe));\n\n         // alice puts a htlc contract to bob\n         graphene::chain::htlc_create_operation create_operation;\n         BOOST_TEST_MESSAGE(\"Alice, who has 100 coins, is transferring 3 coins to Bob\");\n         create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         create_operation.to = bob_id;\n         create_operation.claim_period_seconds = 60;\n         create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n         create_operation.preimage_size = preimage_size;\n         create_operation.from = alice_id;\n         create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation);\n         trx.operations.push_back(create_operation);\n         sign(trx, alice_private_key);\n         PUSH_TX(db, trx, ~0);\n         trx.clear();\n      }\n\n// declare db_api1 ~ db_api60\n#define SUB_NOTIF_TEST_NUM_CALLBACKS_PLUS_ONE 61\n// db_api1  ~ db_api30 auto-subscribe in the beginning,\n// db_api31 ~ db_api60 don't auto-subscribe\n#define SUB_NOTIF_TEST_START_ID_DISABLE_AUTO_SUB 31\n\n// create callback functions\n#define SUB_NOTIF_TEST_INIT_CALLBACKS(z, i, data) \\\n      uint32_t objects_changed ## i = 0; \\\n      auto callback ## i = [&]( const variant& v ) \\\n      { \\\n         idump((i)(v)); \\\n         ++objects_changed ## i; \\\n      }; \\\n      uint32_t expected_objects_changed ## i = 0;\n\n// create function to check results\n#define SUB_NOTIF_TEST_CHECK(z, i, data) \\\n      if( expected_objects_changed ## i > 0 ) { \\\n         BOOST_CHECK_LE( expected_objects_changed ## i, objects_changed ## i ); \\\n      } else { \\\n         BOOST_CHECK_EQUAL( expected_objects_changed ## i, objects_changed ## i ); \\\n      } \\\n      expected_objects_changed ## i = 0; \\\n      objects_changed ## i = 0;\n\n      BOOST_PP_REPEAT_FROM_TO( 1, SUB_NOTIF_TEST_NUM_CALLBACKS_PLUS_ONE, SUB_NOTIF_TEST_INIT_CALLBACKS, unused );\n\n      auto check_results = [&]()\n      {\n         BOOST_PP_REPEAT_FROM_TO( 1, SUB_NOTIF_TEST_NUM_CALLBACKS_PLUS_ONE, SUB_NOTIF_TEST_CHECK, unused );\n      };\n\n#undef SUB_NOTIF_TEST_CHECK\n#undef SUB_NOTIF_TEST_INIT_CALLBACKS\n\n      graphene::app::database_api db_api1(db);\n\n      // subscribing to all should fail\n      BOOST_CHECK_THROW( db_api1.set_subscribe_callback( callback1, true ), fc::exception );\n\n      db_api1.set_subscribe_callback( callback1, false );\n\n      graphene::app::application_options opt;\n      opt.enable_subscribe_to_all = true;\n\n      graphene::app::database_api db_api2( db, &opt );\n      db_api2.set_subscribe_callback( callback2, true ); // subscribing to all should succeed\n\n// declare the rest of API callers and initialize callbacks\n#define SUB_NOTIF_TEST_INIT_APIS(z, i, data) \\\n      graphene::app::database_api db_api ## i( db, &opt ); \\\n      db_api ## i.set_subscribe_callback( callback ## i, false );\n\n      BOOST_PP_REPEAT_FROM_TO( 3, SUB_NOTIF_TEST_NUM_CALLBACKS_PLUS_ONE, SUB_NOTIF_TEST_INIT_APIS, unused );\n\n#undef SUB_NOTIF_TEST_INIT_APIS\n\n// disable auto-subscription for some API callers\n#define SUB_NOTIF_TEST_DISABLE_AUTO_SUB(z, i, data) \\\n      db_api ## i.set_auto_subscription( false );\n\n      BOOST_PP_REPEAT_FROM_TO( SUB_NOTIF_TEST_START_ID_DISABLE_AUTO_SUB, SUB_NOTIF_TEST_NUM_CALLBACKS_PLUS_ONE,\n                               SUB_NOTIF_TEST_DISABLE_AUTO_SUB, unused );\n\n#undef SUB_NOTIF_TEST_DISABLE_AUTO_SUB\n#undef SUB_NOTIF_TEST_NUM_CALLBACKS_PLUS_ONE\n#undef SUB_NOTIF_TEST_START_ID_DISABLE_AUTO_SUB\n\n      vector<object_id_type> account_ids;\n      account_ids.push_back( alice.id );\n      db_api1.get_objects( account_ids );         // db_api1  subscribe to Alice\n      db_api11.get_objects( account_ids, true );  // db_api11 subscribe to Alice\n      db_api21.get_objects( account_ids, false ); // db_api21 doesn't subscribe to Alice\n      db_api31.get_objects( account_ids );        // db_api31 doesn't subscribe to Alice\n      db_api41.get_objects( account_ids, true );  // db_api41 subscribe to Alice\n      db_api51.get_objects( account_ids, false ); // db_api51 doesn't subscribe to Alice\n\n      vector<string> account_names;\n      account_names.push_back( \"alice\" );\n      db_api4.get_accounts( account_names );         // db_api4  subscribe to Alice\n      db_api14.get_accounts( account_names, true );  // db_api14 subscribe to Alice\n      db_api24.get_accounts( account_names, false ); // db_api24 doesn't subscribe to Alice\n      db_api34.get_accounts( account_names );        // db_api34 doesn't subscribe to Alice\n      db_api44.get_accounts( account_names, true );  // db_api44 subscribe to Alice\n      db_api54.get_accounts( account_names, false ); // db_api54 doesn't subscribe to Alice\n\n      db_api5.lookup_accounts( \"ali\", 1 );         // db_api5  subscribe to Alice\n      db_api15.lookup_accounts( \"ali\", 1, true );  // db_api15 subscribe to Alice\n      db_api25.lookup_accounts( \"ali\", 1, false ); // db_api25 doesn't subscribe to Alice\n      db_api35.lookup_accounts( \"ali\", 1 );        // db_api35 doesn't subscribe to Alice\n      db_api45.lookup_accounts( \"ali\", 1, true );  // db_api45 subscribe to Alice\n      db_api55.lookup_accounts( \"ali\", 1, false ); // db_api55 doesn't subscribe to Alice\n\n      db_api6.lookup_accounts( \"alice\", 3 );         // db_api6  does not subscribe to Alice\n      db_api16.lookup_accounts( \"alice\", 3, true );  // db_api16 does not subscribe to Alice\n      db_api26.lookup_accounts( \"alice\", 3, false ); // db_api26 does not subscribe to Alice\n      db_api36.lookup_accounts( \"alice\", 3 );        // db_api36 does not subscribe to Alice\n      db_api46.lookup_accounts( \"alice\", 3, true );  // db_api46 does not subscribe to Alice\n      db_api56.lookup_accounts( \"alice\", 3, false ); // db_api56 does not subscribe to Alice\n\n      vector<string> asset_names;\n      asset_names.push_back( \"UIATEST\" );\n      db_api7.get_assets( asset_names );         // db_api7  subscribe to UIA\n      db_api17.get_assets( asset_names, true );  // db_api17 subscribe to UIA\n      db_api27.get_assets( asset_names, false ); // db_api27 doesn't subscribe to UIA\n      db_api37.get_assets( asset_names );        // db_api37 doesn't subscribe to UIA\n      db_api47.get_assets( asset_names, true );  // db_api47 subscribe to UIA\n      db_api57.get_assets( asset_names, false ); // db_api57 doesn't subscribe to UIA\n\n      graphene::chain::htlc_id_type alice_htlc_id_bob; // assuming ID of the first htlc object is 0\n      db_api8.get_htlc( alice_htlc_id_bob );         // db_api8  subscribe to the HTLC object\n      db_api18.get_htlc( alice_htlc_id_bob, true );  // db_api18 subscribe to the HTLC object\n      db_api28.get_htlc( alice_htlc_id_bob, false ); // db_api28 doesn't subscribe to the HTLC object\n      db_api38.get_htlc( alice_htlc_id_bob );        // db_api38 doesn't subscribe to the HTLC object\n      db_api48.get_htlc( alice_htlc_id_bob, true );  // db_api48 subscribe to the HTLC object\n      db_api58.get_htlc( alice_htlc_id_bob, false ); // db_api58 doesn't subscribe to the HTLC object\n\n      generate_block();\n      ++expected_objects_changed1; // db_api1 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed11; // db_api11 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed41; // db_api41 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed2; // db_api2 subscribed to all, notify new objects\n      // db_api3 didn't subscribe to anything, nothing would be notified\n      ++expected_objects_changed4; // db_api4 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed14; // db_api14 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed44; // db_api44 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed5; // db_api5 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed15; // db_api15 subscribed to Alice, notify Alice account creation\n      ++expected_objects_changed45; // db_api45 subscribed to Alice, notify Alice account creation\n      // db_api*6 didn't subscribe to anything, nothing would be notified\n      ++expected_objects_changed7; // db_api7 subscribed to UIA, notify asset creation\n      ++expected_objects_changed17; // db_api17 subscribed to UIA, notify asset creation\n      ++expected_objects_changed47; // db_api47 subscribed to UIA, notify asset creation\n      ++expected_objects_changed8; // db_api8 subscribed to HTLC object, notify object creation\n      ++expected_objects_changed18; // db_api18 subscribed to HTLC object, notify object creation\n      ++expected_objects_changed48; // db_api48 subscribed to HTLC object, notify object creation\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n      transfer( account_id_type(), alice_id, asset(1) );\n      generate_block();\n      // db_api1 didn't subscribe to Alice with get_full_accounts but only subscribed to the account object,\n      //   nothing would be notified\n      ++expected_objects_changed2; // db_api2 subscribed to all, notify new balance object and etc\n      // db_api3 didn't subscribe to anything, nothing would be notified\n      // db_api4 only subscribed to the account object of Alice, nothing notified\n      // db_api5 only subscribed to the account object of Alice, nothing notified\n      // db_api6 didn't subscribe to anything, nothing would be notified\n      // db_api7: no change on UIA, nothing would be notified\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n      vector<object_id_type> obj_ids;\n      obj_ids.push_back( db.get_dynamic_global_properties().id );\n      db_api3.get_objects( obj_ids ); // db_api3 subscribe to dynamic global properties\n\n      db_api4.get_full_accounts( account_names, true );   // db_api4 subscribe to Alice with get_full_accounts\n      db_api14.get_full_accounts( account_names, false ); // db_api14 doesn't subscribe\n      db_api24.get_full_accounts( account_names );        // db_api24 subscribe to Alice with get_full_accounts\n      db_api34.get_full_accounts( account_names, true );  // db_api34 subscribe to Alice with get_full_accounts\n      db_api44.get_full_accounts( account_names, false ); // db_api44 doesn't subscribe\n      db_api54.get_full_accounts( account_names );        // db_api54 doesn't subscribe\n\n      db_api5.get_full_accounts( account_names, false ); // db_api5 doesn't subscribe\n\n      transfer( account_id_type(), alice_id, asset(1) );\n      generate_block();\n      // db_api1 didn't subscribe to Alice with get_full_accounts but only subscribed to the account object,\n      //   nothing would be notified\n      ++expected_objects_changed2; // db_api2 subscribed to all, notify new history records and etc\n      ++expected_objects_changed3; // db_api3 subscribed to dynamic global properties, would be notified\n      ++expected_objects_changed4; // db_api4 subscribed to full account data of Alice, would be notified\n      ++expected_objects_changed24; // db_api24 subscribed to full account data of Alice, would be notified\n      ++expected_objects_changed34; // db_api34 subscribed to full account data of Alice, would be notified\n      // db_api5 only subscribed to the account object of Alice, nothing notified\n      // db_api6 didn't subscribe to anything, nothing would be notified\n      // db_api7: no change on UIA, nothing would be notified\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n      db_api6.set_auto_subscription( false );\n      db_api6.get_objects( obj_ids ); // db_api6 doesn't auto-subscribe to dynamic global properties\n\n      generate_block();\n      // db_api1 only subscribed to the account object of Alice, nothing notified\n      // db_api2 subscribed to all, but no object is created or removed in this block, so nothing notified\n      ++expected_objects_changed3; // db_api3 subscribed to dynamic global properties, would be notified\n      // db_api4 subscribed to full account data of Alice, nothing would be notified\n      // db_api5 only subscribed to the account object of Alice, nothing notified\n      // db_api6 didn't subscribe to anything, nothing would be notified\n      // db_api7: no change on UIA, nothing would be notified\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n      account_names.clear();\n      account_names.push_back( \"bob\" );\n      db_api5.set_auto_subscription( false );\n      db_api5.get_full_accounts( account_names, true ); // db_api5 subscribe to full account data of Bob\n\n      db_api6.get_full_accounts( account_names, false ); // db_api6 doesn't subscribe\n\n      transfer( account_id_type(), bob_id, asset(1) );\n\n      generate_block();\n      // db_api1 only subscribed to the account object of Alice, nothing notified\n      ++expected_objects_changed2; // db_api2 subscribed to all, notify new history records and etc\n      ++expected_objects_changed3; // db_api3 subscribed to dynamic global properties, would be notified\n      // db_api4 subscribed to full account data of Alice, nothing would be notified\n      ++expected_objects_changed5; // db_api5 subscribed to full account data of Bob, would be notified\n      // db_api6 didn't subscribe to anything, nothing would be notified\n      // db_api7: no change on UIA, nothing would be notified\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n      db_api6.set_auto_subscription( true );\n      db_api6.get_objects( obj_ids ); // db_api6 auto-subscribe to dynamic global properties\n\n      generate_block();\n      // db_api1 only subscribed to the account object of Alice, nothing notified\n      // db_api2 subscribed to all, but no object is created or removed in this block, so nothing notified\n      ++expected_objects_changed3; // db_api3 subscribed to dynamic global properties, would be notified\n      // db_api4 subscribed to full account data of Alice, nothing would be notified\n      // db_api5 subscribed to full account data of Bob, nothing notified\n      ++expected_objects_changed6; // db_api6 subscribed to dynamic global properties, would be notified\n      // db_api7: no change on UIA, nothing would be notified\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n      db_api5.set_subscribe_callback( callback5, false ); // reset subscription\n\n      db_api6.cancel_all_subscriptions();\n      db_api6.get_objects( obj_ids ); // db_api6 doesn't auto-subscribe to dynamic global properties\n\n      transfer( alice_id, bob_id, asset(1) );\n\n      generate_block();\n      // db_api1 only subscribed to the account object of Alice, nothing notified\n      ++expected_objects_changed2; // db_api2 subscribed to all, notify new history records and etc\n      ++expected_objects_changed3; // db_api3 subscribed to dynamic global properties, would be notified\n      ++expected_objects_changed4; // db_api4 subscribed to full account data of Alice, would be notified\n      ++expected_objects_changed24; // db_api24 subscribed to full account data of Alice, would be notified\n      ++expected_objects_changed34; // db_api34 subscribed to full account data of Alice, would be notified\n      // db_api5 subscribed to anything, nothing notified\n      // db_api6 subscribed to anything, nothing notified\n      // db_api7: no change on UIA, nothing would be notified\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n      check_results();\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( get_all_workers )\n{ try {\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   ACTORS( (connie)(whitney)(wolverine) );\n\n   fund(connie);\n   upgrade_to_lifetime_member(connie);\n   fund(whitney);\n   upgrade_to_lifetime_member(whitney);\n   fund(wolverine);\n   upgrade_to_lifetime_member(wolverine);\n\n   vector<worker_object> results;\n\n   const auto& worker1 = create_worker( connie_id, 1000, fc::days(10) );\n   worker_id_type worker1_id { worker1.id };\n\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 1 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 0 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 1 );\n   BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers(false).front().id == worker1_id );\n\n   generate_blocks( db.head_block_time() + fc::days(11) );\n   set_expiration( db, trx );\n\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 1 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 1 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 0 );\n   BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id );\n\n   const auto& worker2 = create_worker( whitney_id, 1000, fc::days(50) );\n   worker_id_type worker2_id { worker2.id };\n\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 2 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 1 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 1 );\n   BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers().back().id == worker2_id );\n   BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers(false).front().id == worker2_id );\n\n   const auto& worker3 = create_worker( wolverine_id, 1000, fc::days(100) );\n   worker_id_type worker3_id { worker3.id };\n\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 3 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 1 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 2 );\n   BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers().back().id == worker3_id );\n   BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers(false).front().id == worker2_id );\n   BOOST_CHECK( db_api.get_all_workers(false).back().id == worker3_id );\n\n   generate_blocks( db.head_block_time() + fc::days(55) );\n   set_expiration( db, trx );\n\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 3 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 2 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 1 );\n   BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers().back().id == worker3_id );\n   BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers(true).back().id == worker2_id );\n   BOOST_CHECK( db_api.get_all_workers(false).front().id == worker3_id );\n\n   generate_blocks( db.head_block_time() + fc::days(55) );\n   set_expiration( db, trx );\n\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 3 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 3 );\n   BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 0 );\n   BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers().back().id == worker3_id );\n   BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id );\n   BOOST_CHECK( db_api.get_all_workers(true).back().id == worker3_id );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( get_workers_by_account )\n{ try {\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   ACTORS( (connie)(whitney)(wolverine) );\n\n   fund(connie);\n   upgrade_to_lifetime_member(connie);\n   fund(whitney);\n   upgrade_to_lifetime_member(whitney);\n   fund(wolverine);\n   upgrade_to_lifetime_member(wolverine);\n\n   vector<worker_object> results;\n\n   const auto& worker1 = create_worker( connie_id );\n   worker_id_type worker1_id { worker1.id };\n\n   const auto& worker2 = create_worker( whitney_id, 1000, fc::days(50) );\n   worker_id_type worker2_id { worker2.id };\n\n   const auto& worker3 = create_worker( whitney_id, 1000, fc::days(100) );\n   worker_id_type worker3_id { worker3.id };\n\n   BOOST_REQUIRE_EQUAL( db_api.get_workers_by_account(\"connie\").size(), 1 );\n   BOOST_CHECK( db_api.get_workers_by_account(\"connie\").front().id == worker1_id );\n\n   BOOST_REQUIRE_EQUAL( db_api.get_workers_by_account(string(whitney.id)).size(), 2 );\n   BOOST_CHECK( db_api.get_workers_by_account(string(whitney.id)).front().id == worker2_id );\n   BOOST_CHECK( db_api.get_workers_by_account(string(whitney.id)).back().id == worker3_id );\n\n   BOOST_REQUIRE_EQUAL( db_api.get_workers_by_account(\"wolverine\").size(), 0 );\n\n   BOOST_REQUIRE_THROW( db_api.get_workers_by_account(\"not-a-user\"), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( lookup_vote_ids )\n{ try {\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   ACTORS( (connie)(whitney)(wolverine) );\n\n   fund(connie);\n   upgrade_to_lifetime_member(connie);\n   fund(whitney);\n   upgrade_to_lifetime_member(whitney);\n   fund(wolverine);\n   upgrade_to_lifetime_member(wolverine);\n\n   const auto& committee = create_committee_member( connie );\n   const auto& witness = create_witness( whitney );\n   const auto& worker = create_worker( wolverine_id );\n\n   std::vector<vote_id_type> votes;\n   votes.push_back( committee.vote_id );\n   votes.push_back( witness.vote_id );\n   votes.push_back( worker.vote_for );\n\n   const auto results = db_api.lookup_vote_ids( votes );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(get_limit_orders_by_account)\n{ try {\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   ACTORS((seller)(buyer)(watcher));\n\n   const auto& bitcny = create_user_issued_asset(\"CNY\");\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(10000000);\n   transfer( committee_account, seller_id, asset(init_balance) );\n   issue_uia( buyer_id, bitcny.amount(init_balance) );\n   BOOST_CHECK_EQUAL( 10000000, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 10000000, get_balance(buyer, bitcny) );\n\n   std::vector<limit_order_object> results, results2;\n   limit_order_object o;\n\n   // limit too large\n   BOOST_CHECK_THROW( db_api.get_limit_orders_by_account( seller.name, 102 ), fc::exception );\n\n   // The order book is empty\n   results = db_api.get_limit_orders_by_account( seller.name );\n   BOOST_CHECK_EQUAL( results.size(), 0 );\n\n   // Seller create 50 orders\n   for (size_t i = 0 ; i < 50 ; ++i)\n   {\n      BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250)));\n   }\n\n   // Get all orders\n   results = db_api.get_limit_orders_by_account( seller.name );\n   BOOST_CHECK_EQUAL( results.size(), 50 );\n\n   // Seller create 200 orders\n   for (size_t i = 1 ; i < 101 ; ++i)\n   {\n      BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 + i)));\n      BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 - i)));\n   }\n\n   // Buyer create 20 orders\n   for (size_t i = 0 ; i < 70 ; ++i)\n   {\n      BOOST_CHECK(create_sell_order(buyer, bitcny.amount(100), core.amount(5000 + i)));\n   }\n\n   // Get the first 101 orders\n   results = db_api.get_limit_orders_by_account( seller.name );\n   BOOST_CHECK_EQUAL( results.size(), 101 );\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].id < results[i+1].id);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(250)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(276)));\n   o = results.back();\n\n   // Get the No. 101-201 orders\n   results = db_api.get_limit_orders_by_account( seller.name, {}, o.get_id() );\n   BOOST_CHECK_EQUAL( results.size(), 101 );\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].id < results[i+1].id);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(276)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(326)));\n   o = results.back();\n\n   // Get the No. 201- orders\n   results = db_api.get_limit_orders_by_account( seller.name, {}, o.get_id() );\n   BOOST_CHECK_EQUAL( results.size(), 50 );\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].id < results[i+1].id);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(326)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(150)));\n\n   // Get the No. 201-210 orders\n   results2 = db_api.get_limit_orders_by_account( seller.name, 10, o.get_id() );\n   BOOST_CHECK_EQUAL( results2.size(), 10 );\n   for (size_t i = 0 ; i < results2.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results2[i].id < results2[i+1].id);\n      BOOST_CHECK(results[i].id == results2[i].id);\n   }\n   BOOST_CHECK(results2.front().sell_price == price(core.amount(100), bitcny.amount(326)));\n   BOOST_CHECK(results2.back().sell_price == price(core.amount(100), bitcny.amount(170)));\n\n   // Buyer has 70 orders, all IDs are greater than sellers\n   results = db_api.get_limit_orders_by_account( buyer.name, 90, o.get_id() );\n   BOOST_CHECK_EQUAL( results.size(), 70 );\n   o = results.back();\n\n   // All seller's order IDs are smaller, so querying with a buyer's ID will get nothing\n   results = db_api.get_limit_orders_by_account( seller.name, 90, o.get_id() );\n   BOOST_CHECK_EQUAL( results.size(), 0 );\n\n   // Watcher has no order\n   results = db_api.get_limit_orders_by_account( watcher.name );\n   BOOST_CHECK_EQUAL( results.size(), 0 );\n\n   // unregistered account, throws exception\n   BOOST_CHECK_THROW( db_api.get_limit_orders_by_account( \"not-a-user\", 10, limit_order_id_type() ),\n                      fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(get_account_limit_orders)\n{ try {\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   ACTORS((seller));\n\n   const auto& bitcny = create_bitasset(\"CNY\");\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(10000000);\n   transfer(committee_account, seller_id, asset(init_balance));\n   BOOST_CHECK_EQUAL( 10000000, get_balance(seller, core) );\n\n   /// Create 250 versatile orders\n   for (size_t i = 0 ; i < 50 ; ++i)\n   {\n      BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250)));\n   }\n\n   for (size_t i = 1 ; i < 101 ; ++i)\n   {\n      BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 + i)));\n      BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 - i)));\n   }\n\n   std::vector<limit_order_object> results;\n   limit_order_object o;\n\n   // query with no constraint, expected:\n   // 1. up to 101 orders returned\n   // 2. orders were sorted by price desendingly\n   results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\");\n   BOOST_CHECK(results.size() == 101);\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(150)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(250)));\n   results.clear();\n\n   // query with specified limit, expected:\n   // 1. up to specified amount of orders returned\n   // 2. orders were sorted by price desendingly\n   results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\", 50);\n   BOOST_CHECK(results.size() == 50);\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(150)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(199)));\n\n   o = results.back();\n   results.clear();\n\n   // query with specified order id and limit, expected:\n   // same as before, but also the first order's id equal to specified\n   results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\", 80,\n       limit_order_id_type(o.id));\n   BOOST_CHECK(results.size() == 80);\n   BOOST_CHECK(results.front().id == o.id);\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(199)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(250)));\n\n   o = results.back();\n   results.clear();\n\n   // query with specified price and an not exists order id, expected:\n   // 1. the canceled order should not exists in returned orders and first order's\n   //    id should greater than specified\n   // 2. returned orders sorted by price desendingly\n   // 3. the first order's sell price equal to specified\n   cancel_limit_order(o); // NOTE 1: this canceled order was in scope of the\n                          // first created 50 orders, so with price 2.5 BTS/CNY\n   results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\", 50,\n       limit_order_id_type(o.id), o.sell_price);\n   BOOST_CHECK(results.size() == 50);\n   BOOST_CHECK(results.front().id > o.id);\n   // NOTE 2: because of NOTE 1, here should be equal\n   BOOST_CHECK(results.front().sell_price == o.sell_price);\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(250)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(279)));\n\n   o = results.back();\n   results.clear();\n\n   cancel_limit_order(o); // NOTE 3: this time the canceled order was in scope\n                          // of the lowest price 150 orders\n   results = db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\", 101,\n       limit_order_id_type(o.id), o.sell_price);\n   BOOST_CHECK(results.size() == 71);\n   BOOST_CHECK(results.front().id > o.id);\n   // NOTE 3: because of NOTE 1, here should be little than\n   BOOST_CHECK(results.front().sell_price < o.sell_price);\n   for (size_t i = 0 ; i < results.size() - 1 ; ++i)\n   {\n      BOOST_CHECK(results[i].sell_price >= results[i+1].sell_price);\n   }\n   BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(280)));\n   BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(350)));\n\n   BOOST_CHECK_THROW(db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, \"CNY\", 101,\n               limit_order_id_type(o.id)), fc::exception);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( get_transaction_hex )\n{ try {\n   graphene::app::database_api db_api(db);\n   auto test_private_key = generate_private_key(\"testaccount\");\n   public_key_type test_public = test_private_key.get_public_key();\n\n   trx.operations.push_back(make_account(\"testaccount\", test_public));\n   trx.validate();\n\n   // case1: not signed, get hex\n   std::string hex_str = fc::to_hex( fc::raw::pack( trx ) );\n\n   BOOST_CHECK( db_api.get_transaction_hex( trx ) == hex_str );\n   BOOST_CHECK( db_api.get_transaction_hex_without_sig( trx ) + \"00\" == hex_str );\n\n   // case2: signed, get hex\n   sign( trx, test_private_key );\n   hex_str = fc::to_hex( fc::raw::pack( trx ) );\n\n   BOOST_CHECK( db_api.get_transaction_hex( trx ) == hex_str );\n   BOOST_CHECK( db_api.get_transaction_hex_without_sig( trx ) +\n                   fc::to_hex( fc::raw::pack( trx.signatures ) ) == hex_str );\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests get_block, get_block_header, get_block_header_batch\nBOOST_AUTO_TEST_CASE( get_block_tests )\n{ try {\n\n   const auto& get_block_id = []( const graphene::app::maybe_signed_block_header& header )\n   {\n      signed_block_header signed_header( static_cast<block_header>( header ) );\n      if( header.witness_signature.valid() )\n         signed_header.witness_signature = *header.witness_signature;\n      return signed_header.id();\n   };\n\n   generate_block();\n\n   ACTORS( (nathan) );\n   auto block1 = generate_block( ~graphene::chain::database::skip_witness_signature );\n   auto block2 = generate_block( ~graphene::chain::database::skip_witness_signature );\n\n   fund( nathan_id(db) );\n   auto block3 = generate_block( ~graphene::chain::database::skip_witness_signature );\n\n   idump( (block1)(block2)(block3) );\n\n   uint32_t head_block_num = db.head_block_num();\n\n   graphene::app::database_api db_api(db);\n   auto head_block = db_api.get_block( head_block_num );\n   idump( (head_block) );\n   BOOST_REQUIRE( head_block.valid() );\n   BOOST_CHECK_EQUAL( head_block->block_num(), head_block_num );\n   BOOST_CHECK_EQUAL( head_block->transactions.size(), 1U );\n   BOOST_CHECK( head_block->witness_signature != signature_type() );\n   BOOST_CHECK( head_block->id() == block3.id() );\n\n   auto head_block_header = db_api.get_block_header( head_block_num, true );\n   idump( (head_block_header) );\n   BOOST_REQUIRE( head_block_header.valid() );\n   BOOST_CHECK_EQUAL( head_block_header->block_num(), head_block_num );\n   BOOST_REQUIRE( head_block_header->witness_signature.valid() );\n   BOOST_CHECK( *head_block_header->witness_signature == head_block->witness_signature );\n   BOOST_CHECK( get_block_id( *head_block_header ) == head_block->id() );\n\n   auto head_block_header2 = db_api.get_block_header( head_block_num );\n   idump( (head_block_header2) );\n   BOOST_REQUIRE( head_block_header2.valid() );\n   BOOST_CHECK_EQUAL( head_block_header2->block_num(), head_block_num );\n   BOOST_CHECK( head_block_header2->previous == head_block->previous );\n   BOOST_CHECK( head_block_header2->timestamp == head_block->timestamp );\n   BOOST_CHECK( head_block_header2->witness == head_block->witness );\n   BOOST_CHECK( head_block_header2->transaction_merkle_root == head_block->transaction_merkle_root );\n   BOOST_CHECK( !head_block_header2->witness_signature.valid() );\n   BOOST_CHECK( get_block_id( *head_block_header2 ) != head_block->id() );\n\n   auto head_block_header3 = db_api.get_block_header( head_block_num, false );\n   idump( (head_block_header3) );\n   BOOST_REQUIRE( head_block_header3.valid() );\n   BOOST_CHECK_EQUAL( head_block_header3->block_num(), head_block_num );\n   BOOST_CHECK( head_block_header3->previous == head_block->previous );\n   BOOST_CHECK( head_block_header3->timestamp == head_block->timestamp );\n   BOOST_CHECK( head_block_header3->witness == head_block->witness );\n   BOOST_CHECK( head_block_header3->transaction_merkle_root == head_block->transaction_merkle_root );\n   BOOST_CHECK( !head_block_header3->witness_signature.valid() );\n   BOOST_CHECK( get_block_id( *head_block_header3 ) == get_block_id( *head_block_header2 ) );\n\n   auto previous_block = db_api.get_block( head_block_num - 1 );\n   BOOST_REQUIRE( previous_block.valid() );\n   BOOST_CHECK_EQUAL( previous_block->block_num(), head_block_num - 1 );\n   BOOST_CHECK_EQUAL( previous_block->transactions.size(), 0 );\n   BOOST_CHECK( previous_block->id() == head_block->previous );\n   BOOST_CHECK( previous_block->witness_signature != signature_type() );\n   BOOST_CHECK( previous_block->witness_signature != head_block->witness_signature );\n   BOOST_CHECK( previous_block->id() == block2.id() );\n\n   auto previous_block_header = db_api.get_block_header( head_block_num - 1, true );\n   BOOST_REQUIRE( previous_block_header.valid() );\n   BOOST_CHECK_EQUAL( previous_block_header->block_num(), head_block_num - 1 );\n   BOOST_REQUIRE( previous_block_header->witness_signature.valid() );\n   BOOST_CHECK( *previous_block_header->witness_signature == previous_block->witness_signature );\n   BOOST_CHECK( get_block_id( *previous_block_header ) == previous_block->id() );\n\n   auto next_block = db_api.get_block( head_block_num + 1 );\n   BOOST_CHECK( !next_block.valid() );\n\n   auto next_block_header = db_api.get_block_header( head_block_num + 1, true );\n   BOOST_CHECK( !next_block_header.valid() );\n\n   const auto block_headers = db_api.get_block_header_batch( { head_block_num, head_block_num + 1,\n                                                               head_block_num - 1 }, true );\n   BOOST_REQUIRE_EQUAL( block_headers.size(), 3U );\n   BOOST_CHECK_THROW( block_headers.at( head_block_num + 2 ), std::out_of_range );\n   BOOST_CHECK( !block_headers.at( head_block_num + 1 ).valid() );\n   BOOST_REQUIRE( block_headers.at( head_block_num ).valid() );\n   BOOST_CHECK( block_headers.at( head_block_num )->block_num() == head_block_header->block_num() );\n   BOOST_CHECK( get_block_id( *block_headers.at( head_block_num ) ) == head_block->id() );\n   BOOST_REQUIRE( block_headers.at( head_block_num )->witness_signature.valid() );\n   BOOST_CHECK( *block_headers.at( head_block_num )->witness_signature == head_block->witness_signature );\n   BOOST_REQUIRE( block_headers.at( head_block_num - 1 ).valid() );\n   BOOST_CHECK( block_headers.at( head_block_num - 1 )->block_num() == previous_block_header->block_num() );\n   BOOST_CHECK( get_block_id( *block_headers.at( head_block_num - 1 ) ) == previous_block->id() );\n   BOOST_REQUIRE( block_headers.at( head_block_num - 1 )->witness_signature.valid() );\n   BOOST_CHECK( *block_headers.at( head_block_num - 1 )->witness_signature == previous_block->witness_signature );\n\n   const auto block_headers2 = db_api.get_block_header_batch( { head_block_num, head_block_num + 1,\n                                                                head_block_num - 1 } );\n   BOOST_REQUIRE_EQUAL( block_headers2.size(), 3U );\n   BOOST_CHECK_THROW( block_headers2.at( head_block_num + 2 ), std::out_of_range );\n   BOOST_CHECK( !block_headers2.at( head_block_num + 1 ).valid() );\n   BOOST_REQUIRE( block_headers2.at( head_block_num ).valid() );\n   BOOST_CHECK( block_headers2.at( head_block_num )->block_num() == head_block_header->block_num() );\n   BOOST_CHECK( get_block_id( *block_headers2.at( head_block_num ) ) == get_block_id( *head_block_header2 ) );\n   BOOST_CHECK( !block_headers2.at( head_block_num )->witness_signature.valid() );\n   BOOST_REQUIRE( block_headers2.at( head_block_num - 1 ).valid() );\n   BOOST_CHECK( block_headers2.at( head_block_num - 1 )->block_num() == previous_block_header->block_num() );\n   BOOST_CHECK( get_block_id( *block_headers2.at( head_block_num - 1 ) ) != previous_block->id() );\n   BOOST_CHECK( !block_headers2.at( head_block_num - 1 )->witness_signature.valid() );\n\n   const auto block_headers3 = db_api.get_block_header_batch( { head_block_num, head_block_num + 1,\n                                                                head_block_num - 1 }, false );\n   BOOST_REQUIRE_EQUAL( block_headers3.size(), 3U );\n   BOOST_CHECK_THROW( block_headers3.at( head_block_num + 2 ), std::out_of_range );\n   BOOST_CHECK( !block_headers3.at( head_block_num + 1 ).valid() );\n   BOOST_REQUIRE( block_headers3.at( head_block_num ).valid() );\n   BOOST_CHECK( block_headers3.at( head_block_num )->block_num() == head_block_header->block_num() );\n   BOOST_CHECK( get_block_id( *block_headers3.at( head_block_num ) ) == get_block_id( *head_block_header2 ) );\n   BOOST_CHECK( !block_headers3.at( head_block_num )->witness_signature.valid() );\n   BOOST_REQUIRE( block_headers3.at( head_block_num - 1 ).valid() );\n   BOOST_CHECK( block_headers3.at( head_block_num - 1 )->block_num() == previous_block_header->block_num() );\n   BOOST_CHECK( get_block_id( *block_headers3.at( head_block_num - 1 ) )\n                   == get_block_id( *block_headers2.at( head_block_num - 1 ) ) );\n   BOOST_CHECK( !block_headers3.at( head_block_num - 1 )->witness_signature.valid() );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(verify_account_authority)\n{\n      try {\n\n         ACTORS( (nathan) );\n         graphene::app::database_api db_api(db);\n\n         // good keys\n         flat_set<public_key_type> public_keys;\n         public_keys.emplace(nathan_public_key);\n         BOOST_CHECK(db_api.verify_account_authority( \"nathan\", public_keys));\n\n         // bad keys\n         flat_set<public_key_type> bad_public_keys;\n         bad_public_keys.emplace(public_key_type(\"BTS6MkMxwBjFWmcDjXRoJ4mW9Hd4LCSPwtv9tKG1qYW5Kgu4AhoZy\"));\n         BOOST_CHECK(!db_api.verify_account_authority( \"nathan\", bad_public_keys));\n\n      } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( any_two_of_three )\n{\n   try {\n      fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      fc::ecc::private_key nathan_key3 = fc::ecc::private_key::regenerate(fc::digest(\"key3\"));\n      const account_object& nathan = create_account(\"nathan\", nathan_key1.get_public_key() );\n      fund(nathan);\n      graphene::app::database_api db_api(db);\n\n      try {\n         account_update_operation op;\n         op.account = nathan.id;\n         op.active = authority(2, public_key_type(nathan_key1.get_public_key()), 1,\n               public_key_type(nathan_key2.get_public_key()), 1, public_key_type(nathan_key3.get_public_key()), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         sign(trx, nathan_key1);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n      } FC_CAPTURE_AND_RETHROW ((nathan.active))\n\n      // two keys should work\n      {\n      \tflat_set<public_key_type> public_keys;\n      \tpublic_keys.emplace(nathan_key1.get_public_key());\n      \tpublic_keys.emplace(nathan_key2.get_public_key());\n      \tBOOST_CHECK(db_api.verify_account_authority(\"nathan\", public_keys));\n      }\n\n      // the other two keys should work\n      {\n     \t   flat_set<public_key_type> public_keys;\n      \tpublic_keys.emplace(nathan_key2.get_public_key());\n      \tpublic_keys.emplace(nathan_key3.get_public_key());\n     \t   BOOST_CHECK(db_api.verify_account_authority(\"nathan\", public_keys));\n      }\n\n      // just one key should not work\n      {\n     \t   flat_set<public_key_type> public_keys;\n         public_keys.emplace(nathan_key1.get_public_key());\n     \t   BOOST_CHECK(!db_api.verify_account_authority(\"nathan\", public_keys));\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( verify_authority_multiple_accounts )\n{\n   try {\n      ACTORS( (nathan) (alice) (bob) );\n\n      graphene::app::database_api db_api(db);\n\n      try {\n         account_update_operation op;\n         op.account = nathan.id;\n         op.active = authority(3, nathan_public_key, 1, alice.get_id(), 1, bob.get_id(), 1);\n         op.owner = *op.active;\n         trx.operations.push_back(op);\n         sign(trx, nathan_private_key);\n         PUSH_TX( db, trx, database::skip_transaction_dupe_check );\n         trx.clear();\n      } FC_CAPTURE_AND_RETHROW ((nathan.active))\n\n      // requires 3 signatures\n      {\n      \tflat_set<public_key_type> public_keys;\n      \tpublic_keys.emplace(nathan_public_key);\n      \tpublic_keys.emplace(alice_public_key);\n      \tpublic_keys.emplace(bob_public_key);\n      \tBOOST_CHECK(db_api.verify_account_authority(\"nathan\", public_keys));\n      }\n\n      // only 2 signatures given\n      {\n      \tflat_set<public_key_type> public_keys;\n      \tpublic_keys.emplace(nathan_public_key);\n      \tpublic_keys.emplace(bob_public_key);\n      \tBOOST_CHECK(!db_api.verify_account_authority(\"nathan\", public_keys));\n      }\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( get_assets_by_issuer ) {\n   try {\n      graphene::app::database_api db_api(db, &(this->app.get_options()));\n\n      create_bitasset(\"CNY\");\n      create_bitasset(\"EUR\");\n      create_bitasset(\"USD\");\n\n      generate_block();\n\n      auto assets = db_api.get_assets_by_issuer(\"witness-account\", asset_id_type(), 10);\n\n      BOOST_CHECK(assets.size() == 3);\n      BOOST_CHECK(assets[0].symbol == \"CNY\");\n      BOOST_CHECK(assets[1].symbol == \"EUR\");\n      BOOST_CHECK(assets[2].symbol == \"USD\");\n\n      assets = db_api.get_assets_by_issuer(\"witness-account\", asset_id_type(200), 100);\n      BOOST_CHECK(assets.size() == 0);\n\n      GRAPHENE_CHECK_THROW(db_api.get_assets_by_issuer(\"nosuchaccount\", asset_id_type(), 100), fc::exception);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( get_call_orders_by_account ) {\n\n   try {\n      ACTORS((caller)(feedproducer));\n\n      graphene::app::database_api db_api(db, &(this->app.get_options()));\n\n      const auto &usd = create_bitasset(\"USD\", feedproducer_id);\n      const auto &cny = create_bitasset(\"CNY\", feedproducer_id);\n      const auto &core = asset_id_type()(db);\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, caller_id, asset(init_balance));\n\n      update_feed_producers(usd, {feedproducer.get_id()});\n      update_feed_producers(cny, {feedproducer.get_id()});\n\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = usd.amount(1) / core.amount(5);\n      publish_feed(usd, feedproducer, current_feed);\n\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = cny.amount(1) / core.amount(5);\n      publish_feed(cny, feedproducer, current_feed);\n\n      auto call1 = borrow(caller, usd.amount(1000), asset(15000));\n      auto call2 = borrow(caller, cny.amount(1000), asset(15000));\n\n      auto calls = db_api.get_call_orders_by_account(\"caller\", asset_id_type(), 100);\n\n      BOOST_CHECK(calls.size() == 2);\n      BOOST_CHECK(calls[0].id == call1->id);\n      BOOST_CHECK(calls[1].id == call2->id);\n\n      GRAPHENE_CHECK_THROW(db_api.get_call_orders_by_account(\"nosuchaccount\", asset_id_type(), 100), fc::exception);\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( get_settle_orders_by_account ) {\n   try {\n      ACTORS((creator)(settler)(caller)(feedproducer));\n\n      graphene::app::database_api db_api(db, &(this->app.get_options()));\n\n      const auto &usd = create_bitasset(\"USD\", creator_id);\n      const auto &core = asset_id_type()(db);\n      asset_id_type usd_id = usd.get_id();\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, settler_id, asset(init_balance));\n      transfer(committee_account, caller_id, asset(init_balance));\n\n      update_feed_producers(usd, {feedproducer.get_id()});\n\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = usd.amount(1) / core.amount(5);\n      publish_feed(usd, feedproducer, current_feed);\n\n      borrow(caller, usd.amount(1000), asset(15000));\n      generate_block();\n\n      transfer(caller.get_id(), settler.get_id(), asset(200, usd_id));\n\n      auto result = force_settle( settler, usd_id(db).amount(4));\n      generate_block();\n\n      auto settlements = db_api.get_settle_orders_by_account(\"settler\", force_settlement_id_type(), 100);\n\n      BOOST_CHECK(settlements.size() == 1);\n      BOOST_CHECK(settlements[0].id == *result.get<extendable_operation_result>().value.new_objects->begin());\n\n      GRAPHENE_CHECK_THROW( db_api.get_settle_orders_by_account(\"nosuchaccount\", force_settlement_id_type(), 100),\n                            fc::exception );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( asset_in_collateral )\n{ try {\n   ACTORS( (dan)(nathan) );\n   fund( nathan );\n   fund( dan );\n\n   graphene::app::database_api db_api( db, &( app.get_options() ) );\n\n   auto oassets = db_api.get_assets( { GRAPHENE_SYMBOL } );\n   BOOST_REQUIRE( !oassets.empty() );\n   BOOST_REQUIRE( oassets[0].valid() );\n   BOOST_REQUIRE( oassets[0]->total_in_collateral.valid() );\n   BOOST_CHECK_EQUAL( 0, oassets[0]->total_in_collateral->value );\n   BOOST_CHECK( !oassets[0]->total_backing_collateral.valid() );\n\n   asset_id_type bitusd_id = create_bitasset( \"USDBIT\", nathan_id, 100, charge_market_fee ).get_id();\n   update_feed_producers( bitusd_id, { nathan_id } );\n   asset_id_type bitdan_id = create_bitasset( \"DANBIT\", dan_id, 100, charge_market_fee ).get_id();\n   update_feed_producers( bitdan_id, { nathan_id } );\n   asset_id_type btc_id = create_bitasset( \"BTC\", nathan_id, 100, charge_market_fee, 8, bitusd_id ).get_id();\n   update_feed_producers( btc_id, { nathan_id } );\n\n   oassets = db_api.get_assets( { GRAPHENE_SYMBOL, \"USDBIT\", \"DANBIT\", \"BTC\" } );\n   BOOST_REQUIRE_EQUAL( 4, oassets.size() );\n   BOOST_REQUIRE( oassets[0].valid() );\n   BOOST_REQUIRE( oassets[0]->total_in_collateral.valid() );\n   BOOST_CHECK( !oassets[0]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[1].valid() );\n   BOOST_REQUIRE( oassets[1]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[1]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[2].valid() );\n   BOOST_REQUIRE( oassets[2]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[2]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[3].valid() );\n   BOOST_REQUIRE( oassets[3]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[3]->total_backing_collateral.valid() );\n   BOOST_CHECK_EQUAL( 0, oassets[0]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[1]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[1]->total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[2]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[2]->total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[3]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[3]->total_backing_collateral->value );\n\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n\n   const auto& bitusd = bitusd_id( db );\n   const auto& bitdan = bitdan_id( db );\n   const auto& btc = btc_id( db );\n\n   {\n      const auto& core = asset_id_type()( db );\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd.amount(1) / core.amount(5);\n      publish_feed( bitusd_id, nathan_id, current_feed );\n      current_feed.settlement_price = bitdan.amount(1) / core.amount(5);\n      publish_feed( bitdan_id, nathan_id, current_feed );\n      current_feed.settlement_price = btc.amount(1) / bitusd.amount(100);\n      publish_feed( btc_id, nathan_id, current_feed );\n   }\n\n   borrow( nathan_id, bitusd.amount(1000), asset(15000) );\n   borrow( dan_id, bitusd.amount(100), asset(2000) );\n\n   oassets = db_api.get_assets( { GRAPHENE_SYMBOL, \"USDBIT\", \"DANBIT\", \"BTC\" } );\n   BOOST_REQUIRE_EQUAL( 4, oassets.size() );\n   BOOST_REQUIRE( oassets[0].valid() );\n   BOOST_REQUIRE( oassets[0]->total_in_collateral.valid() );\n   BOOST_CHECK( !oassets[0]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[1].valid() );\n   BOOST_REQUIRE( oassets[1]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[1]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[2].valid() );\n   BOOST_REQUIRE( oassets[2]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[2]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[3].valid() );\n   BOOST_REQUIRE( oassets[3]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[3]->total_backing_collateral.valid() );\n   BOOST_CHECK_EQUAL( 17000, oassets[0]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[1]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 17000, oassets[1]->total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[2]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[2]->total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[3]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[3]->total_backing_collateral->value );\n\n   borrow( nathan_id, bitdan.amount(1000), asset(15000) );\n   borrow( nathan_id, btc.amount(5), bitusd.amount(1000) );\n\n   oassets = db_api.lookup_asset_symbols( { GRAPHENE_SYMBOL, \"USDBIT\", \"DANBIT\", \"BTC\" } );\n   BOOST_REQUIRE_EQUAL( 4, oassets.size() );\n   BOOST_REQUIRE( oassets[0].valid() );\n   BOOST_REQUIRE( oassets[0]->total_in_collateral.valid() );\n   BOOST_CHECK( !oassets[0]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[1].valid() );\n   BOOST_REQUIRE( oassets[1]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[1]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[2].valid() );\n   BOOST_REQUIRE( oassets[2]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[2]->total_backing_collateral.valid() );\n   BOOST_REQUIRE( oassets[3].valid() );\n   BOOST_REQUIRE( oassets[3]->total_in_collateral.valid() );\n   BOOST_REQUIRE( oassets[3]->total_backing_collateral.valid() );\n   BOOST_CHECK_EQUAL( 32000, oassets[0]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 1000, oassets[1]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 17000, oassets[1]->total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[2]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 15000, oassets[2]->total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, oassets[3]->total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 1000, oassets[3]->total_backing_collateral->value );\n\n   force_settle( dan_id(db), bitusd.amount(100) ); // settles against nathan, receives 500 CORE collateral\n   generate_blocks( db.head_block_time() + fc::days(2) );\n   fc::usleep(fc::milliseconds(100));\n\n   auto assets = db_api.list_assets( GRAPHENE_SYMBOL, 1 );\n   BOOST_REQUIRE( !assets.empty() );\n   BOOST_REQUIRE( assets[0].total_in_collateral.valid() );\n   BOOST_CHECK( !assets[0].total_backing_collateral.valid() );\n   BOOST_CHECK_EQUAL( 31500, assets[0].total_in_collateral->value );\n\n   assets = db_api.get_assets_by_issuer( \"nathan\", asset_id_type(1), 2 );\n   BOOST_REQUIRE_EQUAL( 2, assets.size() );\n   BOOST_REQUIRE( assets[0].total_in_collateral.valid() );\n   BOOST_REQUIRE( assets[0].total_backing_collateral.valid() );\n   BOOST_REQUIRE( assets[1].total_in_collateral.valid() );\n   BOOST_REQUIRE( assets[1].total_backing_collateral.valid() );\n   BOOST_CHECK_EQUAL( 1000, assets[0].total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 16500, assets[0].total_backing_collateral->value );\n   BOOST_CHECK_EQUAL( 0, assets[1].total_in_collateral->value );\n   BOOST_CHECK_EQUAL( 1000, assets[1].total_backing_collateral->value );\n\n} FC_LOG_AND_RETHROW() }\n\n\nBOOST_AUTO_TEST_CASE( get_trade_history )\n{ try {\n\n   app.enable_plugin(\"market_history\");\n   graphene::app::application_options opt=app.get_options();\n   opt.has_market_history_plugin = true;\n   graphene::app::database_api db_api( db, &opt);\n\n   // check get_next_object_id\n   auto next_id = db_api.get_next_object_id( MARKET_HISTORY_SPACE_ID,\n                                             graphene::market_history::order_history_object_type,\n                                             false );\n   BOOST_CHECK( std::string(next_id) == \"5.0.0\" );\n   next_id = db_api.get_next_object_id( MARKET_HISTORY_SPACE_ID,\n                                        graphene::market_history::order_history_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"5.0.0\" );\n\n   ACTORS((bob)(alice));\n\n   const auto& eur = create_user_issued_asset(\"EUR\");\n   asset_id_type eur_id = eur.get_id();\n   const auto& usd = create_user_issued_asset(\"USD\");\n   asset_id_type usd_id = usd.get_id();\n\n   issue_uia( bob_id, usd.amount(1000000) );\n   issue_uia( alice_id, eur.amount(1000000) );\n\n   // maker create an order\n   create_sell_order(bob, usd.amount(200), eur.amount(210));\n\n   // btw check get_next_object_id\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"1.7.0\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"1.7.1\" );\n\n   // taker match it\n   create_sell_order(alice, eur.amount(210), usd.amount(200));\n\n   // btw check get_next_object_id\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"1.7.0\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"1.7.2\" );\n\n   generate_block();\n\n   // taker is selling\n   auto history = db_api.get_trade_history( \"EUR\", \"USD\", db.head_block_time(), db.head_block_time() - fc::days(1) );\n   BOOST_REQUIRE_EQUAL( 1, history.size() );\n   BOOST_CHECK_EQUAL( \"1.05\", history[0].price );\n   BOOST_CHECK_EQUAL( \"2\", history[0].amount );\n   BOOST_CHECK_EQUAL( \"2.10\", history[0].value );\n   BOOST_CHECK_EQUAL( \"sell\", history[0].type );\n   BOOST_CHECK_EQUAL( bob_id.instance.value, history[0].side1_account_id.instance.value );\n   BOOST_CHECK_EQUAL( alice_id.instance.value, history[0].side2_account_id.instance.value );\n\n   // opposite side, taker is buying\n   history = db_api.get_trade_history( \"USD\", \"EUR\", db.head_block_time(), db.head_block_time() - fc::days(1) );\n   BOOST_REQUIRE_EQUAL( 1, history.size() );\n   BOOST_CHECK_EQUAL( \"0.9523809523809523809\", history[0].price );\n   BOOST_CHECK_EQUAL( \"2.10\", history[0].amount );\n   BOOST_CHECK_EQUAL( \"2\", history[0].value );\n   BOOST_CHECK_EQUAL( \"buy\", history[0].type );\n   BOOST_CHECK_EQUAL( bob_id.instance.value, history[0].side1_account_id.instance.value );\n   BOOST_CHECK_EQUAL( alice_id.instance.value, history[0].side2_account_id.instance.value );\n\n   // by sequence\n   history = db_api.get_trade_history_by_sequence( \"EUR\", \"USD\", 2, db.head_block_time() - fc::days(1) );\n   BOOST_CHECK_EQUAL( \"1.05\", history[0].price );\n   BOOST_CHECK_EQUAL( \"2\", history[0].amount );\n   BOOST_CHECK_EQUAL( \"2.10\", history[0].value );\n   BOOST_CHECK_EQUAL( \"sell\", history[0].type );\n   BOOST_CHECK_EQUAL( bob_id.instance.value, history[0].side1_account_id.instance.value );\n   BOOST_CHECK_EQUAL( alice_id.instance.value, history[0].side2_account_id.instance.value );\n\n   // opposite side\n   history = db_api.get_trade_history_by_sequence( \"USD\", \"EUR\", 2, db.head_block_time() - fc::days(1) );\n   BOOST_REQUIRE_EQUAL( 1, history.size() );\n   BOOST_CHECK_EQUAL( \"0.9523809523809523809\", history[0].price );\n   BOOST_CHECK_EQUAL( \"2.10\", history[0].amount );\n   BOOST_CHECK_EQUAL( \"2\", history[0].value );\n   BOOST_CHECK_EQUAL( \"buy\", history[0].type );\n   BOOST_CHECK_EQUAL( bob_id.instance.value, history[0].side1_account_id.instance.value );\n   BOOST_CHECK_EQUAL( alice_id.instance.value, history[0].side2_account_id.instance.value );\n\n   // check get_next_object_id\n   next_id = db_api.get_next_object_id( MARKET_HISTORY_SPACE_ID,\n                                        graphene::market_history::order_history_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"5.0.2\" );\n   next_id = db_api.get_next_object_id( MARKET_HISTORY_SPACE_ID,\n                                        graphene::market_history::order_history_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"5.0.2\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"1.7.2\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"1.7.2\" );\n   // maker create an order\n   create_sell_order(bob, asset(200,usd_id), asset(210,eur_id));\n   // check get_next_object_id\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"1.7.2\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"1.7.3\" );\n   BOOST_CHECK_THROW( db_api.get_next_object_id( 1,100,true ), fc::exception );\n   BOOST_CHECK_THROW( db_api.get_next_object_id( 10,0,false ), fc::exception );\n\n   generate_block();\n   // check get_next_object_id\n   next_id = db_api.get_next_object_id( MARKET_HISTORY_SPACE_ID,\n                                        graphene::market_history::order_history_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"5.0.2\" );\n   next_id = db_api.get_next_object_id( MARKET_HISTORY_SPACE_ID,\n                                        graphene::market_history::order_history_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"5.0.2\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        false );\n   BOOST_CHECK( std::string(next_id) == \"1.7.3\" );\n   next_id = db_api.get_next_object_id( protocol_ids,\n                                        limit_order_object_type,\n                                        true );\n   BOOST_CHECK( std::string(next_id) == \"1.7.3\" );\n\n} FC_LOG_AND_RETHROW() }\n\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/database_tests.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\n\nBOOST_FIXTURE_TEST_SUITE( database_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( undo_test )\n{\n   try {\n      database db;\n      auto ses = db._undo_db.start_undo_session();\n      const auto& bal_obj1 = db.create<account_balance_object>( [&]( account_balance_object& obj ){\n               /* no balances right now */\n      });\n      auto id1 = bal_obj1.id;\n      // abandon changes\n      ses.undo();\n      // start a new session\n      ses = db._undo_db.start_undo_session();\n\n      const auto& bal_obj2 = db.create<account_balance_object>( [&]( account_balance_object& obj ){\n               /* no balances right now */\n      });\n      auto id2 = bal_obj2.id;\n      BOOST_CHECK( id1 == id2 );\n   } catch ( const fc::exception& e )\n   {\n      edump( (e.to_detail_string()) );\n      throw;\n   }\n}\n\n/**\n * Check that database modify() functors that throw do not get caught by boost, which will remove the object\n */\nBOOST_AUTO_TEST_CASE(failed_modify_test)\n{ try {\n   database db;\n   // Create dummy object\n   const auto& obj = db.create<account_balance_object>([](account_balance_object& obj) {\n                     obj.owner = account_id_type(123);\n                  });\n   account_balance_id_type obj_id { obj.id };\n   BOOST_CHECK_EQUAL(obj.owner.instance.value, 123u);\n\n   // Modify dummy object, check that changes stick\n   db.modify(obj, [](account_balance_object& obj) {\n      obj.owner = account_id_type(234);\n   });\n   BOOST_CHECK_EQUAL(obj_id(db).owner.instance.value, 234u);\n\n   // Throw exception when modifying object, check that object still exists after\n   BOOST_CHECK_THROW(db.modify(obj, [](account_balance_object& obj) {\n      throw 5;\n   }), int);\n   BOOST_CHECK(db.find(obj_id));\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( flat_index_test )\n{ try {\n   ACTORS((sam));\n   const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id());\n   const asset_id_type bitusd_id = bitusd.get_id();\n   update_feed_producers(bitusd, {sam.get_id()});\n   price_feed current_feed;\n   current_feed.settlement_price = bitusd.amount(100) / asset(100);\n   publish_feed(bitusd, sam, current_feed);\n   BOOST_CHECK_EQUAL( (int)bitusd.bitasset_data_id->instance, 1 );\n   BOOST_CHECK( !(*bitusd.bitasset_data_id)(db).current_feed.settlement_price.is_null() );\n   try {\n      auto ses = db._undo_db.start_undo_session();\n      const auto& obj1 = db.create<asset_bitasset_data_object>( [&]( asset_bitasset_data_object& obj ){\n          obj.settlement_fund = 17;\n      });\n      BOOST_REQUIRE_EQUAL( obj1.settlement_fund.value, 17 );\n      throw std::string(\"Expected\");\n      // With flat_index, obj1 will not really be removed from the index\n   } catch ( const std::string& e )\n   { // ignore\n   }\n\n   // force maintenance\n   const auto& dynamic_global_props = db.get(dynamic_global_property_id_type());\n   generate_blocks(dynamic_global_props.next_maintenance_time, true);\n\n   BOOST_CHECK( !(*bitusd_id(db).bitasset_data_id)(db).current_feed.settlement_price.is_null() );\n} FC_CAPTURE_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( merge_test )\n{\n   try {\n      database db;\n      auto ses = db._undo_db.start_undo_session();\n      db.create<account_balance_object>( [&]( account_balance_object& obj ){\n          obj.balance = 42;\n      });\n      ses.merge();\n\n      auto balance = db.get_balance( account_id_type(), asset_id_type() );\n      BOOST_CHECK_EQUAL( 42, balance.amount.value );\n   } catch ( const fc::exception& e )\n   {\n      edump( (e.to_detail_string()) );\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( direct_index_test )\n{ try {\n   try {\n      const graphene::db::primary_index< account_index, 6 > small_chunkbits( db );\n      BOOST_FAIL( \"Expected assertion failure!\" );\n   } catch( const fc::assert_exception& expected ) {}\n\n   graphene::db::primary_index< account_index, 8 > my_accounts( db );\n   const auto& direct = my_accounts.get_secondary_index<graphene::db::direct_index< account_object, 8 >>();\n   BOOST_CHECK_EQUAL( 0u, my_accounts.indices().size() );\n   BOOST_CHECK( nullptr == direct.find( account_id_type( 1 ) ) );\n   // BOOST_CHECK_THROW( direct.find( asset_id_type( 1 ) ), fc::assert_exception ); // compile-time error\n   BOOST_CHECK_THROW( direct.find( object_id_type( asset_id_type( 1 ) ) ), fc::assert_exception );\n   BOOST_CHECK_THROW( direct.get( account_id_type( 1 ) ), fc::assert_exception );\n\n   account_object test_account;\n   test_account.id = object_id_type( account_id_type(1) );\n   test_account.name = \"account1\";\n\n   my_accounts.load( fc::raw::pack( test_account ) );\n\n   BOOST_CHECK_EQUAL( 1u, my_accounts.indices().size() );\n   BOOST_CHECK( nullptr == direct.find( account_id_type( 0 ) ) );\n   BOOST_CHECK( nullptr == direct.find( account_id_type( 2 ) ) );\n   BOOST_CHECK( nullptr != direct.find( account_id_type( 1 ) ) );\n   BOOST_CHECK_EQUAL( test_account.name, direct.get( test_account.id ).name );\n\n   // The following assumes that MAX_HOLE = 100\n   test_account.id = object_id_type( account_id_type(102) );\n   test_account.name = \"account102\";\n   // highest insert was 1, direct.next is 2 => 102 is highest allowed instance\n   my_accounts.load( fc::raw::pack( test_account ) );\n   BOOST_CHECK_EQUAL( test_account.name, direct.get( test_account.id ).name );\n\n   // direct.next is now 103, but index sequence counter is 0\n   my_accounts.create( [] ( object& o ) {\n       account_object& acct = dynamic_cast< account_object& >( o );\n       BOOST_CHECK_EQUAL( 0u, acct.id.instance() );\n       acct.name = \"account0\";\n   } );\n\n   test_account.id = object_id_type( account_id_type(50) );\n   test_account.name = \"account50\";\n   my_accounts.load( fc::raw::pack( test_account ) );\n\n   // can handle nested modification\n   my_accounts.modify( direct.get( account_id_type(0) ), [&direct,&my_accounts] ( object& outer ) {\n      account_object& _outer = dynamic_cast< account_object& >( outer );\n      my_accounts.modify( direct.get( account_id_type(50) ), [] ( object& inner ) {\n         account_object& _inner = dynamic_cast< account_object& >( inner );\n         _inner.referrer = account_id_type(102);\n      });\n      _outer.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n   });\n\n   // direct.next is still 103, so 204 is not allowed\n   test_account.id = object_id_type( account_id_type(204) );\n   test_account.name = \"account204\";\n   GRAPHENE_REQUIRE_THROW( my_accounts.load( fc::raw::pack( test_account ) ), fc::assert_exception );\n   // This is actually undefined behaviour. The object has been inserted into\n   // the primary index, but the secondary has refused to insert it!\n   BOOST_CHECK_EQUAL( 5u, my_accounts.indices().size() );\n\n   uint32_t count = 0;\n   for( uint32_t i = 0; i < 250; i++ )\n   {\n      const account_object* aptr = dynamic_cast< const account_object* >(\n                                      my_accounts.find( object_id_type( account_id_type( i ) ) ) );\n      if( aptr )\n      {\n         count++;\n         BOOST_CHECK( aptr->id.instance() == 0 || aptr->id.instance() == 1\n                      || aptr->id.instance() == 50 || aptr->id.instance() == 102 );\n         BOOST_CHECK_EQUAL( i, aptr->id.instance() );\n         BOOST_CHECK_EQUAL( \"account\" + std::to_string( i ), aptr->name );\n      }\n   }\n   BOOST_CHECK_EQUAL( count, my_accounts.indices().size() - 1 );\n\n   GRAPHENE_REQUIRE_THROW( my_accounts.modify( direct.get( account_id_type( 1 ) ), [] ( object& acct ) {\n      acct.id = object_id_type( account_id_type(2) );\n   }), fc::assert_exception );\n   // This is actually undefined behaviour. The object has been modified, but\n   // but the secondary has not updated its representation\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( required_approval_index_test ) // see https://github.com/bitshares/bitshares-core/issues/1719\n{ try {\n   ACTORS( (alice)(bob)(charlie)(agnetha)(benny)(carlos) );\n\n   database db1;\n   db1.initialize_indexes();\n   const auto& required_approvals = db1.add_secondary_index<primary_index<proposal_index>, required_approval_index>()\n                                       ->_account_to_proposals;\n\n   // Create a proposal\n   const auto& prop = db1.create<proposal_object>( [this,alice_id,agnetha_id]( object& o ) {\n      proposal_object& prop = static_cast<proposal_object&>(o);\n      prop.proposer = committee_account;\n      prop.required_active_approvals.insert( alice_id );\n      prop.required_owner_approvals.insert( agnetha_id );\n   });\n\n   BOOST_CHECK_EQUAL( 2u, required_approvals.size() );\n   BOOST_REQUIRE( required_approvals.find( alice_id )   != required_approvals.end() );\n   BOOST_REQUIRE( required_approvals.find( agnetha_id ) != required_approvals.end() );\n   BOOST_CHECK_EQUAL( 1u, required_approvals.find(alice_id)->second.size() );\n   BOOST_CHECK_EQUAL( 1u, required_approvals.find(agnetha_id)->second.size() );\n\n   // add approvals\n   db1.modify( prop, [bob_id,benny_id]( object& o ) {\n      proposal_object& prop = static_cast<proposal_object&>(o);\n      prop.available_active_approvals.insert( bob_id );\n      prop.available_owner_approvals.insert( benny_id );\n   });\n\n   BOOST_CHECK_EQUAL( 4u, required_approvals.size() );\n   BOOST_REQUIRE( required_approvals.find( bob_id )   != required_approvals.end() );\n   BOOST_REQUIRE( required_approvals.find( benny_id ) != required_approvals.end() );\n   BOOST_CHECK_EQUAL( 1u, required_approvals.find(bob_id)->second.size() );\n   BOOST_CHECK_EQUAL( 1u, required_approvals.find(benny_id)->second.size() );\n\n   // remove approvals + add others\n   db1.modify( prop, [bob_id,charlie_id,benny_id,carlos_id]( object& o ) {\n      proposal_object& prop = static_cast<proposal_object&>(o);\n      prop.available_active_approvals.insert( charlie_id );\n      prop.available_owner_approvals.insert( carlos_id );\n      prop.available_active_approvals.erase( bob_id );\n      prop.available_owner_approvals.erase( benny_id );\n   });\n\n   BOOST_CHECK_EQUAL( 4u, required_approvals.size() );\n   BOOST_REQUIRE( required_approvals.find( charlie_id ) != required_approvals.end() );\n   BOOST_REQUIRE( required_approvals.find( carlos_id )  != required_approvals.end() );\n   BOOST_CHECK_EQUAL( 1u, required_approvals.find(charlie_id)->second.size() );\n   BOOST_CHECK_EQUAL( 1u, required_approvals.find(carlos_id)->second.size() );\n\n   // simulate save/restore\n   std::vector<char> serialized = fc::raw::pack( prop );\n   database db2;\n   db2.initialize_indexes();\n   const auto& reloaded_proposals = db2.get_index_type< primary_index< proposal_index > >();\n   const auto& reloaded_approvals = db2.add_secondary_index<primary_index<proposal_index>, required_approval_index>()\n                                       ->_account_to_proposals;\n   const_cast< primary_index< proposal_index >& >( reloaded_proposals ).load( serialized );\n   const auto& prop2 = *reloaded_proposals.indices().begin();\n\n   BOOST_CHECK_EQUAL( 4u, reloaded_approvals.size() );\n   BOOST_REQUIRE( reloaded_approvals.find( charlie_id ) != reloaded_approvals.end() );\n   BOOST_REQUIRE( reloaded_approvals.find( carlos_id )  != reloaded_approvals.end() );\n   BOOST_CHECK_EQUAL( 1u, reloaded_approvals.find(charlie_id)->second.size() );\n   BOOST_CHECK_EQUAL( 1u, reloaded_approvals.find(carlos_id)->second.size() );\n\n   db2.modify( prop2, []( object& o ) {\n      proposal_object& prop = static_cast<proposal_object&>(o);\n      prop.available_active_approvals.clear();\n      prop.available_owner_approvals.clear();\n   });\n\n   BOOST_CHECK_EQUAL( 2u, reloaded_approvals.size() );\n   BOOST_REQUIRE( reloaded_approvals.find( alice_id )   != reloaded_approvals.end() );\n   BOOST_REQUIRE( reloaded_approvals.find( agnetha_id ) != reloaded_approvals.end() );\n\n   db2.remove( prop2 );\n\n   BOOST_CHECK_EQUAL( 0u, reloaded_approvals.size() );\n\n   db1.remove( prop );\n\n   BOOST_CHECK_EQUAL( 0u, required_approvals.size() );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/fee_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/fba_accumulator_id.hpp>\n\n#include <graphene/chain/fba_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/exceptions.hpp>\n\n#include <fc/uint128.hpp>\n\n#include <boost/test/unit_test.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( fee_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( nonzero_fee_test )\n{\n   try\n   {\n      ACTORS((alice)(bob));\n\n      const share_type prec = asset::scaled_precision( asset_id_type()(db).precision );\n\n      // Return number of core shares (times precision)\n      auto _core = [&]( int64_t x ) -> asset\n      {  return asset( x*prec );    };\n\n      transfer( committee_account, alice_id, _core(1000000) );\n\n      // make sure the database requires our fee to be nonzero\n      enable_fees();\n\n      signed_transaction tx;\n      transfer_operation xfer_op;\n      xfer_op.from = alice_id;\n      xfer_op.to = bob_id;\n      xfer_op.amount = _core(1000);\n      xfer_op.fee = _core(0);\n      tx.operations.push_back( xfer_op );\n      set_expiration( db, tx );\n      sign( tx, alice_private_key );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx ), insufficient_fee );\n   }\n   catch( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(asset_claim_fees_test)\n{\n   try\n   {\n      ACTORS((alice)(bob)(izzy)(jill));\n      // Izzy issues asset to Alice\n      // Jill issues asset to Bob\n      // Alice and Bob trade in the market and pay fees\n      // Verify that Izzy and Jill can claim the fees\n\n      const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision );\n\n      // Return number of core shares (times precision)\n      auto _core = [&]( int64_t x ) -> asset\n      {  return asset( x*core_prec );    };\n\n      transfer( committee_account, alice_id, _core(1000000) );\n      transfer( committee_account,   bob_id, _core(1000000) );\n      transfer( committee_account,  izzy_id, _core(1000000) );\n      transfer( committee_account,  jill_id, _core(1000000) );\n\n      asset_id_type izzycoin_id = create_bitasset( \"IZZYCOIN\", izzy_id,   GRAPHENE_1_PERCENT, charge_market_fee )\n                                     .get_id();\n      asset_id_type jillcoin_id = create_bitasset( \"JILLCOIN\", jill_id, 2*GRAPHENE_1_PERCENT, charge_market_fee )\n                                     .get_id();\n\n      const share_type izzy_prec = asset::scaled_precision( asset_id_type(izzycoin_id)(db).precision );\n      const share_type jill_prec = asset::scaled_precision( asset_id_type(jillcoin_id)(db).precision );\n\n      auto _izzy = [&]( int64_t x ) -> asset\n      {   return asset( x*izzy_prec, izzycoin_id );   };\n      auto _jill = [&]( int64_t x ) -> asset\n      {   return asset( x*jill_prec, jillcoin_id );   };\n\n      update_feed_producers( izzycoin_id(db), { izzy_id } );\n      update_feed_producers( jillcoin_id(db), { jill_id } );\n\n      const asset izzy_satoshi = asset(1, izzycoin_id);\n      const asset jill_satoshi = asset(1, jillcoin_id);\n\n      // Izzycoin is worth 100 BTS\n      price_feed feed;\n      feed.settlement_price = price( _izzy(1), _core(100) );\n      feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      publish_feed( izzycoin_id(db), izzy, feed );\n\n      // Jillcoin is worth 30 BTS\n      feed.settlement_price = price( _jill(1), _core(30) );\n      feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      publish_feed( jillcoin_id(db), jill, feed );\n\n      enable_fees();\n\n      // Alice and Bob create some coins\n      borrow( alice_id, _izzy( 200), _core( 60000) );\n      borrow(   bob_id, _jill(2000), _core(180000) );\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice_id, _izzy(100), _jill(300) );   // Alice is willing to sell her Izzy's for 3 Jill\n      create_sell_order(   bob_id, _jill(700), _izzy(200) );   // Bob is buying up to 200 Izzy's for up to 3.5 Jill\n\n      // 100 Izzys and 300 Jills are matched, so the fees should be\n      //   1 Izzy (1%) and 6 Jill (2%).\n\n      auto claim_fees = [&]( account_id_type issuer, asset amount_to_claim )\n      {\n         asset_claim_fees_operation claim_op;\n         claim_op.issuer = issuer;\n         claim_op.amount_to_claim = amount_to_claim;\n         signed_transaction tx;\n         tx.operations.push_back( claim_op );\n         db.current_fee_schedule().set_fee( tx.operations.back() );\n         set_expiration( db, tx );\n         fc::ecc::private_key   my_pk = (issuer == izzy_id) ? izzy_private_key : jill_private_key;\n         fc::ecc::private_key your_pk = (issuer == izzy_id) ? jill_private_key : izzy_private_key;\n         sign( tx, your_pk );\n         GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx ), fc::exception );\n         tx.clear_signatures();\n         sign( tx, my_pk );\n         PUSH_TX( db, tx );\n      };\n\n      {\n         const asset_object& izzycoin = izzycoin_id(db);\n         const asset_object& jillcoin = jillcoin_id(db);\n\n         //wdump( (izzycoin)(izzycoin.dynamic_asset_data_id(db))((*izzycoin.bitasset_data_id)(db)) );\n         //wdump( (jillcoin)(jillcoin.dynamic_asset_data_id(db))((*jillcoin.bitasset_data_id)(db)) );\n\n         // check the correct amount of fees has been awarded\n         BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == _izzy(1).amount );\n         BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == _jill(6).amount );\n\n      }\n\n      {\n         const asset_object& izzycoin = izzycoin_id(db);\n         const asset_object& jillcoin = jillcoin_id(db);\n\n         // can't claim more than balance\n         GRAPHENE_REQUIRE_THROW( claim_fees( izzy_id, _izzy(1) + izzy_satoshi ), fc::exception );\n         GRAPHENE_REQUIRE_THROW( claim_fees( jill_id, _jill(6) + jill_satoshi ), fc::exception );\n\n         // can't claim asset that doesn't belong to you\n         GRAPHENE_REQUIRE_THROW( claim_fees( jill_id, izzy_satoshi ), fc::exception );\n         GRAPHENE_REQUIRE_THROW( claim_fees( izzy_id, jill_satoshi ), fc::exception );\n\n         // can claim asset in one go\n         claim_fees( izzy_id, _izzy(1) );\n         GRAPHENE_REQUIRE_THROW( claim_fees( izzy_id, izzy_satoshi ), fc::exception );\n         BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == _izzy(0).amount );\n\n         // can claim in multiple goes\n         claim_fees( jill_id, _jill(4) );\n         BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == _jill(2).amount );\n         GRAPHENE_REQUIRE_THROW( claim_fees( jill_id, _jill(2) + jill_satoshi ), fc::exception );\n         claim_fees( jill_id, _jill(2) );\n         BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == _jill(0).amount );\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(asset_claim_pool_test)\n{\n    try\n    {\n        ACTORS((alice)(bob));\n        // Alice and Bob create some user issued assets\n        // Alice deposits BTS to the fee pool\n        // Alice claimes fee pool of her asset and can't claim pool of Bob's asset\n\n        const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision );\n\n        // return number of core shares (times precision)\n        auto _core = [&core_prec]( int64_t x ) -> asset\n        {  return asset( x*core_prec );    };\n\n        const asset_object& alicecoin = create_user_issued_asset( \"ALICECOIN\", alice,  0 );\n        const asset_object& aliceusd = create_user_issued_asset( \"ALICEUSD\", alice, 0 );\n\n        asset_id_type alicecoin_id = alicecoin.get_id();\n        asset_id_type aliceusd_id = aliceusd.get_id();\n        asset_id_type bobcoin_id = create_user_issued_asset( \"BOBCOIN\", bob, 0).get_id();\n\n        // prepare users' balance\n        issue_uia( alice, aliceusd.amount( 20000000 ) );\n        issue_uia( alice, alicecoin.amount( 10000000 ) );\n\n        transfer( committee_account, alice_id, _core(1000) );\n        transfer( committee_account, bob_id, _core(1000) );\n\n        enable_fees();\n\n        auto claim_pool = [&]( const account_id_type issuer, const asset_id_type asset_to_claim,\n                              const asset& amount_to_fund, const asset_object& fee_asset  )\n        {\n            asset_claim_pool_operation claim_op;\n            claim_op.issuer = issuer;\n            claim_op.asset_id = asset_to_claim;\n            claim_op.amount_to_claim = amount_to_fund;\n\n            signed_transaction tx;\n            tx.operations.push_back( claim_op );\n            db.current_fee_schedule().set_fee( tx.operations.back(), fee_asset.options.core_exchange_rate );\n            set_expiration( db, tx );\n            sign( tx, alice_private_key );\n            PUSH_TX( db, tx );\n\n        };\n\n        auto claim_pool_proposal = [&]( const account_id_type issuer, const asset_id_type asset_to_claim,\n                                        const asset& amount_to_fund, const asset_object& fee_asset  )\n        {\n            asset_claim_pool_operation claim_op;\n            claim_op.issuer = issuer;\n            claim_op.asset_id = asset_to_claim;\n            claim_op.amount_to_claim = amount_to_fund;\n\n            const auto& curfees = db.get_global_properties().parameters.get_current_fees();\n            const auto& proposal_create_fees = curfees.get<proposal_create_operation>();\n            proposal_create_operation prop;\n            prop.fee_paying_account = alice_id;\n            prop.proposed_ops.emplace_back( claim_op );\n            prop.expiration_time =  db.head_block_time() + fc::days(1);\n            prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n\n            signed_transaction tx;\n            tx.operations.push_back( prop );\n            db.current_fee_schedule().set_fee( tx.operations.back(), fee_asset.options.core_exchange_rate );\n            set_expiration( db, tx );\n            sign( tx, alice_private_key );\n            PUSH_TX( db, tx );\n\n        };\n\n        // deposit 100 BTS to the fee pool of ALICEUSD asset\n        fund_fee_pool( alice_id(db), aliceusd_id(db), _core(100).amount );\n\n        // New reference for core_asset after having produced blocks\n        const asset_object& core_asset_hf = asset_id_type()(db);\n\n        // can't claim pool because it is empty\n        GRAPHENE_REQUIRE_THROW( claim_pool( alice_id, alicecoin_id, _core(1), core_asset_hf), fc::exception );\n\n        // deposit 300 BTS to the fee pool of ALICECOIN asset\n        fund_fee_pool( alice_id(db), alicecoin_id(db), _core(300).amount );\n\n        // Test amount of CORE in fee pools\n        BOOST_CHECK( alicecoin_id(db).dynamic_asset_data_id(db).fee_pool == _core(300).amount );\n        BOOST_CHECK( aliceusd_id(db).dynamic_asset_data_id(db).fee_pool == _core(100).amount );\n\n        // can't claim pool of an asset that doesn't belong to you\n        GRAPHENE_REQUIRE_THROW( claim_pool( alice_id, bobcoin_id, _core(200), core_asset_hf), fc::exception );\n\n        // can't claim more than is available in the fee pool\n        GRAPHENE_REQUIRE_THROW( claim_pool( alice_id, alicecoin_id, _core(400), core_asset_hf ), fc::exception );\n\n        // can't pay fee in the same asset whose pool is being drained\n        GRAPHENE_REQUIRE_THROW( claim_pool( alice_id, alicecoin_id, _core(200), alicecoin_id(db) ), fc::exception );\n\n        // can claim BTS back from the fee pool\n        claim_pool( alice_id, alicecoin_id, _core(200), core_asset_hf );\n        BOOST_CHECK( alicecoin_id(db).dynamic_asset_data_id(db).fee_pool == _core(100).amount );\n\n        // can pay fee in the asset other than the one whose pool is being drained\n        share_type balance_before_claim = get_balance( alice_id, asset_id_type() );\n        claim_pool( alice_id, alicecoin_id, _core(100), aliceusd_id(db) );\n        BOOST_CHECK( alicecoin_id(db).dynamic_asset_data_id(db).fee_pool == _core(0).amount );\n\n        //check balance after claiming pool\n        share_type current_balance = get_balance( alice_id, asset_id_type() );\n        BOOST_CHECK( balance_before_claim + _core(100).amount == current_balance );\n\n        // can create a proposal to claim claim pool after hard fork\n        claim_pool_proposal( alice_id, aliceusd_id, _core(1), core_asset_hf);\n    }\n    FC_LOG_AND_RETHROW()\n}\n\n///////////////////////////////////////////////////////////////\n// cashback_test infrastructure                              //\n///////////////////////////////////////////////////////////////\n\n#define CHECK_BALANCE( actor_name, amount ) \\\n   BOOST_CHECK_EQUAL( get_balance( actor_name ## _id, asset_id_type() ), amount )\n\n#define CHECK_VESTED_CASHBACK( actor_name, amount ) \\\n   BOOST_CHECK_EQUAL( actor_name ## _id(db).statistics(db).pending_vested_fees.value, amount )\n\n#define CHECK_UNVESTED_CASHBACK( actor_name, amount ) \\\n   BOOST_CHECK_EQUAL( actor_name ## _id(db).statistics(db).pending_fees.value, amount )\n\n#define GET_CASHBACK_BALANCE( account ) \\\n   ( (account.cashback_vb.valid()) \\\n   ? account.cashback_balance(db).balance.amount.value \\\n   : 0 )\n\n#define CHECK_CASHBACK_VBO( actor_name, _amount ) \\\n   BOOST_CHECK_EQUAL( GET_CASHBACK_BALANCE( actor_name ## _id(db) ), _amount )\n\n#define P100 GRAPHENE_100_PERCENT\n#define P1 GRAPHENE_1_PERCENT\n\nuint64_t pct( uint64_t percentage, uint64_t val )\n{\n   fc::uint128_t x = percentage;\n   x *= val;\n   x /= GRAPHENE_100_PERCENT;\n   return static_cast<uint64_t>(x);\n}\n\nuint64_t pct( uint64_t percentage0, uint64_t percentage1, uint64_t val )\n{\n   return pct( percentage1, pct( percentage0, val ) );\n}\n\nuint64_t pct( uint64_t percentage0, uint64_t percentage1, uint64_t percentage2, uint64_t val )\n{\n   return pct( percentage2, pct( percentage1, pct( percentage0, val ) ) );\n}\n\nstruct actor_audit\n{\n   int64_t b0 = 0;      // starting balance parameter\n   int64_t bal = 0;     // balance should be this\n   int64_t ubal = 0;    // unvested balance (in VBO) should be this\n   int64_t ucb = 0;     // unvested cashback in account_statistics should be this\n   int64_t vcb = 0;     // vested cashback in account_statistics should be this\n   int64_t ref_pct = 0; // referrer percentage should be this\n};\n\nBOOST_AUTO_TEST_CASE( cashback_test )\n{ try {\n   /*                        Account Structure used in this test                         *\n    *                                                                                    *\n    *               /-----------------\\       /-------------------\\                      *\n    *               | life (Lifetime) |       |  rog (Lifetime)   |                      *\n    *               \\-----------------/       \\-------------------/                      *\n    *                  | Ref&Reg    | Refers     | Registers  | Registers                *\n    *                  |            | 75         | 25         |                          *\n    *                  v            v            v            |                          *\n    *  /----------------\\         /----------------\\          |                          *\n    *  |  ann (Annual)  |         |  dumy (basic)  |          |                          *\n    *  \\----------------/         \\----------------/          |-------------.            *\n    * 80 | Refers      L--------------------------------.     |             |            *\n    *    v                     Refers                80 v     v 20          |            *\n    *  /----------------\\                         /----------------\\        |            *\n    *  |  scud (basic)  |<------------------------|  stud (basic)  |        |            *\n    *  \\----------------/ 20   Registers          | (Upgrades to   |        | 5          *\n    *                                             |   Lifetime)    |        v            *\n    *                                             \\----------------/   /--------------\\  *\n    *                                                         L------->| pleb (Basic) |  *\n    *                                                       95 Refers  \\--------------/  *\n    *                                                                                    *\n    * Fee distribution chains (80-20 referral/net split, 50-30 referrer/LTM split)       *\n    * life : 80% -> life, 20% -> net                                                     *\n    * rog: 80% -> rog, 20% -> net                                                        *\n    * ann (before upg): 80% -> life, 20% -> net                                          *\n    * ann (after upg): 80% * 5/8 -> ann, 80% * 3/8 -> life, 20% -> net                   *\n    * stud (before upg): 80% * 5/8 -> ann, 80% * 3/8 -> life, 20% * 80% -> rog,          *\n    *                    20% -> net                                                      *\n    * stud (after upg): 80% -> stud, 20% -> net                                          *\n    * dumy : 75% * 80% -> life, 25% * 80% -> rog, 20% -> net                             *\n    * scud : 80% * 5/8 -> ann, 80% * 3/8 -> life, 20% * 80% -> stud, 20% -> net          *\n    * pleb : 95% * 80% -> stud, 5% * 80% -> rog, 20% -> net                              *\n    */\n\n   BOOST_TEST_MESSAGE(\"Creating actors\");\n\n   ACTOR(life);\n   ACTOR(rog);\n   PREP_ACTOR(ann);\n   PREP_ACTOR(scud);\n   PREP_ACTOR(dumy);\n   PREP_ACTOR(stud);\n   PREP_ACTOR(pleb);\n   // use ##_public_key vars to silence unused variable warning\n   BOOST_CHECK_GT(ann_public_key.key_data.size(), 0u);\n   BOOST_CHECK_GT(scud_public_key.key_data.size(), 0u);\n   BOOST_CHECK_GT(dumy_public_key.key_data.size(), 0u);\n   BOOST_CHECK_GT(stud_public_key.key_data.size(), 0u);\n   BOOST_CHECK_GT(pleb_public_key.key_data.size(), 0u);\n\n   account_id_type ann_id, scud_id, dumy_id, stud_id, pleb_id;\n   actor_audit alife, arog, aann, ascud, adumy, astud, apleb;\n\n   alife.b0 = 100000000;\n   arog.b0 = 100000000;\n   aann.b0 = 1000000;\n   astud.b0 = 1000000;\n   astud.ref_pct = 80 * GRAPHENE_1_PERCENT;\n   ascud.ref_pct = 80 * GRAPHENE_1_PERCENT;\n   adumy.ref_pct = 75 * GRAPHENE_1_PERCENT;\n   apleb.ref_pct = 95 * GRAPHENE_1_PERCENT;\n\n   transfer(account_id_type(), life_id, asset(alife.b0));\n   alife.bal += alife.b0;\n   transfer(account_id_type(), rog_id, asset(arog.b0));\n   arog.bal += arog.b0;\n   upgrade_to_lifetime_member(life_id);\n   upgrade_to_lifetime_member(rog_id);\n\n   BOOST_TEST_MESSAGE(\"Enable fees\");\n   const auto& fees = db.get_global_properties().parameters.get_current_fees();\n\n#define CustomRegisterActor(actor_name, registrar_name, referrer_name, referrer_rate) \\\n   { \\\n      account_create_operation op; \\\n      op.registrar = registrar_name ## _id; \\\n      op.referrer = referrer_name ## _id; \\\n      op.referrer_percent = referrer_rate*GRAPHENE_1_PERCENT; \\\n      op.name = BOOST_PP_STRINGIZE(actor_name); \\\n      op.options.memo_key = actor_name ## _private_key.get_public_key(); \\\n      op.active = authority(1, public_key_type(actor_name ## _private_key.get_public_key()), 1); \\\n      op.owner = op.active; \\\n      op.fee = fees.calculate_fee(op); \\\n      trx.operations = {op}; \\\n      sign( trx,  registrar_name ## _private_key ); \\\n      actor_name ## _id = PUSH_TX( db, trx ).operation_results.front().get<object_id_type>(); \\\n      trx.clear(); \\\n   }\n#define CustomAuditActor(actor_name)                                \\\n   if( actor_name ## _id != account_id_type() )                     \\\n   {                                                                \\\n      CHECK_BALANCE( actor_name, a ## actor_name.bal );             \\\n      CHECK_VESTED_CASHBACK( actor_name, a ## actor_name.vcb );     \\\n      CHECK_UNVESTED_CASHBACK( actor_name, a ## actor_name.ucb );   \\\n      CHECK_CASHBACK_VBO( actor_name, a ## actor_name.ubal );       \\\n   }\n\n#define CustomAudit()                                \\\n   {                                                 \\\n      CustomAuditActor( life );                      \\\n      CustomAuditActor( rog );                       \\\n      CustomAuditActor( ann );                       \\\n      CustomAuditActor( stud );                      \\\n      CustomAuditActor( dumy );                      \\\n      CustomAuditActor( scud );                      \\\n      CustomAuditActor( pleb );                      \\\n   }\n\n   int64_t reg_fee    = fees.get< account_create_operation >().premium_fee;\n   int64_t xfer_fee   = fees.get< transfer_operation >().fee;\n   int64_t upg_an_fee = fees.get< account_upgrade_operation >().membership_annual_fee;\n   int64_t upg_lt_fee = fees.get< account_upgrade_operation >().membership_lifetime_fee;\n   // all percentages here are cut from whole pie!\n   uint64_t network_pct = 20 * P1;\n   uint64_t lt_pct = 375 * P100 / 1000;\n\n   BOOST_TEST_MESSAGE(\"Register and upgrade Ann\");\n   {\n      CustomRegisterActor(ann, life, life, 75);\n      alife.vcb += reg_fee; alife.bal += -reg_fee;\n      CustomAudit();\n\n      transfer(life_id, ann_id, asset(aann.b0));\n      alife.vcb += xfer_fee; alife.bal += -xfer_fee -aann.b0; aann.bal += aann.b0;\n      CustomAudit();\n\n      upgrade_to_annual_member(ann_id);\n      aann.ucb += upg_an_fee; aann.bal += -upg_an_fee;\n\n      // audit distribution of fees from Ann\n      alife.ubal += pct( P100-network_pct, aann.ucb );\n      alife.bal  += pct( P100-network_pct, aann.vcb );\n      aann.ucb = 0; aann.vcb = 0;\n      CustomAudit();\n   }\n\n   BOOST_TEST_MESSAGE(\"Register dumy and stud\");\n   CustomRegisterActor(dumy, rog, life, 75);\n   arog.vcb += reg_fee; arog.bal += -reg_fee;\n   CustomAudit();\n\n   CustomRegisterActor(stud, rog, ann, 80);\n   arog.vcb += reg_fee; arog.bal += -reg_fee;\n   CustomAudit();\n\n   BOOST_TEST_MESSAGE(\"Upgrade stud to lifetime member\");\n\n   transfer(life_id, stud_id, asset(astud.b0));\n   alife.vcb += xfer_fee; alife.bal += -astud.b0 -xfer_fee; astud.bal += astud.b0;\n   CustomAudit();\n\n   upgrade_to_lifetime_member(stud_id);\n   astud.ucb += upg_lt_fee; astud.bal -= upg_lt_fee;\n\n/*\nnetwork_cut:   20000\nreferrer_cut:  40000 -> ann\nregistrar_cut: 10000 -> rog\nlifetime_cut:  30000 -> life\n\nNET : net\nLTM : net' ltm\nREF : net' ltm' ref\nREG : net' ltm' ref'\n*/\n\n   // audit distribution of fees from stud\n   alife.ubal += pct( P100-network_pct,      lt_pct,                     astud.ucb );\n   aann.ubal  += pct( P100-network_pct, P100-lt_pct,      astud.ref_pct, astud.ucb );\n   arog.ubal  += pct( P100-network_pct, P100-lt_pct, P100-astud.ref_pct, astud.ucb );\n   astud.ucb  = 0;\n   CustomAudit();\n\n   BOOST_TEST_MESSAGE(\"Register pleb and scud\");\n\n   CustomRegisterActor(pleb, rog, stud, 95);\n   arog.vcb += reg_fee; arog.bal += -reg_fee;\n   CustomAudit();\n\n   CustomRegisterActor(scud, stud, ann, 80);\n   astud.vcb += reg_fee; astud.bal += -reg_fee;\n   CustomAudit();\n\n   generate_block();\n\n   BOOST_TEST_MESSAGE(\"Wait for maintenance interval\");\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   // audit distribution of fees from life\n   alife.ubal += pct( P100-network_pct, alife.ucb +alife.vcb );\n   alife.ucb = 0; alife.vcb = 0;\n\n   // audit distribution of fees from rog\n   arog.ubal += pct( P100-network_pct, arog.ucb + arog.vcb );\n   arog.ucb = 0; arog.vcb = 0;\n\n   // audit distribution of fees from ann\n   alife.ubal += pct( P100-network_pct,      lt_pct,                    aann.ucb+aann.vcb );\n   aann.ubal  += pct( P100-network_pct, P100-lt_pct,      aann.ref_pct, aann.ucb+aann.vcb );\n   alife.ubal += pct( P100-network_pct, P100-lt_pct, P100-aann.ref_pct, aann.ucb+aann.vcb );\n   aann.ucb = 0; aann.vcb = 0;\n\n   // audit distribution of fees from stud\n   astud.ubal += pct( P100-network_pct,                                  astud.ucb+astud.vcb );\n   astud.ucb = 0; astud.vcb = 0;\n\n   // audit distribution of fees from dumy\n   alife.ubal += pct( P100-network_pct,      lt_pct,                     adumy.ucb+adumy.vcb );\n   alife.ubal += pct( P100-network_pct, P100-lt_pct,      adumy.ref_pct, adumy.ucb+adumy.vcb );\n   arog.ubal  += pct( P100-network_pct, P100-lt_pct, P100-adumy.ref_pct, adumy.ucb+adumy.vcb );\n   adumy.ucb = 0; adumy.vcb = 0;\n\n   // audit distribution of fees from scud\n   alife.ubal += pct( P100-network_pct,      lt_pct,                     ascud.ucb+ascud.vcb );\n   aann.ubal  += pct( P100-network_pct, P100-lt_pct,      ascud.ref_pct, ascud.ucb+ascud.vcb );\n   astud.ubal += pct( P100-network_pct, P100-lt_pct, P100-ascud.ref_pct, ascud.ucb+ascud.vcb );\n   ascud.ucb = 0; ascud.vcb = 0;\n\n   // audit distribution of fees from pleb\n   astud.ubal += pct( P100-network_pct,      lt_pct,                     apleb.ucb+apleb.vcb );\n   astud.ubal += pct( P100-network_pct, P100-lt_pct,      apleb.ref_pct, apleb.ucb+apleb.vcb );\n   arog.ubal  += pct( P100-network_pct, P100-lt_pct, P100-apleb.ref_pct, apleb.ucb+apleb.vcb );\n   apleb.ucb = 0; apleb.vcb = 0;\n\n   CustomAudit();\n\n   BOOST_TEST_MESSAGE(\"Doing some transfers\");\n\n   transfer(stud_id, scud_id, asset(500000));\n   astud.bal += -500000-xfer_fee; astud.vcb += xfer_fee; ascud.bal += 500000;\n   CustomAudit();\n\n   transfer(scud_id, pleb_id, asset(400000));\n   ascud.bal += -400000-xfer_fee; ascud.vcb += xfer_fee; apleb.bal += 400000;\n   CustomAudit();\n\n   transfer(pleb_id, dumy_id, asset(300000));\n   apleb.bal += -300000-xfer_fee; apleb.vcb += xfer_fee; adumy.bal += 300000;\n   CustomAudit();\n\n   transfer(dumy_id, rog_id, asset(200000));\n   adumy.bal += -200000-xfer_fee; adumy.vcb += xfer_fee; arog.bal += 200000;\n   CustomAudit();\n\n   BOOST_TEST_MESSAGE(\"Waiting for maintenance time\");\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // audit distribution of fees from life\n   alife.ubal += pct( P100-network_pct, alife.ucb +alife.vcb );\n   alife.ucb = 0; alife.vcb = 0;\n\n   // audit distribution of fees from rog\n   arog.ubal += pct( P100-network_pct, arog.ucb + arog.vcb );\n   arog.ucb = 0; arog.vcb = 0;\n\n   // audit distribution of fees from ann\n   alife.ubal += pct( P100-network_pct,      lt_pct,                    aann.ucb+aann.vcb );\n   aann.ubal  += pct( P100-network_pct, P100-lt_pct,      aann.ref_pct, aann.ucb+aann.vcb );\n   alife.ubal += pct( P100-network_pct, P100-lt_pct, P100-aann.ref_pct, aann.ucb+aann.vcb );\n   aann.ucb = 0; aann.vcb = 0;\n\n   // audit distribution of fees from stud\n   astud.ubal += pct( P100-network_pct,                                  astud.ucb+astud.vcb );\n   astud.ucb = 0; astud.vcb = 0;\n\n   // audit distribution of fees from dumy\n   alife.ubal += pct( P100-network_pct,      lt_pct,                     adumy.ucb+adumy.vcb );\n   alife.ubal += pct( P100-network_pct, P100-lt_pct,      adumy.ref_pct, adumy.ucb+adumy.vcb );\n   arog.ubal  += pct( P100-network_pct, P100-lt_pct, P100-adumy.ref_pct, adumy.ucb+adumy.vcb );\n   adumy.ucb = 0; adumy.vcb = 0;\n\n   // audit distribution of fees from scud\n   alife.ubal += pct( P100-network_pct,      lt_pct,                     ascud.ucb+ascud.vcb );\n   aann.ubal  += pct( P100-network_pct, P100-lt_pct,      ascud.ref_pct, ascud.ucb+ascud.vcb );\n   astud.ubal += pct( P100-network_pct, P100-lt_pct, P100-ascud.ref_pct, ascud.ucb+ascud.vcb );\n   ascud.ucb = 0; ascud.vcb = 0;\n\n   // audit distribution of fees from pleb\n   astud.ubal += pct( P100-network_pct,      lt_pct,                     apleb.ucb+apleb.vcb );\n   astud.ubal += pct( P100-network_pct, P100-lt_pct,      apleb.ref_pct, apleb.ucb+apleb.vcb );\n   arog.ubal  += pct( P100-network_pct, P100-lt_pct, P100-apleb.ref_pct, apleb.ucb+apleb.vcb );\n   apleb.ucb = 0; apleb.vcb = 0;\n\n   CustomAudit();\n\n   BOOST_TEST_MESSAGE(\"Waiting for annual membership to expire\");\n\n   generate_blocks(ann_id(db).membership_expiration_date);\n   generate_block();\n\n   BOOST_TEST_MESSAGE(\"Transferring from scud to pleb\");\n\n   //ann's membership has expired, so scud's fee should go up to life instead.\n   transfer(scud_id, pleb_id, asset(10));\n   ascud.bal += -10-xfer_fee; ascud.vcb += xfer_fee; apleb.bal += 10;\n   CustomAudit();\n\n   BOOST_TEST_MESSAGE(\"Waiting for maint interval\");\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // audit distribution of fees from scud\n   alife.ubal += pct( P100-network_pct,      lt_pct,                     ascud.ucb+ascud.vcb );\n   alife.ubal += pct( P100-network_pct, P100-lt_pct,      ascud.ref_pct, ascud.ucb+ascud.vcb );\n   astud.ubal += pct( P100-network_pct, P100-lt_pct, P100-ascud.ref_pct, ascud.ucb+ascud.vcb );\n   ascud.ucb = 0; ascud.vcb = 0;\n\n   CustomAudit();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( account_create_fee_scaling )\n{ try {\n   auto accounts_per_scale = db.get_global_properties().parameters.accounts_per_fee_scale;\n   db.modify(global_property_id_type()(db), [](global_property_object& gpo)\n   {\n      gpo.parameters.get_mutable_fees() = fee_schedule::get_default();\n      gpo.parameters.get_mutable_fees().get<account_create_operation>().basic_fee = 1;\n   });\n\n   for( int i = db.get_dynamic_global_properties().accounts_registered_this_interval; i < accounts_per_scale; ++i )\n   {\n      BOOST_CHECK_EQUAL(db.get_global_properties().parameters.get_current_fees().get<account_create_operation>().basic_fee, 1u);\n      create_account(\"shill\" + fc::to_string(i));\n   }\n   for( int i = 0; i < accounts_per_scale; ++i )\n   {\n      BOOST_CHECK_EQUAL(db.get_global_properties().parameters.get_current_fees().get<account_create_operation>().basic_fee, 16u);\n      create_account(\"moreshills\" + fc::to_string(i));\n   }\n   for( int i = 0; i < accounts_per_scale; ++i )\n   {\n      BOOST_CHECK_EQUAL(db.get_global_properties().parameters.get_current_fees().get<account_create_operation>().basic_fee, 256u);\n      create_account(\"moarshills\" + fc::to_string(i));\n   }\n   BOOST_CHECK_EQUAL(db.get_global_properties().parameters.get_current_fees().get<account_create_operation>().basic_fee, 4096u);\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   BOOST_CHECK_EQUAL(db.get_global_properties().parameters.get_current_fees().get<account_create_operation>().basic_fee, 1u);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( fee_refund_test )\n{\n   try\n   {\n      ACTORS((alice)(bob)(izzy));\n\n      int64_t alice_b0 = 1000000, bob_b0 = 1000000;\n\n      transfer( account_id_type(), alice_id, asset(alice_b0) );\n      transfer( account_id_type(), bob_id, asset(bob_b0) );\n\n      asset_id_type core_id = asset_id_type();\n      asset_id_type usd_id = create_user_issued_asset( \"IZZYUSD\", izzy_id(db), charge_market_fee ).get_id();\n      issue_uia( alice_id, asset( alice_b0, usd_id ) );\n      issue_uia( bob_id, asset( bob_b0, usd_id ) );\n\n      int64_t order_create_fee = 537;\n      int64_t order_update_fee = 437;\n      int64_t order_cancel_fee = 129;\n\n      uint32_t skip = database::skip_witness_signature\n                    | database::skip_transaction_signatures\n                    | database::skip_transaction_dupe_check\n                    | database::skip_block_size_check\n                    | database::skip_tapos_check\n                    | database::skip_merkle_check\n                    ;\n\n      generate_block( skip );\n\n      for( int i=0; i<5; i++ )\n      {\n         if( i == 1 )\n         {\n            generate_blocks( HARDFORK_445_TIME, true, skip );\n            generate_block( skip );\n         }\n         else if( i == 2 )\n         {\n            generate_blocks( HARDFORK_CORE_1604_TIME, true, skip );\n            generate_block( skip );\n         }\n\n\n         // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation\n         // so we have to do it every time we stop generating/popping blocks and start doing tx's\n         enable_fees();\n         /*\n         change_fees({\n                       limit_order_create_operation::fee_params_t { order_create_fee },\n                       limit_order_cancel_operation::fee_params_t { order_cancel_fee }\n                     });\n         */\n         // C++ -- The above commented out statement doesn't work, I don't know why\n         // so we will use the following rather lengthy initialization instead\n         {\n            fee_parameters::flat_set_type new_fees;\n            {\n               limit_order_create_operation::fee_params_t create_fee_params;\n               create_fee_params.fee = order_create_fee;\n               new_fees.insert( create_fee_params );\n            }\n            {\n               limit_order_update_operation::fee_params_t update_fee_params;\n               update_fee_params.fee = order_update_fee;\n               new_fees.insert( update_fee_params );\n            }\n            {\n               limit_order_cancel_operation::fee_params_t cancel_fee_params;\n               cancel_fee_params.fee = order_cancel_fee;\n               new_fees.insert( cancel_fee_params );\n            }\n            change_fees( new_fees );\n         }\n\n         // Alice creates order\n         // Bob creates order which doesn't match\n\n         // AAAAGGHH create_sell_order reads trx.expiration #469\n         set_expiration( db, trx );\n\n         // Check non-overlapping\n\n         limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(1000, usd_id) )->get_id();\n         limit_order_id_type bo1_id = create_sell_order(   bob_id, asset(500, usd_id), asset(1000) )->get_id();\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_b0 - order_create_fee );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_b0 - 500 );\n\n         int64_t update_net_fee = order_cancel_fee * order_update_fee / order_create_fee ;\n         int64_t bob_update_fees = 0;\n         if( i == 2 )\n         {\n            // Bob updates order\n            update_limit_order( bo1_id, {}, asset(100, usd_id) );\n\n            bob_update_fees += update_net_fee;\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_b0 - bob_update_fees - order_update_fee );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_b0 - 600 );\n         }\n\n         // Bob cancels order\n         cancel_limit_order( bo1_id(db) );\n\n         int64_t cancel_net_fee;\n         if( db.head_block_time() > HARDFORK_445_TIME )\n            cancel_net_fee = order_cancel_fee;\n         else\n            cancel_net_fee = order_create_fee + order_cancel_fee;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_b0 );\n\n         // Alice cancels order\n         cancel_limit_order( ao1_id(db) );\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_b0 );\n\n         // Check partial fill\n         const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id) );\n\n         BOOST_REQUIRE( ao2 != nullptr );\n\n         int64_t alice_update_fees = order_create_fee;\n         int64_t alice_update_amounts = 0;\n         if( i == 3 )\n         {\n            // Alice updates order\n            update_limit_order( *ao2, {}, asset(100) );\n\n            alice_update_fees = update_net_fee + order_update_fee;\n            alice_update_amounts = 100;\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees\n                                                                          - 1000 - alice_update_amounts );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 );\n         }\n\n         const limit_order_object* bo2 = create_sell_order(   bob_id, asset(100, usd_id), asset(500) );\n\n         BOOST_CHECK( bo2 == nullptr );\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees\n                                                                       - 1000 - alice_update_amounts );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 + 100 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ),   bob_b0 - bob_update_fees - cancel_net_fee\n                                                                       - order_create_fee + 500 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ),   bob_b0 - 100 );\n\n         if( i == 4 )\n         {\n            // Alice updates order\n            update_limit_order( *ao2, {}, asset(100) );\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees\n                                                                          - 1000 - alice_update_amounts\n                                                                          - order_update_fee - 100 );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 + 100 );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ),   bob_b0 - bob_update_fees - cancel_net_fee\n                                                                          - order_create_fee + 500 );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ),   bob_b0 - 100 );\n         }\n\n         // cancel Alice order, show that entire deferred_fee was consumed by partial match\n         cancel_limit_order( *ao2 );\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees - 500\n                                                                       - order_cancel_fee );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_b0 + 100 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ),   bob_b0 - bob_update_fees - cancel_net_fee\n                                                                       - order_create_fee + 500 );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ),   bob_b0 - 100 );\n\n         // TODO: Check multiple fill\n         // there really should be a test case involving Alice creating multiple orders matched by single Bob order\n         // but we'll save that for future cleanup\n\n         // undo above tx's and reset\n         generate_block( skip );\n         db.pop_block();\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( non_core_fee_refund_test )\n{\n   try\n   {\n      ACTORS((alice)(bob)(izzy));\n\n      int64_t alice_b0 = 1000000, bob_b0 = 1000000;\n      int64_t pool_0 = 100000, accum_0 = 0;\n\n      transfer( account_id_type(), alice_id, asset(alice_b0) );\n      transfer( account_id_type(), bob_id, asset(bob_b0) );\n\n      asset_id_type core_id = asset_id_type();\n      const auto& usd_obj = create_user_issued_asset( \"IZZYUSD\", izzy_id(db), charge_market_fee );\n      asset_id_type usd_id = usd_obj.get_id();\n      issue_uia( alice_id, asset( alice_b0, usd_id ) );\n      issue_uia( bob_id, asset( bob_b0, usd_id ) );\n\n      fund_fee_pool( committee_account( db ), usd_obj, pool_0 );\n\n      int64_t order_create_fee = 537;\n      int64_t order_cancel_fee = 129;\n\n      uint32_t skip = database::skip_witness_signature\n                    | database::skip_transaction_signatures\n                    | database::skip_transaction_dupe_check\n                    | database::skip_block_size_check\n                    | database::skip_tapos_check\n                    | database::skip_merkle_check\n                    ;\n\n      generate_block( skip );\n\n      fee_parameters::flat_set_type new_fees;\n      {\n         limit_order_create_operation::fee_params_t create_fee_params;\n         create_fee_params.fee = order_create_fee;\n         new_fees.insert( create_fee_params );\n      }\n      {\n         limit_order_cancel_operation::fee_params_t cancel_fee_params;\n         cancel_fee_params.fee = order_cancel_fee;\n         new_fees.insert( cancel_fee_params );\n      }\n      {\n         transfer_operation::fee_params_t transfer_fee_params;\n         transfer_fee_params.fee = 0;\n         transfer_fee_params.price_per_kbyte = 0;\n         new_fees.insert( transfer_fee_params );\n      }\n\n      for( int i=0; i<4; i++ )\n      {\n         bool expire_order = ( i % 2 != 0 );\n         bool before_hardfork_445 = ( i < 2 );\n         if( i == 2 )\n         {\n            generate_blocks( HARDFORK_445_TIME, true, skip );\n            generate_block( skip );\n         }\n\n         // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation\n         // so we have to do it every time we stop generating/popping blocks and start doing tx's\n         enable_fees();\n         change_fees( new_fees );\n\n         // AAAAGGHH create_sell_order reads trx.expiration #469\n         set_expiration( db, trx );\n\n         // prepare params\n         uint32_t blocks_generated = 0;\n         time_point_sec max_exp = time_point_sec::maximum();\n         time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then\n                                                    // expired at current block\n         price cer( asset(1), asset(1, usd_id) );\n         const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n\n         // balance data\n         int64_t alice_bc = alice_b0, bob_bc = bob_b0; // core balance\n         int64_t alice_bu = alice_b0, bob_bu = bob_b0; // usd balance\n         int64_t pool_b = pool_0, accum_b = accum_0;\n\n         // refund data\n         int64_t core_fee_refund_core;\n         int64_t core_fee_refund_usd;\n         int64_t usd_fee_refund_core;\n         int64_t usd_fee_refund_usd;\n         if( db.head_block_time() > HARDFORK_445_TIME )\n         {\n            core_fee_refund_core = order_create_fee;\n            core_fee_refund_usd = 0;\n            usd_fee_refund_core = order_create_fee;\n            usd_fee_refund_usd = 0;\n         }\n         else\n         {\n            core_fee_refund_core = 0;\n            core_fee_refund_usd = 0;\n            usd_fee_refund_core = 0;\n            usd_fee_refund_usd = 0;\n         }\n\n         // Check non-overlapping\n         // Alice creates order\n         // Bob creates order which doesn't match\n         limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(1000, usd_id) )->get_id();\n         limit_order_id_type bo1_id = create_sell_order(   bob_id, asset(500, usd_id), asset(1000), exp, cer )\n                                         ->get_id();\n\n         alice_bc -= order_create_fee;\n         alice_bc -= 1000;\n         bob_bu -= order_create_fee;\n         bob_bu -= 500;\n         pool_b -= order_create_fee;\n         accum_b += order_create_fee;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Bob cancels order\n         if( !expire_order )\n            cancel_limit_order( bo1_id(db) );\n         else\n         {\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n         if( !expire_order || !before_hardfork_445 )\n            bob_bc -= order_cancel_fee;\n         // else do nothing: before hard fork 445, no fee on expired order\n         bob_bc += usd_fee_refund_core;\n         bob_bu += 500;\n         bob_bu += usd_fee_refund_usd;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n\n         // Alice cancels order\n         cancel_limit_order( ao1_id(db) );\n\n         alice_bc -= order_cancel_fee;\n         alice_bc += 1000;\n         alice_bc += core_fee_refund_core;\n         alice_bu += core_fee_refund_usd;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Check partial fill\n         const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer );\n         const limit_order_id_type ao2_id = ao2->get_id();\n         const limit_order_object* bo2 = create_sell_order(   bob_id, asset(100, usd_id), asset(500) );\n\n         BOOST_CHECK( db.find( ao2_id ) != nullptr );\n         BOOST_CHECK( bo2 == nullptr );\n\n         // data after order created\n         alice_bc -= 1000;\n         alice_bu -= order_create_fee;\n         pool_b -= order_create_fee;\n         accum_b += order_create_fee;\n         bob_bc -= order_create_fee;\n         bob_bu -= 100;\n\n         // data after order filled\n         alice_bu += 100;\n         bob_bc += 500;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // cancel Alice order, show that entire deferred_fee was consumed by partial match\n         if( !expire_order )\n            cancel_limit_order( *ao2 );\n         else\n         {\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n\n         if( !expire_order )\n            alice_bc -= order_cancel_fee;\n         // else do nothing:\n         //         before hard fork 445, no fee when order is expired;\n         //         after hard fork 445, when partially filled order expired, order cancel fee is capped at 0\n         alice_bc += 500;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Check multiple fill\n         // Alice creating multiple orders\n         const limit_order_object* ao31 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer );\n         const limit_order_object* ao32 = create_sell_order( alice_id, asset(1000), asset(2000, usd_id), max_exp, cer );\n         const limit_order_object* ao33 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer );\n         const limit_order_object* ao34 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer );\n         const limit_order_object* ao35 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer );\n\n         const limit_order_id_type ao31id = ao31->get_id();\n         const limit_order_id_type ao32id = ao32->get_id();\n         const limit_order_id_type ao33id = ao33->get_id();\n         const limit_order_id_type ao34id = ao34->get_id();\n         const limit_order_id_type ao35id = ao35->get_id();\n\n         alice_bc -= 1000 * 5;\n         alice_bu -= order_create_fee * 5;\n         pool_b -= order_create_fee * 5;\n         accum_b += order_create_fee * 5;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Bob creating an order matching multiple Alice's orders\n         const limit_order_object* bo31 = create_sell_order(   bob_id, asset(500, usd_id), asset(2500), exp );\n\n         BOOST_CHECK( db.find( ao31id ) == nullptr );\n         BOOST_CHECK( db.find( ao32id ) != nullptr );\n         BOOST_CHECK( db.find( ao33id ) == nullptr );\n         BOOST_CHECK( db.find( ao34id ) != nullptr );\n         BOOST_CHECK( db.find( ao35id ) != nullptr );\n         BOOST_CHECK( bo31 == nullptr );\n\n         // data after order created\n         bob_bc -= order_create_fee;\n         bob_bu -= 500;\n\n         // data after order filled\n         alice_bu += 500;\n         bob_bc += 2500;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Bob creating an order matching multiple Alice's orders\n         const limit_order_object* bo32 = create_sell_order(   bob_id, asset(500, usd_id), asset(2500), exp );\n\n         BOOST_CHECK( db.find( ao31id ) == nullptr );\n         BOOST_CHECK( db.find( ao32id ) != nullptr );\n         BOOST_CHECK( db.find( ao33id ) == nullptr );\n         BOOST_CHECK( db.find( ao34id ) == nullptr );\n         BOOST_CHECK( db.find( ao35id ) == nullptr );\n         BOOST_CHECK( bo32 != nullptr );\n\n         // data after order created\n         bob_bc -= order_create_fee;\n         bob_bu -= 500;\n\n         // data after order filled\n         alice_bu += 300;\n         bob_bc += 1500;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // cancel Bob order, show that entire deferred_fee was consumed by partial match\n         if( !expire_order )\n            cancel_limit_order( *bo32 );\n         else\n         {\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n         if( !expire_order )\n            bob_bc -= order_cancel_fee;\n         // else do nothing:\n         //         before hard fork 445, no fee when order is expired;\n         //         after hard fork 445, when partially filled order expired, order cancel fee is capped at 0\n         bob_bu += 200;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // cancel Alice order, will refund after hard fork 445\n         cancel_limit_order( ao32id( db ) );\n\n         alice_bc -= order_cancel_fee;\n         alice_bc += 1000;\n         alice_bc += usd_fee_refund_core;\n         alice_bu += usd_fee_refund_usd;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // undo above tx's and reset\n         generate_block( skip );\n         ++blocks_generated;\n         while( blocks_generated > 0 )\n         {\n            db.pop_block();\n            --blocks_generated;\n         }\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test )\n{ // create orders before hard fork, cancel them after hard fork\n   try\n   {\n      ACTORS((alice)(bob)(izzy));\n\n      int64_t alice_b0 = 1000000, bob_b0 = 1000000;\n      int64_t pool_0 = 100000, accum_0 = 0;\n\n      transfer( account_id_type(), alice_id, asset(alice_b0) );\n      transfer( account_id_type(), bob_id, asset(bob_b0) );\n\n      asset_id_type core_id = asset_id_type();\n      const auto& usd_obj = create_user_issued_asset( \"IZZYUSD\", izzy_id(db), charge_market_fee );\n      asset_id_type usd_id = usd_obj.get_id();\n      issue_uia( alice_id, asset( alice_b0, usd_id ) );\n      issue_uia( bob_id, asset( bob_b0, usd_id ) );\n\n      fund_fee_pool( committee_account( db ), usd_obj, pool_0 );\n\n      int64_t order_create_fee = 537;\n      int64_t order_cancel_fee = 129;\n\n      uint32_t skip = database::skip_witness_signature\n                    | database::skip_transaction_signatures\n                    | database::skip_transaction_dupe_check\n                    | database::skip_block_size_check\n                    | database::skip_tapos_check\n                    | database::skip_merkle_check\n                    ;\n\n      generate_block( skip );\n\n      fee_parameters::flat_set_type new_fees;\n      {\n         limit_order_create_operation::fee_params_t create_fee_params;\n         create_fee_params.fee = order_create_fee;\n         new_fees.insert( create_fee_params );\n      }\n      {\n         limit_order_cancel_operation::fee_params_t cancel_fee_params;\n         cancel_fee_params.fee = order_cancel_fee;\n         new_fees.insert( cancel_fee_params );\n      }\n      {\n         transfer_operation::fee_params_t transfer_fee_params;\n         transfer_fee_params.fee = 0;\n         transfer_fee_params.price_per_kbyte = 0;\n         new_fees.insert( transfer_fee_params );\n      }\n\n      // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation\n      // so we have to do it every time we stop generating/popping blocks and start doing tx's\n      enable_fees();\n      change_fees( new_fees );\n\n      // AAAAGGHH create_sell_order reads trx.expiration #469\n      set_expiration( db, trx );\n\n      // prepare params\n      const chain_parameters& params = db.get_global_properties().parameters;\n      time_point_sec max_exp = time_point_sec::maximum();\n      time_point_sec exp = HARDFORK_445_TIME + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3 );\n      time_point_sec exp2 = HARDFORK_445_TIME + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 13 );\n      price cer( asset(1), asset(1, usd_id) );\n      const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n\n      // balance data\n      int64_t alice_bc = alice_b0, bob_bc = bob_b0; // core balance\n      int64_t alice_bu = alice_b0, bob_bu = bob_b0; // usd balance\n      int64_t pool_b = pool_0, accum_b = accum_0;\n\n      // prepare orders\n      BOOST_TEST_MESSAGE( \"Creating orders those will never match: ao1, ao2, bo1, bo2 ..\" );\n      // ao1: won't expire, won't match, fee in core\n      limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(100000, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao1_id ) != nullptr );\n      // ao2: will expire, won't match, fee in core\n      limit_order_id_type ao2_id = create_sell_order( alice_id, asset(800), asset(100000, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao2_id ) != nullptr );\n      // bo1: won't expire, won't match, fee in usd\n      limit_order_id_type bo1_id = create_sell_order(   bob_id, asset(1000, usd_id), asset(100000), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo1_id ) != nullptr );\n      // bo2: will expire, won't match, fee in usd\n      limit_order_id_type bo2_id = create_sell_order(   bob_id, asset(800, usd_id), asset(100000), exp, cer )->get_id();\n      BOOST_CHECK( db.find( bo2_id ) != nullptr );\n\n      alice_bc -= order_create_fee * 2;\n      alice_bc -= 1000;\n      alice_bc -= 800;\n      bob_bu -= order_create_fee * 2;\n      bob_bu -= 1000;\n      bob_bu -= 800;\n      pool_b -= order_create_fee * 2;\n      accum_b += order_create_fee * 2;\n      int64_t ao1_remain = 1000;\n      int64_t ao2_remain = 800;\n      int64_t bo1_remain = 1000;\n      int64_t bo2_remain = 800;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao3: won't expire, partially match before hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao3 ..\" );\n      limit_order_id_type ao3_id = create_sell_order( alice_id, asset(900), asset(2700, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao3_id ) != nullptr );\n      create_sell_order( bob_id, asset(600, usd_id), asset(200) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 900;\n      alice_bu += 600;\n      bob_bc -= order_create_fee;\n      bob_bu -= 600;\n      bob_bc += 200;\n      int64_t ao3_remain = 900 - 200;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao4: will expire, will partially match before hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao4 ..\" );\n      limit_order_id_type ao4_id = create_sell_order( alice_id, asset(700), asset(1400, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao4_id ) != nullptr );\n      create_sell_order( bob_id, asset(200, usd_id), asset(100) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 700;\n      alice_bu += 200;\n      bob_bc -= order_create_fee;\n      bob_bu -= 200;\n      bob_bc += 100;\n      int64_t ao4_remain = 700 - 100;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo3: won't expire, will partially match before hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo3 ..\" );\n      limit_order_id_type bo3_id = create_sell_order(   bob_id, asset(500, usd_id), asset(1500), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo3_id ) != nullptr );\n      create_sell_order( alice_id, asset(450), asset(150, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 450;\n      alice_bu += 150;\n      bob_bu -= order_create_fee;\n      bob_bu -= 500;\n      bob_bc += 450;\n      pool_b -= order_create_fee;\n      accum_b += order_create_fee;\n      int64_t bo3_remain = 500 - 150;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo4: will expire, will partially match before hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo4 ..\" );\n      limit_order_id_type bo4_id = create_sell_order(   bob_id, asset(300, usd_id), asset(600), exp, cer )->get_id();\n      BOOST_CHECK( db.find( bo4_id ) != nullptr );\n      create_sell_order( alice_id, asset(140), asset(70, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 140;\n      alice_bu += 70;\n      bob_bu -= order_create_fee;\n      bob_bu -= 300;\n      bob_bc += 140;\n      pool_b -= order_create_fee;\n      accum_b += order_create_fee;\n      int64_t bo4_remain = 300 - 70;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao5: won't expire, partially match after hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao5 ..\" );\n      limit_order_id_type ao5_id = create_sell_order( alice_id, asset(606), asset(909, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao5_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 606;\n      int64_t ao5_remain = 606;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao6: will expire, partially match after hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao6 ..\" );\n      limit_order_id_type ao6_id = create_sell_order( alice_id, asset(333), asset(444, usd_id), exp2 )->get_id();\n      BOOST_CHECK( db.find( ao6_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 333;\n      int64_t ao6_remain = 333;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo5: won't expire, partially match after hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo5 ..\" );\n      limit_order_id_type bo5_id = create_sell_order(   bob_id, asset(255, usd_id), asset(408), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo5_id ) != nullptr );\n\n      bob_bu -= order_create_fee;\n      bob_bu -= 255;\n      pool_b -= order_create_fee;\n      accum_b += order_create_fee;\n      int64_t bo5_remain = 255;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo6: will expire, partially match after hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo6 ..\" );\n      limit_order_id_type bo6_id = create_sell_order(   bob_id, asset(127, usd_id), asset(127), exp2, cer )->get_id();\n      BOOST_CHECK( db.find( bo6_id ) != nullptr );\n\n      bob_bu -= order_create_fee;\n      bob_bu -= 127;\n      pool_b -= order_create_fee;\n      accum_b += order_create_fee;\n      int64_t bo6_remain = 127;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block so the orders will be in db before hard fork\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      // generate blocks util hard fork 445\n      generate_blocks( HARDFORK_445_TIME, true, skip );\n      generate_block( skip );\n\n      // nothing will change\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate more blocks, so some orders will expire\n      generate_blocks( exp, true, skip );\n\n      // no fee refund for orders created before hard fork 445, but remaining funds will be refunded\n      BOOST_TEST_MESSAGE( \"Checking expired orders: ao2, ao4, bo2, bo4 ..\" );\n      alice_bc += ao2_remain;\n      alice_bc += ao4_remain;\n      bob_bu += bo2_remain;\n      bob_bu += bo4_remain;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // cancel ao1\n      BOOST_TEST_MESSAGE( \"Cancel order ao1 ..\" );\n      cancel_limit_order( ao1_id(db) );\n\n      alice_bc += ao1_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel ao3\n      BOOST_TEST_MESSAGE( \"Cancel order ao3 ..\" );\n      cancel_limit_order( ao3_id(db) );\n\n      alice_bc += ao3_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo1\n      BOOST_TEST_MESSAGE( \"Cancel order bo1 ..\" );\n      cancel_limit_order( bo1_id(db) );\n\n      bob_bu += bo1_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo3\n      BOOST_TEST_MESSAGE( \"Cancel order bo3 ..\" );\n      cancel_limit_order( bo3_id(db) );\n\n      bob_bu += bo3_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill ao6\n      BOOST_TEST_MESSAGE( \"Partially fill ao6 ..\" );\n      create_sell_order( bob_id, asset(88, usd_id), asset(66) );\n\n      alice_bu += 88;\n      bob_bc -= order_create_fee;\n      bob_bu -= 88;\n      bob_bc += 66;\n      ao6_remain -= 66;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo6\n      BOOST_TEST_MESSAGE( \"Partially fill bo6 ..\" );\n      create_sell_order( alice_id, asset(59), asset(59, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 59;\n      alice_bu += 59;\n      bob_bc += 59;\n      bo6_remain -= 59;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block to save the changes\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate blocks util exp2, so some orders will expire\n      generate_blocks( exp2, true, skip );\n\n      // no fee refund for orders created before hard fork 445, but remaining funds will be refunded\n      BOOST_TEST_MESSAGE( \"Checking expired orders: ao6, bo6 ..\" );\n      alice_bc += ao6_remain;\n      bob_bu += bo6_remain;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // partially fill ao5\n      BOOST_TEST_MESSAGE( \"Partially fill ao5 ..\" );\n      create_sell_order( bob_id, asset(93, usd_id), asset(62) );\n\n      alice_bu += 93;\n      bob_bc -= order_create_fee;\n      bob_bu -= 93;\n      bob_bc += 62;\n      ao5_remain -= 62;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo5\n      BOOST_TEST_MESSAGE( \"Partially fill bo5 ..\" );\n      create_sell_order( alice_id, asset(24), asset(15, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 24;\n      alice_bu += 15;\n      bob_bc += 24;\n      bo5_remain -= 15;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel ao5\n      BOOST_TEST_MESSAGE( \"Cancel order ao5 ..\" );\n      cancel_limit_order( ao5_id(db) );\n\n      alice_bc += ao5_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo5\n      BOOST_TEST_MESSAGE( \"Cancel order bo5 ..\" );\n      cancel_limit_order( bo5_id(db) );\n\n      bob_bu += bo5_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block to save the changes\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( bsip26_fee_refund_test )\n{\n   try\n   {\n      ACTORS((alice)(bob)(izzy));\n\n      int64_t alice_b0 = 1000000, bob_b0 = 1000000;\n      int64_t pool_0 = 1000000, accum_0 = 0;\n\n      transfer( account_id_type(), alice_id, asset(alice_b0) );\n      transfer( account_id_type(), bob_id, asset(bob_b0) );\n\n      asset_id_type core_id = asset_id_type();\n      int64_t cer_core_amount = 1801;\n      int64_t cer_usd_amount = 3;\n      price tmp_cer( asset( cer_core_amount ), asset( cer_usd_amount, asset_id_type(1) ) );\n      const auto& usd_obj = create_user_issued_asset( \"IZZYUSD\", izzy_id(db), charge_market_fee, tmp_cer );\n      asset_id_type usd_id = usd_obj.get_id();\n      issue_uia( alice_id, asset( alice_b0, usd_id ) );\n      issue_uia( bob_id, asset( bob_b0, usd_id ) );\n\n      fund_fee_pool( committee_account( db ), usd_obj, pool_0 );\n\n      int64_t order_create_fee = 547;\n      int64_t order_update_fee = 487;\n      int64_t order_cancel_fee;\n      int64_t order_cancel_fee1 = 139;\n      int64_t order_cancel_fee2 = 829;\n\n      uint32_t skip = database::skip_witness_signature\n                    | database::skip_transaction_signatures\n                    | database::skip_transaction_dupe_check\n                    | database::skip_block_size_check\n                    | database::skip_tapos_check\n                    | database::skip_merkle_check\n                    ;\n\n      generate_block( skip );\n\n      fee_parameters::flat_set_type new_fees;\n      fee_parameters::flat_set_type new_fees1;\n      fee_parameters::flat_set_type new_fees2;\n      {\n         limit_order_create_operation::fee_params_t create_fee_params;\n         create_fee_params.fee = order_create_fee;\n         new_fees1.insert( create_fee_params );\n         new_fees2.insert( create_fee_params );\n      }\n      {\n         limit_order_update_operation::fee_params_t update_fee_params;\n         update_fee_params.fee = order_update_fee;\n         new_fees1.insert( update_fee_params );\n         new_fees2.insert( update_fee_params );\n      }\n      {\n         limit_order_cancel_operation::fee_params_t cancel_fee_params;\n         cancel_fee_params.fee = order_cancel_fee1;\n         new_fees1.insert( cancel_fee_params );\n      }\n      {\n         limit_order_cancel_operation::fee_params_t cancel_fee_params;\n         cancel_fee_params.fee = order_cancel_fee2;\n         new_fees2.insert( cancel_fee_params );\n      }\n      {\n         transfer_operation::fee_params_t transfer_fee_params;\n         transfer_fee_params.fee = 0;\n         transfer_fee_params.price_per_kbyte = 0;\n         new_fees1.insert( transfer_fee_params );\n         new_fees2.insert( transfer_fee_params );\n      }\n\n      for( int i=0; i<16; i++ )\n      {\n         bool expire_order = ( i % 2 != 0 );\n         bool high_cancel_fee = ( i % 4 >= 2 );\n         bool before_hardfork_445 = ( i < 4 );\n         bool after_bsip26 = ( i >= 8 );\n         bool after_core_hf1604 = ( i >= 12 );\n         idump( (before_hardfork_445)(after_bsip26)(after_core_hf1604)(expire_order)(high_cancel_fee) );\n         if( i == 4 )\n         {\n            BOOST_TEST_MESSAGE( \"Hard fork 445\" );\n            generate_blocks( HARDFORK_445_TIME, true, skip );\n            generate_block( skip );\n         }\n         else if( i == 8 )\n         {\n            BOOST_TEST_MESSAGE( \"Hard fork core-604 (bsip26)\" );\n            generate_blocks( HARDFORK_CORE_604_TIME, true, skip );\n            generate_block( skip );\n         }\n         else if( i == 12 )\n         {\n            BOOST_TEST_MESSAGE( \"Hard fork core-1604\" );\n            generate_blocks( HARDFORK_CORE_1604_TIME, true, skip );\n            generate_block( skip );\n         }\n\n         if( high_cancel_fee )\n         {\n            new_fees = new_fees2;\n            order_cancel_fee = order_cancel_fee2;\n         }\n         else\n         {\n            new_fees = new_fees1;\n            order_cancel_fee = order_cancel_fee1;\n         }\n\n         int64_t usd_create_fee = order_create_fee * cer_usd_amount / cer_core_amount;\n         if( usd_create_fee * cer_core_amount != order_create_fee * cer_usd_amount ) usd_create_fee += 1;\n         int64_t usd_update_fee = order_update_fee * cer_usd_amount / cer_core_amount;\n         if( usd_update_fee * cer_core_amount != order_update_fee * cer_usd_amount ) usd_update_fee += 1;\n         int64_t usd_cancel_fee = order_cancel_fee * cer_usd_amount / cer_core_amount;\n         if( usd_cancel_fee * cer_core_amount != order_cancel_fee * cer_usd_amount ) usd_cancel_fee += 1;\n         int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount;\n         int64_t core_update_fee = usd_update_fee * cer_core_amount / cer_usd_amount;\n         int64_t core_cancel_fee = usd_cancel_fee * cer_core_amount / cer_usd_amount;\n         BOOST_CHECK( core_cancel_fee >= order_cancel_fee );\n\n         BOOST_TEST_MESSAGE( \"Start\" );\n\n         // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation\n         // so we have to do it every time we stop generating/popping blocks and start doing tx's\n         enable_fees();\n         change_fees( new_fees );\n\n         // AAAAGGHH create_sell_order reads trx.expiration #469\n         set_expiration( db, trx );\n\n         // prepare params\n         uint32_t blocks_generated = 0;\n         time_point_sec max_exp = time_point_sec::maximum();\n         time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then\n                                                    // expired at current block\n         price cer = usd_id( db ).options.core_exchange_rate;\n         const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n\n         // balance data\n         int64_t alice_bc = alice_b0, bob_bc = bob_b0; // core balance\n         int64_t alice_bu = alice_b0, bob_bu = bob_b0; // usd balance\n         int64_t pool_b = pool_0, accum_b = accum_0;\n\n         // refund data\n         int64_t core_fee_refund_core;\n         int64_t core_fee_refund_usd;\n         int64_t usd_fee_refund_core;\n         int64_t usd_fee_refund_usd;\n         int64_t accum_on_new;\n         int64_t accum_on_fill;\n         int64_t pool_refund;\n         if( db.head_block_time() > HARDFORK_CORE_604_TIME )\n         {\n            core_fee_refund_core = order_create_fee;\n            core_fee_refund_usd = 0;\n            usd_fee_refund_core = 0;\n            usd_fee_refund_usd = usd_create_fee;\n            accum_on_new = 0;\n            accum_on_fill = usd_create_fee;\n            pool_refund = core_create_fee;\n         }\n         else if( db.head_block_time() > HARDFORK_445_TIME )\n         {\n            core_fee_refund_core = order_create_fee;\n            core_fee_refund_usd = 0;\n            usd_fee_refund_core = core_create_fee;\n            usd_fee_refund_usd = 0;\n            accum_on_new = usd_create_fee;\n            accum_on_fill = 0;\n            pool_refund = 0;\n         }\n         else\n         {\n            core_fee_refund_core = 0;\n            core_fee_refund_usd = 0;\n            usd_fee_refund_core = 0;\n            usd_fee_refund_usd = 0;\n            accum_on_new = usd_create_fee;\n            accum_on_fill = 0;\n            pool_refund = 0;\n         }\n         // refund data related to order update\n         int64_t core_update_fee_refund_core = order_update_fee;\n         int64_t core_update_fee_refund_usd = 0;\n         int64_t usd_update_fee_refund_core = 0;\n         int64_t usd_update_fee_refund_usd = usd_update_fee;\n         int64_t accum_on_fill_after_update = usd_update_fee;\n         int64_t pool_refund_after_update = core_update_fee;\n\n         int64_t update_net_fee = order_cancel_fee * order_update_fee / order_create_fee;\n\n         // Check non-overlapping\n         // Alice creates order\n         // Bob creates order which doesn't match\n         BOOST_TEST_MESSAGE( \"Creating non-overlapping orders\" );\n         BOOST_TEST_MESSAGE( \"Creating ao1\" );\n         limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(1000, usd_id), exp )->get_id();\n\n         alice_bc -= order_create_fee;\n         alice_bc -= 1000;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Alice updates order\n         if( after_core_hf1604 )\n         {\n            BOOST_TEST_MESSAGE( \"Update order ao1\" );\n            update_limit_order( ao1_id, {}, asset(100), {}, cer );\n\n            alice_bc -= 100; // delta\n            alice_bu -= usd_update_fee; // fee\n            pool_b -= core_update_fee; // pool fee\n\n            alice_bc += order_create_fee; // refund\n            alice_bc -= std::min( order_create_fee, update_net_fee ); // charge a fee (adjusted cancel_fee, capped)\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n            BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n            BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n         }\n\n         // Alice cancels order\n         if( !expire_order )\n         {\n            BOOST_TEST_MESSAGE( \"Cancel order ao1\" );\n            cancel_limit_order( ao1_id(db) );\n         }\n         else\n         {\n            BOOST_TEST_MESSAGE( \"Order ao1 expired\" );\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n         if( after_core_hf1604 )\n         {\n            if( !expire_order )\n               alice_bc -= order_cancel_fee; // manual cancellation always need a fee\n            else // bsip26\n            {\n               // when expired, should have core_update_fee in deferred, usd_update_fee in deferred_paid\n\n               // charge a cancellation fee in core from fee_pool, capped by deffered\n               int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_update_fee );\n               pool_b -= capped_core_cancel_fee;\n\n               // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped\n               int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_update_fee / core_update_fee;\n               if( capped_usd_cancel_fee * core_update_fee != capped_core_cancel_fee * usd_update_fee )\n                  capped_usd_cancel_fee += 1;\n               if( capped_usd_cancel_fee > usd_update_fee )\n                  capped_usd_cancel_fee = usd_update_fee;\n               alice_bu -= capped_usd_cancel_fee;\n\n               // cancellation fee goes to accumulated fees\n               accum_b += capped_usd_cancel_fee;\n            }\n            alice_bc += 100; // delta\n            alice_bc += usd_update_fee_refund_core;\n            alice_bu += usd_update_fee_refund_usd;\n            pool_b += pool_refund_after_update;\n         }\n         else\n         {\n            if( !expire_order )\n               alice_bc -= order_cancel_fee; // manual cancellation always need a fee\n            else if( before_hardfork_445 )\n            {  // do nothing: before hard fork 445, no fee on expired order\n            }\n            else if( !after_bsip26 )\n            {\n               // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee\n               alice_bc -= std::min( order_cancel_fee, order_create_fee );\n            }\n            else // bsip26\n            {\n               // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee\n               alice_bc -= std::min( order_cancel_fee, order_create_fee );\n            }\n            alice_bc += core_fee_refund_core;\n            alice_bu += core_fee_refund_usd;\n         }\n         alice_bc += 1000;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         BOOST_TEST_MESSAGE( \"Creating bo1\" );\n         limit_order_id_type bo1_id = create_sell_order(   bob_id, asset(500, usd_id), asset(1000), exp, cer )\n                                         ->get_id();\n\n         bob_bu -= usd_create_fee;\n         bob_bu -= 500;\n         pool_b -= core_create_fee;\n         accum_b += accum_on_new;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Bob updates order\n         if( after_core_hf1604 )\n         {\n            BOOST_TEST_MESSAGE( \"Update order bo1\" );\n            update_limit_order( bo1_id, {}, asset(100, usd_id) );\n\n            bob_bu -= 100; // delta\n            bob_bc -= order_update_fee; // fee\n\n            // there should have core_create_fee in deferred, usd_create_fee in deferred_paid\n\n            // refund\n            pool_b += core_create_fee;\n            bob_bu += usd_create_fee;\n\n            // charge an adjusted cancellation fee in core from fee_pool, capped by deffered\n            int64_t capped_core_cancel_fee = std::min( update_net_fee, core_create_fee );\n            pool_b -= capped_core_cancel_fee;\n\n            // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped\n            int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee;\n            if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee )\n               capped_usd_cancel_fee += 1;\n            if( capped_usd_cancel_fee > usd_create_fee )\n               capped_usd_cancel_fee = usd_create_fee;\n            bob_bu -= capped_usd_cancel_fee;\n\n            // cancellation fee goes to accumulated fees\n            accum_b += capped_usd_cancel_fee;\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n            BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n            BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n         }\n\n         // Bob cancels order\n         if( !expire_order )\n         {\n            BOOST_TEST_MESSAGE( \"Cancel order bo1\" );\n            cancel_limit_order( bo1_id(db) );\n         }\n         else\n         {\n            BOOST_TEST_MESSAGE( \"Order bo1 expired\" );\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n         if( after_core_hf1604 )\n         {\n            if( !expire_order )\n               bob_bc -= order_cancel_fee; // manual cancellation always need a fee\n            else\n            {\n               // charge a cancellation fee in core, capped by deffered_fee which is order_update_fee\n               bob_bc -= std::min( order_cancel_fee, order_update_fee );\n            }\n            bob_bu += 100; // delta\n            bob_bc += core_update_fee_refund_core;\n            bob_bu += core_update_fee_refund_usd;\n         }\n         else\n         {\n            if( !expire_order )\n               bob_bc -= order_cancel_fee; // manual cancellation always need a fee\n            else if( before_hardfork_445 )\n            {  // do nothing: before hard fork 445, no fee on expired order\n            }\n            else if( !after_bsip26 )\n            {\n               // charge a cancellation fee in core, capped by deffered_fee which is core_create_fee\n               bob_bc -= std::min( order_cancel_fee, core_create_fee );\n            }\n            else // bsip26\n            {\n               // when expired, should have core_create_fee in deferred, usd_create_fee in deferred_paid\n\n               // charge a cancellation fee in core from fee_pool, capped by deffered\n               int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_create_fee );\n               pool_b -= capped_core_cancel_fee;\n\n               // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped\n               int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee;\n               if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee )\n                  capped_usd_cancel_fee += 1;\n               if( capped_usd_cancel_fee > usd_create_fee )\n                  capped_usd_cancel_fee = usd_create_fee;\n               bob_bu -= capped_usd_cancel_fee;\n\n               // cancellation fee goes to accumulated fees\n               accum_b += capped_usd_cancel_fee;\n            }\n            bob_bc += usd_fee_refund_core;\n            bob_bu += usd_fee_refund_usd;\n            pool_b += pool_refund; // bo1\n         }\n         bob_bu += 500;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n\n         // Check partial fill\n         BOOST_TEST_MESSAGE( \"Creating ao2\" );\n         const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer );\n         const limit_order_id_type ao2_id = ao2->get_id();\n\n         // data after order created\n         alice_bc -= 1000;\n         alice_bu -= usd_create_fee;\n         pool_b -= core_create_fee;\n         accum_b += accum_on_new;\n\n         // Alice updates order\n         if( after_core_hf1604 )\n         {\n            BOOST_TEST_MESSAGE( \"Update order ao2\" );\n            update_limit_order( ao2_id, {}, asset(100), {}, cer );\n\n            alice_bc -= 100; // delta\n            alice_bu -= usd_update_fee; // fee\n            pool_b -= core_update_fee; // pool fee\n\n            // there should have core_create_fee in deferred, usd_create_fee in deferred_paid\n\n            // refund\n            pool_b += core_create_fee;\n            alice_bu += usd_create_fee;\n\n            // charge an adjusted cancellation fee in core from fee_pool, capped by deffered\n            int64_t capped_core_cancel_fee = std::min( update_net_fee, core_create_fee );\n            pool_b -= capped_core_cancel_fee;\n\n            // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped\n            int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee;\n            if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee )\n               capped_usd_cancel_fee += 1;\n            if( capped_usd_cancel_fee > usd_create_fee )\n               capped_usd_cancel_fee = usd_create_fee;\n            alice_bu -= capped_usd_cancel_fee;\n\n            // cancellation fee goes to accumulated fees\n            accum_b += capped_usd_cancel_fee;\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n            BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n            BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n         }\n\n         // Alice updates order again\n         if( after_core_hf1604 )\n         {\n            BOOST_TEST_MESSAGE( \"Update order ao2 again\" );\n            update_limit_order( ao2_id, {}, asset(100), {}, cer );\n\n            alice_bc -= 100; // delta\n            alice_bu -= usd_update_fee; // fee\n            pool_b -= core_update_fee; // pool fee\n\n            // there should have core_update_fee in deferred, usd_update_fee in deferred_paid\n\n            // refund\n            pool_b += core_update_fee;\n            alice_bu += usd_update_fee;\n\n            // charge an adjusted cancellation fee in core from fee_pool, capped by deffered\n            int64_t capped_core_cancel_fee = std::min( update_net_fee, core_update_fee );\n            pool_b -= capped_core_cancel_fee;\n\n            // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped\n            int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_update_fee / core_update_fee;\n            if( capped_usd_cancel_fee * core_update_fee != capped_core_cancel_fee * usd_update_fee )\n               capped_usd_cancel_fee += 1;\n            if( capped_usd_cancel_fee > usd_update_fee )\n               capped_usd_cancel_fee = usd_update_fee;\n            alice_bu -= capped_usd_cancel_fee;\n\n            // cancellation fee goes to accumulated fees\n            accum_b += capped_usd_cancel_fee;\n\n            BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n            BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n            BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n            BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n            BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n         }\n\n         BOOST_TEST_MESSAGE( \"Creating bo2, partially fill ao2\" );\n         const limit_order_object* bo2 = create_sell_order(   bob_id, asset(100, usd_id), asset(500) );\n\n         BOOST_CHECK( db.find( ao2_id ) != nullptr );\n         BOOST_CHECK( bo2 == nullptr );\n\n         // data after order created\n         bob_bc -= order_create_fee;\n         bob_bu -= 100;\n\n         // data after order filled\n         alice_bu += 100;\n         bob_bc += 500;\n         if( after_core_hf1604 )\n            accum_b += accum_on_fill_after_update; // ao2\n         else\n            accum_b += accum_on_fill; // ao2\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // cancel Alice order, show that entire deferred_fee was consumed by partial match\n         if( !expire_order )\n         {\n            BOOST_TEST_MESSAGE( \"Cancel order ao2\" );\n            cancel_limit_order( *ao2 );\n         }\n         else\n         {\n            BOOST_TEST_MESSAGE( \"Order ao2 expired\" );\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n\n         if( !expire_order )\n            alice_bc -= order_cancel_fee;\n         // else do nothing:\n         //         before hard fork 445, no fee when order is expired;\n         //         after hard fork 445, when partially filled order expired, order cancel fee is capped at 0\n         alice_bc += 500;\n         if( after_core_hf1604 )\n            alice_bc += 200; // delta * 2\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Check multiple fill\n         // Alice creating multiple orders\n         BOOST_TEST_MESSAGE( \"Creating ao31-ao35\" );\n         const limit_order_object* ao31 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer);\n         const limit_order_object* ao32 = create_sell_order( alice_id, asset(1000), asset(2000,usd_id), max_exp, cer);\n         const limit_order_object* ao33 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer);\n         const limit_order_object* ao34 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer);\n         const limit_order_object* ao35 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer);\n\n         const limit_order_id_type ao31id = ao31->get_id();\n         const limit_order_id_type ao32id = ao32->get_id();\n         const limit_order_id_type ao33id = ao33->get_id();\n         const limit_order_id_type ao34id = ao34->get_id();\n         const limit_order_id_type ao35id = ao35->get_id();\n\n         alice_bc -= 1000 * 5;\n         alice_bu -= usd_create_fee * 5;\n         pool_b -= core_create_fee * 5;\n         accum_b += accum_on_new * 5;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Bob creating an order matching multiple Alice's orders\n         BOOST_TEST_MESSAGE( \"Creating bo31, completely fill ao31 and ao33, partially fill ao34\" );\n         const limit_order_object* bo31 = create_sell_order(   bob_id, asset(500, usd_id), asset(2500), exp );\n\n         BOOST_CHECK( db.find( ao31id ) == nullptr );\n         BOOST_CHECK( db.find( ao32id ) != nullptr );\n         BOOST_CHECK( db.find( ao33id ) == nullptr );\n         BOOST_CHECK( db.find( ao34id ) != nullptr );\n         BOOST_CHECK( db.find( ao35id ) != nullptr );\n         BOOST_CHECK( bo31 == nullptr );\n\n         // data after order created\n         bob_bc -= order_create_fee;\n         bob_bu -= 500;\n\n         // data after order filled\n         alice_bu += 500;\n         bob_bc += 2500;\n         accum_b += accum_on_fill * 3; // ao31, ao33, ao34\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // Bob creating an order matching multiple Alice's orders\n         BOOST_TEST_MESSAGE( \"Creating bo32, completely fill partially filled ao34 and new ao35, leave on market\" );\n         const limit_order_object* bo32 = create_sell_order(   bob_id, asset(500, usd_id), asset(2500), exp );\n\n         BOOST_CHECK( db.find( ao31id ) == nullptr );\n         BOOST_CHECK( db.find( ao32id ) != nullptr );\n         BOOST_CHECK( db.find( ao33id ) == nullptr );\n         BOOST_CHECK( db.find( ao34id ) == nullptr );\n         BOOST_CHECK( db.find( ao35id ) == nullptr );\n         BOOST_CHECK( bo32 != nullptr );\n\n         // data after order created\n         bob_bc -= order_create_fee;\n         bob_bu -= 500;\n\n         // data after order filled\n         alice_bu += 300;\n         bob_bc += 1500;\n         accum_b += accum_on_fill; // ao35\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // cancel Bob order, show that entire deferred_fee was consumed by partial match\n         if( !expire_order )\n         {\n            BOOST_TEST_MESSAGE( \"Cancel order bo32\" );\n            cancel_limit_order( *bo32 );\n         }\n         else\n         {\n            BOOST_TEST_MESSAGE( \"Order bo32 expired\" );\n            // empty accounts before generate block, to test if it will fail when charging order cancel fee\n            transfer( alice_id, account_id_type(), asset(alice_bc, core_id) );\n            transfer( alice_id, account_id_type(), asset(alice_bu,  usd_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bc, core_id) );\n            transfer(   bob_id, account_id_type(), asset(  bob_bu,  usd_id) );\n            // generate a new block so one or more order will expire\n            generate_block( skip );\n            ++blocks_generated;\n            enable_fees();\n            change_fees( new_fees );\n            set_expiration( db, trx );\n            exp = db.head_block_time();\n            usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n            // restore account balances\n            transfer( account_id_type(), alice_id, asset(alice_bc, core_id) );\n            transfer( account_id_type(), alice_id, asset(alice_bu,  usd_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bc, core_id) );\n            transfer( account_id_type(),   bob_id, asset(  bob_bu,  usd_id) );\n         }\n\n         if( !expire_order )\n            bob_bc -= order_cancel_fee;\n         // else do nothing:\n         //         before hard fork 445, no fee when order is expired;\n         //         after hard fork 445, when partially filled order expired, order cancel fee is capped at 0\n         bob_bu += 200;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // cancel Alice order, will refund after hard fork 445\n         BOOST_TEST_MESSAGE( \"Cancel order ao32\" );\n         cancel_limit_order( ao32id( db ) );\n\n         alice_bc -= order_cancel_fee;\n         alice_bc += 1000;\n         alice_bc += usd_fee_refund_core;\n         alice_bu += usd_fee_refund_usd;\n         pool_b += pool_refund;\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n         BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n         BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n         BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n         BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n         // undo above tx's and reset\n         BOOST_TEST_MESSAGE( \"Clean up\" );\n         generate_block( skip );\n         ++blocks_generated;\n         while( blocks_generated > 0 )\n         {\n            db.pop_block();\n            --blocks_generated;\n         }\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test )\n{ // create orders before hard fork, cancel them after hard fork\n   try\n   {\n      ACTORS((alice)(bob)(izzy));\n\n      int64_t alice_b0 = 1000000, bob_b0 = 1000000;\n      int64_t pool_0 = 1000000, accum_0 = 0;\n\n      transfer( account_id_type(), alice_id, asset(alice_b0) );\n      transfer( account_id_type(), bob_id, asset(bob_b0) );\n\n      asset_id_type core_id = asset_id_type();\n      int64_t cer_core_amount = 1801;\n      int64_t cer_usd_amount = 3;\n      price tmp_cer( asset( cer_core_amount ), asset( cer_usd_amount, asset_id_type(1) ) );\n      const auto& usd_obj = create_user_issued_asset( \"IZZYUSD\", izzy_id(db), charge_market_fee, tmp_cer );\n      asset_id_type usd_id = usd_obj.get_id();\n      issue_uia( alice_id, asset( alice_b0, usd_id ) );\n      issue_uia( bob_id, asset( bob_b0, usd_id ) );\n\n      fund_fee_pool( committee_account( db ), usd_obj, pool_0 );\n\n      int64_t order_create_fee = 547;\n      int64_t order_cancel_fee = 139;\n      int64_t usd_create_fee = order_create_fee * cer_usd_amount / cer_core_amount;\n      if( usd_create_fee * cer_core_amount != order_create_fee * cer_usd_amount ) usd_create_fee += 1;\n      int64_t usd_cancel_fee = order_cancel_fee * cer_usd_amount / cer_core_amount;\n      if( usd_cancel_fee * cer_core_amount != order_cancel_fee * cer_usd_amount ) usd_cancel_fee += 1;\n      int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount;\n      int64_t core_cancel_fee = usd_cancel_fee * cer_core_amount / cer_usd_amount;\n      BOOST_CHECK( core_cancel_fee >= order_cancel_fee );\n\n      uint32_t skip = database::skip_witness_signature\n                    | database::skip_transaction_signatures\n                    | database::skip_transaction_dupe_check\n                    | database::skip_block_size_check\n                    | database::skip_tapos_check\n                    | database::skip_merkle_check\n                    ;\n\n      generate_block( skip );\n\n      fee_parameters::flat_set_type new_fees;\n      {\n         limit_order_create_operation::fee_params_t create_fee_params;\n         create_fee_params.fee = order_create_fee;\n         new_fees.insert( create_fee_params );\n      }\n      {\n         limit_order_cancel_operation::fee_params_t cancel_fee_params;\n         cancel_fee_params.fee = order_cancel_fee;\n         new_fees.insert( cancel_fee_params );\n      }\n      {\n         transfer_operation::fee_params_t transfer_fee_params;\n         transfer_fee_params.fee = 0;\n         transfer_fee_params.price_per_kbyte = 0;\n         new_fees.insert( transfer_fee_params );\n      }\n\n      // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation\n      // so we have to do it every time we stop generating/popping blocks and start doing tx's\n      enable_fees();\n      change_fees( new_fees );\n\n      // AAAAGGHH create_sell_order reads trx.expiration #469\n      set_expiration( db, trx );\n\n      // prepare params\n      const chain_parameters& params = db.get_global_properties().parameters;\n      time_point_sec max_exp = time_point_sec::maximum();\n      time_point_sec exp = HARDFORK_CORE_604_TIME + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3 );\n      time_point_sec exp1 = HARDFORK_CORE_604_TIME + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 13 );\n      time_point_sec exp2 = HARDFORK_CORE_604_TIME + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 23 );\n      price cer = usd_id( db ).options.core_exchange_rate;\n      const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n\n      // balance data\n      int64_t alice_bc = alice_b0, bob_bc = bob_b0; // core balance\n      int64_t alice_bu = alice_b0, bob_bu = bob_b0; // usd balance\n      int64_t pool_b = pool_0, accum_b = accum_0;\n\n      // prepare orders\n      BOOST_TEST_MESSAGE( \"Creating orders those will never match: ao1, ao2, bo1, bo2 ..\" );\n      // ao1: won't expire, won't match, fee in core\n      limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(100000, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao1_id ) != nullptr );\n      // ao2: will expire, won't match, fee in core\n      limit_order_id_type ao2_id = create_sell_order( alice_id, asset(800), asset(100000, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao2_id ) != nullptr );\n      // bo1: won't expire, won't match, fee in usd\n      limit_order_id_type bo1_id = create_sell_order(   bob_id, asset(1000, usd_id), asset(100000), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo1_id ) != nullptr );\n      // bo2: will expire, won't match, fee in usd\n      limit_order_id_type bo2_id = create_sell_order(   bob_id, asset(800, usd_id), asset(100000), exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo2_id ) != nullptr );\n\n      alice_bc -= order_create_fee * 2;\n      alice_bc -= 1000;\n      alice_bc -= 800;\n      bob_bu -= usd_create_fee * 2;\n      bob_bu -= 1000;\n      bob_bu -= 800;\n      pool_b -= core_create_fee * 2;\n      accum_b += usd_create_fee * 2;\n      int64_t ao1_remain = 1000;\n      int64_t ao2_remain = 800;\n      int64_t bo1_remain = 1000;\n      int64_t bo2_remain = 800;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao3: won't expire, partially match before hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao3 ..\" ); // 1:30\n      limit_order_id_type ao3_id = create_sell_order( alice_id, asset(900), asset(27000, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao3_id ) != nullptr );\n      create_sell_order( bob_id, asset(6000, usd_id), asset(200) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 900;\n      alice_bu += 6000;\n      bob_bc -= order_create_fee;\n      bob_bu -= 6000;\n      bob_bc += 200;\n      int64_t ao3_remain = 900 - 200;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao4: will expire, will partially match before hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao4 ..\" ); // 1:20\n      limit_order_id_type ao4_id = create_sell_order( alice_id, asset(700), asset(14000, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao4_id ) != nullptr );\n      create_sell_order( bob_id, asset(2000, usd_id), asset(100) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 700;\n      alice_bu += 2000;\n      bob_bc -= order_create_fee;\n      bob_bu -= 2000;\n      bob_bc += 100;\n      int64_t ao4_remain = 700 - 100;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo3: won't expire, will partially match before hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo3 ..\" ); // 1:30\n      limit_order_id_type bo3_id = create_sell_order(   bob_id, asset(500, usd_id), asset(15000), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo3_id ) != nullptr );\n      create_sell_order( alice_id, asset(4500), asset(150, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 4500;\n      alice_bu += 150;\n      bob_bu -= usd_create_fee;\n      bob_bu -= 500;\n      bob_bc += 4500;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo3_remain = 500 - 150;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo4: will expire, will partially match before hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo4 ..\" ); // 1:20\n      limit_order_id_type bo4_id = create_sell_order(   bob_id, asset(300, usd_id), asset(6000), exp, cer )->get_id();\n      BOOST_CHECK( db.find( bo4_id ) != nullptr );\n      create_sell_order( alice_id, asset(1400), asset(70, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 1400;\n      alice_bu += 70;\n      bob_bu -= usd_create_fee;\n      bob_bu -= 300;\n      bob_bc += 1400;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo4_remain = 300 - 70;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n\n      // ao11: won't expire, partially match after hard fork core-604, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao11 ..\" ); // 1:18\n      limit_order_id_type ao11_id = create_sell_order( alice_id, asset(510), asset(9180, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao11_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 510;\n      int64_t ao11_remain = 510;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao12: will expire, partially match after hard fork core-604, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao12 ..\" ); // 1:16\n      limit_order_id_type ao12_id = create_sell_order( alice_id, asset(256), asset(4096, usd_id), exp2 )->get_id();\n      BOOST_CHECK( db.find( ao12_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 256;\n      int64_t ao12_remain = 256;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo11: won't expire, partially match after hard fork core-604, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo11 ..\" ); // 1:18\n      limit_order_id_type bo11_id = create_sell_order(   bob_id, asset(388, usd_id), asset(6984), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo11_id ) != nullptr );\n\n      bob_bu -= usd_create_fee;\n      bob_bu -= 388;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo11_remain = 388;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo12: will expire, partially match after hard fork core-604, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo12 ..\" ); // 1:17\n      limit_order_id_type bo12_id = create_sell_order(   bob_id, asset(213, usd_id), asset(3621), exp2, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo12_id ) != nullptr );\n\n      bob_bu -= usd_create_fee;\n      bob_bu -= 213;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo12_remain = 213;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao5: won't expire, partially match after hard fork 445, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao5 ..\" ); // 1:15\n      limit_order_id_type ao5_id = create_sell_order( alice_id, asset(606), asset(9090, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao5_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 606;\n      int64_t ao5_remain = 606;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao6: will expire, partially match after hard fork 445, fee in core\n      if( false ) { // only can have either ao5 or ao6, can't have both\n      BOOST_TEST_MESSAGE( \"Creating order ao6 ..\" ); // 3:40 = 1:13.33333\n      limit_order_id_type ao6_id = create_sell_order( alice_id, asset(333), asset(4440, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao6_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 333;\n      // int64_t ao6_remain = 333; // only can have either ao5 or ao6, can't have both\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n      }\n\n      // bo5: won't expire, partially match after hard fork 445, fee in usd\n      if( false ) { // only can have either bo5 or bo6, can't have both\n      BOOST_TEST_MESSAGE( \"Creating order bo5 ..\" ); // 1:16\n      limit_order_id_type bo5_id = create_sell_order(   bob_id, asset(255, usd_id), asset(4080), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo5_id ) != nullptr );\n\n      bob_bu -= usd_create_fee;\n      bob_bu -= 255;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      //int64_t bo5_remain = 255; // only can have either bo5 or bo6, can't have both\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n      }\n\n      // bo6: will expire, partially match after hard fork 445, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo6 ..\" ); // 1:10\n      limit_order_id_type bo6_id = create_sell_order(   bob_id, asset(127, usd_id), asset(1270), exp, cer )->get_id();\n      BOOST_CHECK( db.find( bo6_id ) != nullptr );\n      BOOST_CHECK( db.find( bo6_id ) != nullptr );\n\n      bob_bu -= usd_create_fee;\n      bob_bu -= 127;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo6_remain = 127;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block so the orders will be in db before hard fork 445\n      BOOST_TEST_MESSAGE( \"Generating blocks, passing hard fork 445 ...\" );\n      generate_block( skip );\n\n      // generate blocks util hard fork 445\n      generate_blocks( HARDFORK_445_TIME, true, skip );\n      generate_block( skip );\n\n      // nothing will change\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // partially fill ao6\n      if( false ) { // only can have either ao5 or ao6, can't have both\n      BOOST_TEST_MESSAGE( \"Partially fill ao6 ..\" ); // 3:40\n      create_sell_order( bob_id, asset(880, usd_id), asset(66) );\n\n      alice_bu += 880;\n      bob_bc -= order_create_fee;\n      bob_bu -= 880;\n      bob_bc += 66;\n      //ao6_remain -= 66;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n      }\n\n      // partially fill bo6\n      BOOST_TEST_MESSAGE( \"Partially fill bo6 ..\" ); // 1:10\n      create_sell_order( alice_id, asset(590), asset(59, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 590;\n      alice_bu += 59;\n      bob_bc += 590;\n      bo6_remain -= 59;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill ao5\n      BOOST_TEST_MESSAGE( \"Partially fill ao5 ..\" ); // 1:15\n      create_sell_order( bob_id, asset(930, usd_id), asset(62) );\n\n      alice_bu += 930;\n      bob_bc -= order_create_fee;\n      bob_bu -= 930;\n      bob_bc += 62;\n      ao5_remain -= 62;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo5\n      if( false ) { // only can have either bo5 or bo6, can't have both\n      BOOST_TEST_MESSAGE( \"Partially fill bo5 ..\" ); // 1:16\n      create_sell_order( alice_id, asset(240), asset(15, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 240;\n      alice_bu += 15;\n      bob_bc += 240;\n      //bo5_remain -= 15;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n      }\n\n      // prepare more orders\n      BOOST_TEST_MESSAGE( \"Creating more orders those will never match: ao7, ao8, bo7, bo8 ..\" ); // ~ 1:100\n      // ao7: won't expire, won't match, fee in core\n      limit_order_id_type ao7_id = create_sell_order( alice_id, asset(1003), asset(100000, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao7_id ) != nullptr );\n      // ao8: will expire, won't match, fee in core\n      limit_order_id_type ao8_id = create_sell_order( alice_id, asset(803), asset(100000, usd_id), exp1 )->get_id();\n      BOOST_CHECK( db.find( ao8_id ) != nullptr );\n      // bo7: won't expire, won't match, fee in usd\n      limit_order_id_type bo7_id = create_sell_order(   bob_id, asset(1003, usd_id), asset(100000), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo7_id ) != nullptr );\n      // bo8: will expire, won't match, fee in usd\n      limit_order_id_type bo8_id = create_sell_order(   bob_id, asset(803, usd_id), asset(100000), exp1, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo8_id ) != nullptr );\n\n      alice_bc -= order_create_fee * 2;\n      alice_bc -= 1003;\n      alice_bc -= 803;\n      bob_bu -= usd_create_fee * 2;\n      bob_bu -= 1003;\n      bob_bu -= 803;\n      pool_b -= core_create_fee * 2;\n      accum_b += usd_create_fee * 2;\n      int64_t ao7_remain = 1003;\n      int64_t ao8_remain = 803;\n      int64_t bo7_remain = 1003;\n      int64_t bo8_remain = 803;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao9: won't expire, partially match before hard fork core-604, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao9 ..\" ); // 1:3\n      limit_order_id_type ao9_id = create_sell_order( alice_id, asset(909), asset(2727, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao9_id ) != nullptr );\n      create_sell_order( bob_id, asset(606, usd_id), asset(202) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 909;\n      alice_bu += 606;\n      bob_bc -= order_create_fee;\n      bob_bu -= 606;\n      bob_bc += 202;\n      int64_t ao9_remain = 909 - 202;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao10: will expire, will partially match before hard fork core-604, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao10 ..\" ); // 1:2\n      limit_order_id_type ao10_id = create_sell_order( alice_id, asset(707), asset(1414, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao10_id ) != nullptr );\n      create_sell_order( bob_id, asset(202, usd_id), asset(101) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 707;\n      alice_bu += 202;\n      bob_bc -= order_create_fee;\n      bob_bu -= 202;\n      bob_bc += 101;\n      int64_t ao10_remain = 707 - 101;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo9: won't expire, will partially match before hard fork core-604, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo9 ..\" ); // 1:3\n      limit_order_id_type bo9_id = create_sell_order(   bob_id, asset(505, usd_id), asset(1515), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo9_id ) != nullptr );\n      create_sell_order( alice_id, asset(453), asset(151, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 453;\n      alice_bu += 151;\n      bob_bu -= usd_create_fee;\n      bob_bu -= 505;\n      bob_bc += 453;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo9_remain = 505 - 151;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo10: will expire, will partially match before hard fork core-604, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo10 ..\" ); // 1:2\n      limit_order_id_type bo10_id = create_sell_order(   bob_id, asset(302, usd_id), asset(604), exp, cer )->get_id();\n      BOOST_CHECK( db.find( bo10_id ) != nullptr );\n      create_sell_order( alice_id, asset(142), asset(71, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 142;\n      alice_bu += 71;\n      bob_bu -= usd_create_fee;\n      bob_bu -= 302;\n      bob_bc += 142;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo10_remain = 302 - 71;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao13: won't expire, partially match after hard fork core-604, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao13 ..\" ); // 1:1.5\n      limit_order_id_type ao13_id = create_sell_order( alice_id, asset(424), asset(636, usd_id) )->get_id();\n      BOOST_CHECK( db.find( ao13_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 424;\n      int64_t ao13_remain = 424;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // ao14: will expire, partially match after hard fork core-604, fee in core\n      BOOST_TEST_MESSAGE( \"Creating order ao14 ..\" ); // 1:1.2\n      limit_order_id_type ao14_id = create_sell_order( alice_id, asset(525), asset(630, usd_id), exp )->get_id();\n      BOOST_CHECK( db.find( ao14_id ) != nullptr );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 525;\n      int64_t ao14_remain = 525;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo13: won't expire, partially match after hard fork core-604, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo13 ..\" ); // 1:1.5\n      limit_order_id_type bo13_id = create_sell_order(   bob_id, asset(364, usd_id), asset(546), max_exp, cer )\n                                      ->get_id();\n      BOOST_CHECK( db.find( bo13_id ) != nullptr );\n\n      bob_bu -= usd_create_fee;\n      bob_bu -= 364;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo13_remain = 364;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // bo14: will expire, partially match after hard fork core-604, fee in usd\n      BOOST_TEST_MESSAGE( \"Creating order bo14 ..\" ); // 1:1.2\n      limit_order_id_type bo14_id = create_sell_order(   bob_id, asset(365, usd_id), asset(438), exp, cer )->get_id();\n      BOOST_CHECK( db.find( bo14_id ) != nullptr );\n\n      bob_bu -= usd_create_fee;\n      bob_bu -= 365;\n      pool_b -= core_create_fee;\n      accum_b += usd_create_fee;\n      int64_t bo14_remain = 365;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block so the orders will be in db before hard fork core-604\n      BOOST_TEST_MESSAGE( \"Generating blocks, passing hard fork core-604 ...\" );\n      generate_block( skip );\n\n      // generate blocks util hard fork core-604\n      generate_blocks( HARDFORK_CORE_604_TIME, true, skip );\n      generate_block( skip );\n\n      // nothing will change\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // partially fill ao14\n      BOOST_TEST_MESSAGE( \"Partially fill ao14 ..\" ); // 1:1.2\n      create_sell_order( bob_id, asset(72, usd_id), asset(60) );\n\n      alice_bu += 72;\n      bob_bc -= order_create_fee;\n      bob_bu -= 72;\n      bob_bc += 60;\n      ao14_remain -= 60;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo14\n      BOOST_TEST_MESSAGE( \"Partially fill bo14 ..\" ); // 1:1.2\n      create_sell_order( alice_id, asset(66), asset(55, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 66;\n      alice_bu += 55;\n      bob_bc += 66;\n      bo14_remain -= 55;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block to save the changes\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate more blocks, so some orders will expire\n      generate_blocks( exp, true, skip );\n\n      // don't refund fee, only refund remaining funds, for:\n      // * orders created before hard fork 445 : ao2, ao4, ao6, bo2, bo4, bo6\n      // * partially filled orders (cancellation fee capped at 0) : ao10, ao14, bo10, bo14\n      BOOST_TEST_MESSAGE( \"Checking expired orders: ao2, ao4, ao6, ao10, ao14, bo2, bo4, bo6, bo10, bo14 ..\" );\n      alice_bc += ao2_remain;\n      alice_bc += ao4_remain;\n      //alice_bc += ao6_remain; // can only have ao5 or ao6 but not both\n      alice_bc += ao10_remain;\n      alice_bc += ao14_remain;\n      bob_bu += bo2_remain;\n      bob_bu += bo4_remain;\n      bob_bu += bo6_remain; // can only have bo5 or bo6 but not both\n      bob_bu += bo10_remain;\n      bob_bu += bo14_remain;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // partially fill ao13\n      BOOST_TEST_MESSAGE( \"Partially fill ao13 ..\" ); // 1:1.5\n      create_sell_order( bob_id, asset(78, usd_id), asset(52) );\n\n      alice_bu += 78;\n      bob_bc -= order_create_fee;\n      bob_bu -= 78;\n      bob_bc += 52;\n      ao13_remain -= 52;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo13\n      BOOST_TEST_MESSAGE( \"Partially fill bo13 ..\" ); // 1:1.5\n      create_sell_order( alice_id, asset(63), asset(42, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 63;\n      alice_bu += 42;\n      bob_bc += 63;\n      bo13_remain -= 42;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // don't refund fee, only refund remaining funds, for manually cancellations with an explicit fee:\n      // * orders created before hard fork 445 : ao1, ao3, ao5, bo1, bo3, bo5\n      // * partially filled orders (cancellation fee capped at 0) : ao9, ao13, bo9, bo13\n\n      // cancel ao1\n      BOOST_TEST_MESSAGE( \"Cancel order ao1 ..\" );\n      cancel_limit_order( ao1_id(db) );\n\n      alice_bc += ao1_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo1\n      BOOST_TEST_MESSAGE( \"Cancel order bo1 ..\" );\n      cancel_limit_order( bo1_id(db) );\n\n      bob_bu += bo1_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel ao3\n      BOOST_TEST_MESSAGE( \"Cancel order ao3 ..\" );\n      cancel_limit_order( ao3_id(db) );\n\n      alice_bc += ao3_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo3\n      BOOST_TEST_MESSAGE( \"Cancel order bo3 ..\" );\n      cancel_limit_order( bo3_id(db) );\n\n      bob_bu += bo3_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel ao5\n      BOOST_TEST_MESSAGE( \"Cancel order ao5 ..\" );\n      cancel_limit_order( ao5_id(db) );\n\n      alice_bc += ao5_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo5\n      if( false ) { // can only have bo5 or bo6 but not both\n      BOOST_TEST_MESSAGE( \"Cancel order bo5 ..\" );\n      //cancel_limit_order( bo5_id(db) );\n\n      //bob_bu += bo5_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n      }\n\n      // cancel ao9\n      BOOST_TEST_MESSAGE( \"Cancel order ao9 ..\" );\n      cancel_limit_order( ao9_id(db) );\n\n      alice_bc += ao9_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo9\n      BOOST_TEST_MESSAGE( \"Cancel order bo9 ..\" );\n      cancel_limit_order( bo9_id(db) );\n\n      bob_bu += bo9_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel ao13\n      BOOST_TEST_MESSAGE( \"Cancel order ao13 ..\" );\n      cancel_limit_order( ao13_id(db) );\n\n      alice_bc += ao13_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo13\n      BOOST_TEST_MESSAGE( \"Cancel order bo13 ..\" );\n      cancel_limit_order( bo13_id(db) );\n\n      bob_bu += bo13_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n\n      // generate block to save the changes\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate blocks util exp1, so some orders will expire\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n      generate_blocks( exp1, true, skip );\n\n      // orders created after hard fork 445 but before core-604, no partially filled,\n      // will refund remaining funds, and will refund create fee in core (minus cancel fee, capped)\n      BOOST_TEST_MESSAGE( \"Checking expired orders: ao8, bo8 ..\" );\n      alice_bc += ao8_remain;\n      alice_bc += std::max(order_create_fee - order_cancel_fee, int64_t(0));\n      bob_bu += bo8_remain;\n      bob_bc += std::max(core_create_fee - order_cancel_fee, int64_t(0));\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // orders created after hard fork 445 but before core-604, no partially filled,\n      // when manually cancelling (with an explicit fee),\n      // will refund remaining funds, and will refund create fee in core\n\n      // cancel ao7\n      BOOST_TEST_MESSAGE( \"Cancel order ao7 ..\" );\n      cancel_limit_order( ao7_id(db) );\n\n      alice_bc += ao7_remain;\n      alice_bc -= order_cancel_fee;\n      alice_bc += order_create_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo7\n      BOOST_TEST_MESSAGE( \"Cancel order bo7 ..\" );\n      cancel_limit_order( bo7_id(db) );\n\n      bob_bu += bo7_remain;\n      bob_bc -= order_cancel_fee;\n      bob_bc += core_create_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill ao12\n      BOOST_TEST_MESSAGE( \"Partially fill ao12 ..\" ); // 1:16\n      create_sell_order( bob_id, asset(688, usd_id), asset(43) );\n\n      alice_bu += 688;\n      bob_bc -= order_create_fee;\n      bob_bu -= 688;\n      bob_bc += 43;\n      ao12_remain -= 43;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo12\n      BOOST_TEST_MESSAGE( \"Partially fill bo12 ..\" ); // 1:17\n      create_sell_order( alice_id, asset(629), asset(37, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 629;\n      alice_bu += 37;\n      bob_bc += 629;\n      bo12_remain -= 37;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block to save the changes\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate blocks util exp2, so some orders will expire\n      generate_blocks( exp2, true, skip );\n\n      // no fee refund for orders created before hard fork 445, cancellation fee capped at 0\n      // remaining funds will be refunded\n      BOOST_TEST_MESSAGE( \"Checking expired orders: ao12, bo12 ..\" );\n      alice_bc += ao12_remain;\n      bob_bu += bo12_remain;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // prepare for new transactions\n      enable_fees();\n      change_fees( new_fees );\n      usd_stat = &usd_id( db ).dynamic_asset_data_id( db );\n      set_expiration( db, trx );\n\n      // partially fill ao11\n      BOOST_TEST_MESSAGE( \"Partially fill ao11 ..\" ); // 1:18\n      create_sell_order( bob_id, asset(1422, usd_id), asset(79) );\n\n      alice_bu += 1422;\n      bob_bc -= order_create_fee;\n      bob_bu -= 1422;\n      bob_bc += 79;\n      ao11_remain -= 79;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // partially fill bo11\n      BOOST_TEST_MESSAGE( \"Partially fill bo11 ..\" ); // 1:18\n      create_sell_order( alice_id, asset(1494), asset(83, usd_id) );\n\n      alice_bc -= order_create_fee;\n      alice_bc -= 1494;\n      alice_bu += 83;\n      bob_bc += 1494;\n      bo11_remain -= 83;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // no fee refund for orders created before hard fork 445, if manually cancelled with an explicit fee.\n      // remaining funds will be refunded\n\n      // cancel ao11\n      BOOST_TEST_MESSAGE( \"Cancel order ao11 ..\" );\n      cancel_limit_order( ao11_id(db) );\n\n      alice_bc += ao11_remain;\n      alice_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // cancel bo11\n      BOOST_TEST_MESSAGE( \"Cancel order bo11 ..\" );\n      cancel_limit_order( bo11_id(db) );\n\n      bob_bu += bo11_remain;\n      bob_bc -= order_cancel_fee;\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n      // generate block to save the changes\n      BOOST_TEST_MESSAGE( \"Generating blocks ...\" );\n      generate_block( skip );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc );\n      BOOST_CHECK_EQUAL( get_balance( alice_id,  usd_id ), alice_bu );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id, core_id ), bob_bc );\n      BOOST_CHECK_EQUAL( get_balance(   bob_id,  usd_id ), bob_bu );\n      BOOST_CHECK_EQUAL( usd_stat->fee_pool.value,          pool_b );\n      BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value,  accum_b );\n\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( stealth_fba_test )\n{\n   try\n   {\n      ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin)(tom) );\n      upgrade_to_lifetime_member(philbin_id);\n\n      generate_blocks( HARDFORK_555_TIME );\n      generate_blocks( HARDFORK_563_TIME );\n\n      // Philbin (registrar who registers Rex)\n\n      // Izzy (initial issuer of stealth asset, will later transfer to Tom)\n      // Alice, Bob, Chloe, Dan (ABCD)\n      // Rex (recycler -- buyback account for stealth asset)\n      // Tom (owner of stealth asset who will be set as top_n authority)\n\n      // Izzy creates STEALTH\n      asset_id_type stealth_id = create_user_issued_asset( \"STEALTH\", izzy_id(db),\n         disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee ).get_id();\n\n      /*\n      // this is disabled because it doesn't work, our modify() is probably being overwritten by undo\n\n      //\n      // Init blockchain with stealth ID's\n      // On a real chain, this would be done with #define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET\n      // causing the designated_asset fields of these objects to be set at genesis, but for\n      // this test we modify the db directly.\n      //\n      auto set_fba_asset = [&]( uint64_t fba_acc_id, asset_id_type asset_id )\n      {\n         db.modify( fba_accumulator_id_type(fba_acc_id)(db), [&]( fba_accumulator_object& fba )\n         {\n            fba.designated_asset = asset_id;\n         } );\n      };\n\n      set_fba_asset( fba_accumulator_id_transfer_to_blind  , stealth_id );\n      set_fba_asset( fba_accumulator_id_blind_transfer     , stealth_id );\n      set_fba_asset( fba_accumulator_id_transfer_from_blind, stealth_id );\n      */\n\n      // Izzy kills some permission bits (this somehow happened to the real STEALTH in production)\n      {\n         asset_update_operation update_op;\n         update_op.issuer = izzy_id;\n         update_op.asset_to_update = stealth_id;\n         asset_options new_options;\n         new_options = stealth_id(db).options;\n         new_options.issuer_permissions = charge_market_fee;\n         new_options.flags = disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee;\n         // after fixing #579 you should be able to delete the following line\n         new_options.core_exchange_rate = price( asset( 1, stealth_id ), asset( 1, asset_id_type() ) );\n         update_op.new_options = new_options;\n         signed_transaction tx;\n         tx.operations.push_back( update_op );\n         set_expiration( db, tx );\n         sign( tx, izzy_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      // Izzy transfers issuer duty to Tom\n      {\n         asset_update_operation update_op;\n         update_op.issuer = izzy_id;\n         update_op.asset_to_update = stealth_id;\n         update_op.new_issuer = tom_id;\n         // new_options should be optional, but isn't...the following line should be unnecessary #580\n         update_op.new_options = stealth_id(db).options;\n         signed_transaction tx;\n         tx.operations.push_back( update_op );\n         set_expiration( db, tx );\n         sign( tx, izzy_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      // Tom re-enables the permission bits to clear the flags, then clears them again\n      // Allowed by #572 when current_supply == 0\n      {\n         asset_update_operation update_op;\n         update_op.issuer = tom_id;\n         update_op.asset_to_update = stealth_id;\n         asset_options new_options;\n         new_options = stealth_id(db).options;\n         new_options.issuer_permissions = new_options.flags | charge_market_fee;\n         update_op.new_options = new_options;\n         signed_transaction tx;\n         // enable perms is one op\n         tx.operations.push_back( update_op );\n\n         new_options.issuer_permissions = charge_market_fee;\n         new_options.flags = charge_market_fee;\n         update_op.new_options = new_options;\n         // reset wrongly set flags and reset permissions can be done in a single op\n         tx.operations.push_back( update_op );\n\n         set_expiration( db, tx );\n         sign( tx, tom_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      // Philbin registers Rex who will be the asset's buyback, including sig from the new issuer (Tom)\n      account_id_type rex_id;\n      {\n         buyback_account_options bbo;\n         bbo.asset_to_buy = stealth_id;\n         bbo.asset_to_buy_issuer = tom_id;\n         bbo.markets.emplace( asset_id_type() );\n         account_create_operation create_op = make_account( \"rex\" );\n         create_op.registrar = philbin_id;\n         create_op.extensions.value.buyback_options = bbo;\n         create_op.owner = authority::null_authority();\n         create_op.active = authority::null_authority();\n\n         signed_transaction tx;\n         tx.operations.push_back( create_op );\n         set_expiration( db, tx );\n         sign( tx, philbin_private_key );\n         sign( tx, tom_private_key );\n\n         processed_transaction ptx = PUSH_TX( db, tx );\n         rex_id = ptx.operation_results.back().get< object_id_type >();\n      }\n\n      // Tom issues some asset to Alice and Bob\n      set_expiration( db, trx );  // #11\n      issue_uia( alice_id, asset( 1000, stealth_id ) );\n      issue_uia(   bob_id, asset( 1000, stealth_id ) );\n\n      // Tom sets his authority to the top_n of the asset\n      {\n         top_holders_special_authority top2;\n         top2.num_top_holders = 2;\n         top2.asset = stealth_id;\n\n         account_update_operation op;\n         op.account = tom_id;\n         op.extensions.value.active_special_authority = top2;\n         op.extensions.value.owner_special_authority = top2;\n\n         signed_transaction tx;\n         tx.operations.push_back( op );\n\n         set_expiration( db, tx );\n         sign( tx, tom_private_key );\n\n         PUSH_TX( db, tx );\n      }\n\n      // Wait until the next maintenance interval for top_n to take effect\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      // Do a blind op to add some fees to the pool.\n      fund( chloe_id(db), asset( 100000, asset_id_type() ) );\n\n      auto create_transfer_to_blind = [&]( account_id_type account, asset amount, const std::string& key ) -> transfer_to_blind_operation\n      {\n         fc::ecc::private_key blind_key = fc::ecc::private_key::regenerate( fc::sha256::hash( key+\"-privkey\" ) );\n         public_key_type blind_pub = blind_key.get_public_key();\n\n         fc::sha256 secret = fc::sha256::hash( key+\"-secret\" );\n         fc::sha256 nonce = fc::sha256::hash( key+\"-nonce\" );\n\n         transfer_to_blind_operation op;\n         blind_output blind_out;\n         blind_out.owner = authority( 1, blind_pub, 1 );\n         blind_out.commitment = fc::ecc::blind( secret, amount.amount.value );\n         blind_out.range_proof = fc::ecc::range_proof_sign( 0, blind_out.commitment, secret, nonce, 0, 0, amount.amount.value );\n\n         op.amount = amount;\n         op.from = account;\n         op.blinding_factor = fc::ecc::blind_sum( {secret}, 1 );\n         op.outputs = {blind_out};\n\n         return op;\n      };\n\n      {\n         transfer_to_blind_operation op = create_transfer_to_blind( chloe_id, asset( 5000, asset_id_type() ), \"chloe-key\" );\n         op.fee = asset( 1000, asset_id_type() );\n\n         signed_transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         sign( tx, chloe_private_key );\n\n         PUSH_TX( db, tx );\n      }\n\n      // wait until next maint interval\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      idump( ( get_operation_history( chloe_id ) ) );\n      idump( ( get_operation_history( rex_id ) ) );\n      idump( ( get_operation_history( tom_id ) ) );\n   }\n   catch( const fc::exception& e )\n   {\n      elog( \"caught exception ${e}\", (\"e\", e.to_detail_string()) );\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( defaults_test )\n{ try {\n    fee_schedule schedule;\n    const limit_order_create_operation::fee_params_t default_order_fee {};\n\n    // no fees set yet -> default\n    asset fee = schedule.calculate_fee( limit_order_create_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)default_order_fee.fee, fee.amount.value );\n\n    // fill_order fee is zero\n    fee = schedule.calculate_fee( fill_order_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value );\n\n    // execute_bid fee is zero\n    fee = schedule.calculate_fee( execute_bid_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value );\n\n    limit_order_create_operation::fee_params_t new_order_fee; new_order_fee.fee = 123;\n    // set fee + check\n    schedule.parameters.insert( new_order_fee );\n    fee = schedule.calculate_fee( limit_order_create_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)new_order_fee.fee, fee.amount.value );\n\n    // bid_collateral fee defaults to call_order_update fee\n    // call_order_update fee is unset -> default\n    const call_order_update_operation::fee_params_t default_short_fee {};\n    call_order_update_operation::fee_params_t new_short_fee; new_short_fee.fee = 123;\n    fee = schedule.calculate_fee( bid_collateral_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)default_short_fee.fee, fee.amount.value );\n\n    // set call_order_update fee + check bid_collateral fee\n    schedule.parameters.insert( new_short_fee );\n    fee = schedule.calculate_fee( bid_collateral_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)new_short_fee.fee, fee.amount.value );\n\n    // set bid_collateral fee + check\n    bid_collateral_operation::fee_params_t new_bid_fee; new_bid_fee.fee = 124;\n    schedule.parameters.insert( new_bid_fee );\n    fee = schedule.calculate_fee( bid_collateral_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)new_bid_fee.fee, fee.amount.value );\n\n    // fill_order fee is still zero\n    fee = schedule.calculate_fee( fill_order_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value );\n\n    // execute_bid fee is still zero\n    fee = schedule.calculate_fee( execute_bid_operation() );\n    BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value );\n\n  }\n  catch( const fc::exception& e )\n  {\n     elog( \"caught exception ${e}\", (\"e\", e.to_detail_string()) );\n     throw;\n  }\n}\n\nBOOST_AUTO_TEST_CASE( sub_asset_creation_fee_test )\n{ try {\n   fee_schedule schedule;\n\n   asset_create_operation::fee_params_t default_ac_fee;\n\n   asset_create_operation op;\n   op.symbol = \"TEST.SUB\";\n\n   auto op_size = fc::raw::pack_size(op);\n\n   auto expected_data_fee = op.calculate_data_fee( op_size, default_ac_fee.price_per_kbyte );\n   int64_t expected_fee = default_ac_fee.long_symbol + expected_data_fee;\n\n   // no fees set yet -> default\n   BOOST_TEST_MESSAGE(\"Testing default fee schedule\");\n   asset fee = schedule.calculate_fee( op );\n   BOOST_CHECK_EQUAL( fee.amount.value, expected_fee );\n\n   // set fee + check\n   asset_create_operation::fee_params_t ac_fee;\n   ac_fee.long_symbol = 100100;\n   ac_fee.symbol4 = 2000200;\n   ac_fee.symbol3 = 30000300;\n   ac_fee.price_per_kbyte = 1050;\n\n   schedule.parameters.insert( ac_fee );\n\n   expected_data_fee = op.calculate_data_fee( op_size, ac_fee.price_per_kbyte );\n   expected_fee = ac_fee.long_symbol + expected_data_fee;\n\n   fee = schedule.calculate_fee( op );\n   BOOST_CHECK_EQUAL( fee.amount.value, expected_fee );\n\n   // set fee for account_transfer_operation, no change on asset creation fee\n   BOOST_TEST_MESSAGE(\"Testing our fee schedule without sub-asset creation fee enabled\");\n   account_transfer_operation::fee_params_t at_fee;\n   at_fee.fee = 5500;\n\n   schedule.parameters.insert( at_fee );\n\n   fee = schedule.calculate_fee( op );\n   BOOST_CHECK_EQUAL( fee.amount.value, expected_fee );\n\n   // enable sub-asset creation fee\n   BOOST_TEST_MESSAGE(\"Testing our fee schedule with sub-asset creation fee enabled\");\n   schedule.parameters.insert( ticket_create_operation::fee_params_t() );\n\n   expected_fee = at_fee.fee + expected_data_fee;\n\n   fee = schedule.calculate_fee( op );\n   BOOST_CHECK_EQUAL( fee.amount.value, expected_fee );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( issue_429_test )\n{\n   try\n   {\n      ACTORS((alice));\n\n      transfer( committee_account, alice_id, asset( 1000000 * asset::scaled_precision( asset_id_type()(db).precision ) ) );\n\n      // make sure the database requires our fee to be nonzero\n      enable_fees();\n\n      const auto& fees = db.get_global_properties().parameters.get_current_fees();\n      auto fees_to_pay = fees.get<asset_create_operation>();\n\n      {\n         signed_transaction tx;\n         asset_create_operation op;\n         op.issuer = alice_id;\n         op.symbol = \"ALICE\";\n         op.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type( 1 ) );\n         op.fee = asset( (fees_to_pay.long_symbol + fees_to_pay.price_per_kbyte) & (~1) );\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         sign( tx, alice_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      verify_asset_supplies( db );\n\n      {\n         signed_transaction tx;\n         asset_create_operation op;\n         op.issuer = alice_id;\n         op.symbol = \"ALICE.ODD\";\n         op.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type( 1 ) );\n         op.fee = asset((fees_to_pay.long_symbol + fees_to_pay.price_per_kbyte) | 1);\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         sign( tx, alice_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      verify_asset_supplies( db );\n\n      generate_blocks( HARDFORK_CORE_429_TIME + 10 );\n\n      {\n         signed_transaction tx;\n         asset_create_operation op;\n         op.issuer = alice_id;\n         op.symbol = \"ALICE.ODDER\";\n         op.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type( 1 ) );\n         op.fee = asset((fees_to_pay.long_symbol + fees_to_pay.price_per_kbyte) | 1);\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         sign( tx, alice_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      verify_asset_supplies( db );\n   }\n   catch( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( issue_433_test )\n{\n   try\n   {\n      ACTORS((alice));\n\n      auto& core = asset_id_type()(db);\n\n      transfer( committee_account, alice_id, asset( 1000000 * asset::scaled_precision( core.precision ) ) );\n\n      const auto& myusd = create_user_issued_asset( \"MYUSD\", alice, 0 );\n      issue_uia( alice, myusd.amount( 2000000000 ) );\n\n      // make sure the database requires our fee to be nonzero\n      enable_fees();\n\n      const auto& fees = db.get_global_properties().parameters.get_current_fees();\n      const auto asset_create_fees = fees.get<asset_create_operation>();\n\n      fund_fee_pool( alice, myusd, 5*asset_create_fees.long_symbol );\n\n      asset_create_operation op;\n      op.issuer = alice_id;\n      op.symbol = \"ALICE\";\n      op.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type( 1 ) );\n      op.fee = myusd.amount( ((asset_create_fees.long_symbol + asset_create_fees.price_per_kbyte) & (~1)) );\n      signed_transaction tx;\n      tx.operations.push_back( op );\n      set_expiration( db, tx );\n      sign( tx, alice_private_key );\n      PUSH_TX( db, tx );\n\n      verify_asset_supplies( db );\n   }\n   catch( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( issue_433_indirect_test )\n{\n   try\n   {\n      ACTORS((alice));\n\n      auto& core = asset_id_type()(db);\n\n      transfer( committee_account, alice_id, asset( 1000000 * asset::scaled_precision( core.precision ) ) );\n\n      const auto& myusd = create_user_issued_asset( \"MYUSD\", alice, 0 );\n      issue_uia( alice, myusd.amount( 2000000000 ) );\n\n      // make sure the database requires our fee to be nonzero\n      enable_fees();\n\n      const auto& fees = db.get_global_properties().parameters.get_current_fees();\n      const auto asset_create_fees = fees.get<asset_create_operation>();\n\n      fund_fee_pool( alice, myusd, 5*asset_create_fees.long_symbol );\n\n      asset_create_operation op;\n      op.issuer = alice_id;\n      op.symbol = \"ALICE\";\n      op.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type( 1 ) );\n      op.fee = myusd.amount( ((asset_create_fees.long_symbol + asset_create_fees.price_per_kbyte) & (~1)) );\n\n      const auto proposal_create_fees = fees.get<proposal_create_operation>();\n      proposal_create_operation prop;\n      prop.fee_paying_account = alice_id;\n      prop.proposed_ops.emplace_back( op );\n      prop.expiration_time =  db.head_block_time() + fc::days(1);\n      prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n      object_id_type proposal_id;\n      {\n         signed_transaction tx;\n         tx.operations.push_back( prop );\n         set_expiration( db, tx );\n         sign( tx, alice_private_key );\n         proposal_id = PUSH_TX( db, tx ).operation_results.front().get<object_id_type>();\n      }\n      const proposal_object& proposal = db.get<proposal_object>( proposal_id );\n\n      const auto proposal_update_fees = fees.get<proposal_update_operation>();\n      proposal_update_operation pup;\n      pup.proposal = proposal.id;\n      pup.fee_paying_account = alice_id;\n      pup.active_approvals_to_add.insert(alice_id);\n      pup.fee = asset( proposal_update_fees.fee + proposal_update_fees.price_per_kbyte );\n      {\n         signed_transaction tx;\n         tx.operations.push_back( pup );\n         set_expiration( db, tx );\n         sign( tx, alice_private_key );\n         PUSH_TX( db, tx );\n      }\n\n      verify_asset_supplies( db );\n   }\n   catch( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( fee_change_test )\n{ try {\n\n   // At the beginning, the fee schedule is all zero\n   const auto& check_zero_fees = [&]() {\n      BOOST_REQUIRE( db.get_global_properties().parameters.current_fees );\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->scale, 0 );\n      BOOST_REQUIRE( !db.get_global_properties().parameters.current_fees->parameters.empty() );\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->parameters.begin()->get<\n                               transfer_operation::fee_params_t>().fee, 0 );\n   };\n\n   check_zero_fees();\n\n   {\n      // Try to set default fees, it should fail, because some operations aren't available\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time() );\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation cmuop;\n      cmuop.new_parameters = db.get_global_properties().parameters;\n      cmuop.new_parameters.get_mutable_fees() = fee_schedule::get_default();\n      cop.proposed_ops.emplace_back( cmuop );\n      trx.operations.push_back( cop );\n\n      // It should fail\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      trx.clear();\n   }\n\n   // The fee schedule is still all zero\n   check_zero_fees();\n\n   // Proceed to a time in the far future\n   generate_blocks( fc::time_point_sec::maximum() - 86400\n                    - db.get_global_properties().parameters.maximum_proposal_lifetime );\n   set_expiration( db, trx );\n\n   // The fee schedule is still all zero\n   check_zero_fees();\n\n   {\n      // Try to set default fees, it should succeed\n      proposal_create_operation cop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time() );\n      cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n      committee_member_update_global_parameters_operation cmuop;\n      cmuop.new_parameters = db.get_global_properties().parameters;\n      cmuop.new_parameters.get_mutable_fees() = fee_schedule::get_default();\n      cop.proposed_ops.emplace_back( cmuop );\n      trx.operations.push_back( cop );\n\n      // It should succeed\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      trx.clear();\n      proposal_id_type prop_id { ptx.operation_results[0].get<object_id_type>() };\n\n      // The fee schedule is still all zero\n      check_zero_fees();\n\n      // Approve the proposal\n      proposal_update_operation uop;\n      uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      uop.active_approvals_to_add = { get_account(\"init0\").get_id(), get_account(\"init1\").get_id(),\n                                      get_account(\"init2\").get_id(), get_account(\"init3\").get_id(),\n                                      get_account(\"init4\").get_id(), get_account(\"init5\").get_id(),\n                                      get_account(\"init6\").get_id(), get_account(\"init7\").get_id() };\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n\n      // The fee schedule is still all zero\n      check_zero_fees();\n\n      generate_blocks( prop_id( db ).expiration_time + 5 );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n      // The fee schedule is no longer all zero\n      BOOST_REQUIRE( db.get_global_properties().parameters.current_fees );\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->scale, GRAPHENE_100_PERCENT );\n      BOOST_REQUIRE( !db.get_global_properties().parameters.current_fees->parameters.empty() );\n      BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->parameters.begin()->get<\n                               transfer_operation::fee_params_t>().fee, transfer_operation::fee_params_t().fee );\n      BOOST_CHECK( transfer_operation::fee_params_t().fee != 0 );\n\n      idump( (db.get_global_properties()) );\n\n   }\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/force_settle_fee_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 Michel Santos, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nstruct force_settle_database_fixture : database_fixture {\n   force_settle_database_fixture()\n           : database_fixture() {\n   }\n\n   /**\n    * Create an operation to create a smart asset\n    * @param name Asset name\n    * @param issuer Issuer ID\n    * @param force_settlement_offset_percent Force-settlement offset percent\n    * @param force_settlement_fee_percent Force-settlement fee percent (BSIP87)\n    * @return An asset_create_operation\n    */\n   const asset_create_operation create_smart_asset_op(\n           const string &name,\n           account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */,\n           uint16_t force_settlement_offset_percent /* 100 = 1% */,\n           optional<uint16_t> force_settlement_fee_percent /* 100 = 1% */\n   ) {\n      try {\n         uint16_t market_fee_percent = 100; /*1%*/\n         uint16_t flags = charge_market_fee;\n         uint16_t precision = 2;\n         asset_id_type backing_asset = {};\n         share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n\n         asset_create_operation creator;\n         creator.issuer = issuer;\n         creator.fee = asset();\n         creator.symbol = name;\n         creator.precision = precision;\n\n         creator.common_options.max_supply = max_supply;\n         creator.common_options.market_fee_percent = market_fee_percent;\n         if (issuer == GRAPHENE_WITNESS_ACCOUNT)\n            flags |= witness_fed_asset;\n         creator.common_options.issuer_permissions = flags;\n         creator.common_options.flags = flags & ~global_settle;\n         creator.common_options.core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1));\n\n         creator.bitasset_opts = bitasset_options();\n         creator.bitasset_opts->force_settlement_offset_percent = force_settlement_offset_percent;\n         creator.bitasset_opts->short_backing_asset = backing_asset;\n         creator.bitasset_opts->extensions.value.force_settle_fee_percent = force_settlement_fee_percent;\n\n         return creator;\n\n      } FC_CAPTURE_AND_RETHROW((name)(issuer))\n   }\n\n\n   /**\n    * Create a smart asset without a force settlement fee percent\n    * @param name Asset name\n    * @param issuer Issuer ID\n    * @param force_settlement_offset_percent Force-settlement offset percent\n    * @return Asset object\n    */\n   const asset_object &create_smart_asset(\n           const string &name,\n           account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */,\n           uint16_t force_settlement_offset_percent /* = 100 */ /* 1% */\n   ) {\n      try {\n         optional<uint16_t> force_settlement_fee_percent; // Not specified\n         return create_smart_asset(name, issuer, force_settlement_offset_percent, force_settlement_fee_percent);\n      } FC_CAPTURE_AND_RETHROW((name)(issuer)(force_settlement_offset_percent))\n   }\n\n\n   /**\n    * Create a smart asset\n    * @param name Asset name\n    * @param issuer Issuer ID\n    * @param force_settlement_offset_percent Force-settlement offset percent\n    * @param force_settlement_fee_percent Force-settlement fee percent (BSIP87)\n    * @return Asset object\n    */\n   const asset_object &create_smart_asset(\n           const string &name,\n           account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */,\n           uint16_t force_settlement_offset_percent /* 100 = 1% */,\n           optional<uint16_t> force_settlement_fee_percent /* 100 = 1% */\n   ) {\n      try {\n         asset_create_operation creator = create_smart_asset_op(name, issuer, force_settlement_offset_percent,\n                                                                force_settlement_fee_percent);\n\n         trx.operations.push_back(std::move(creator));\n         trx.validate();\n         processed_transaction ptx = PUSH_TX(db, trx, ~0);\n         trx.operations.clear();\n         return db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      } FC_CAPTURE_AND_RETHROW((name)(issuer))\n   }\n};\n\n\n/**\n * Test the effects of the new force settlement fee from BSIP87\n */\nBOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture)\n\n   /**\n    * Test when one holder of a smart asset force settles (FS) their holding when there are two debtors\n    *\n    * There are three primary actors: michael, paul, rachel\n    *\n    * 1. Asset owner creates the smart coin called bitUSD\n    * 2. The feed price is 20 satoshi bitUSD for 1 satoshi Core -> 0.2 bitUSD for 0.00001 Core = 20000 bitUSD for 1 Core\n    * 3. Michael borrows 0.06 bitUSD (6 satoshis of bitUSD) from the blockchain with a high amount of collateral\n    * 4. Paul borrows 1000 bitUSD (100000 satoshis of bitUSD) from the blockchain with a low amount of collateral\n    * 5. Paul gives Rachel 200 bitUSD\n    * 6. Rachel force-settles 20 bitUSD which should be collected from Paul's debt position\n    * because of its relatively lower collateral ratio\n    *\n    * The force-settlement by Rachel should account for both the force-settlement offset fee,\n    * and the new force settlement fee from BSIP87.\n    *\n    * Michael's debt and balances should be unaffected by the activities of Paul and Rachel\n    */\n   BOOST_AUTO_TEST_CASE(force_settle_fee_1_test) {\n      try {\n         ///////\n         // Initialize the scenario\n         ///////\n         // Get around Graphene issue #615 feed expiration bug\n         generate_blocks(HARDFORK_615_TIME);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         // Advance to the when the force-settlement fee activates\n         generate_blocks(HARDFORK_CORE_BSIP87_TIME);\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         // Create actors\n         ACTORS((assetowner)(feedproducer)(paul)(michael)(rachel));\n\n         // Fund actors\n         uint64_t initial_balance_core = 10000000;\n         transfer(committee_account, assetowner.get_id(), asset(initial_balance_core));\n         transfer(committee_account, feedproducer.get_id(), asset(initial_balance_core));\n         transfer(committee_account, michael_id, asset(initial_balance_core));\n         transfer(committee_account, paul.get_id(), asset(initial_balance_core));\n\n         // 1. Create assets\n         const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee %\n         const uint16_t usd_fsf_percent = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87)\n         create_smart_asset(\"USDBIT\", assetowner.get_id(), usd_fso_percent, usd_fsf_percent);\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bitusd = get_asset(\"USDBIT\");\n         const auto &core = asset_id_type()(db);\n         asset_id_type bitusd_id = bitusd.get_id();\n         asset_id_type core_id = core.get_id();\n         const int64_t bitusd_unit = asset::scaled_precision(bitusd.precision).value; // 100 satoshi USDBIT in 1 USDBIT\n\n         // 2. Publish a feed for the smart asset\n         update_feed_producers(bitusd_id(db), {feedproducer_id});\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         // Requirement of 20 satoshi bitUSD for 1 satoshi Core -> 0.2 bitUSD for 0.00001 Core = 20000 bitUSD for 1 Core\n         current_feed.settlement_price = bitusd.amount(20) / core.amount(1);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         ///////\n         // 3. Michael borrows 0.06 bitUSD\n         ///////\n         int64_t michael_initial_usd = 6; // 0.06 USD\n         int64_t michael_initial_core = 8;\n         const call_order_object &call_michael = *borrow(michael, bitusd.amount(michael_initial_usd),\n                                                         core.amount(michael_initial_core));\n         call_order_id_type call_michael_id = call_michael.get_id();\n\n         BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core - michael_initial_core);\n\n\n         ///////\n         // 4. Paul borrows 1000 bitUSD\n         ///////\n         // Paul will borrow bitUSD by providing 2x collateral required: 2 * 1/20 = 1/10\n         int64_t paul_initial_usd = 1000 * bitusd_unit; // 100000\n         int64_t paul_initial_core = paul_initial_usd * 2 / 20; // 10000\n         const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd),\n                                                      core.amount(paul_initial_core));\n         call_order_id_type call_paul_id = call_paul.get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(paul, bitusd), paul_initial_usd);\n\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 5. Paul transfers 200 bitUSD to Rachel\n         ///////\n         int64_t rachel_initial_usd = 200 * bitusd_unit;\n         transfer(paul.get_id(), rachel.get_id(), asset(rachel_initial_usd, bitusd.get_id()));\n\n         BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd);\n\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 6. Rachel force settles 20 bitUSD\n         ///////\n         const int64_t rachel_settle_amount = 20 * bitusd_unit;\n         operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount));\n\n         force_settlement_id_type rachel_settle_id { *result.get<extendable_operation_result>()\n                                                           .value.new_objects->begin() };\n         BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount);\n\n         // Check Rachel's balance\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         BOOST_CHECK_EQUAL(paul_initial_usd, call_paul.debt.value);\n         BOOST_CHECK_EQUAL(paul_initial_core, call_paul.collateral.value);\n\n         // Check Michael's balance\n         BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core - michael_initial_core);\n\n         // Check Michael's debt to the blockchain\n         BOOST_CHECK_EQUAL(michael_initial_usd, call_michael.debt.value);\n         BOOST_CHECK_EQUAL(michael_initial_core, call_michael.collateral.value);\n\n\n         ///////\n         // Advance time and update the price feed\n         ///////\n         generate_blocks(db.head_block_time() + fc::hours(20));\n         set_expiration(db, trx);\n         trx.clear();\n\n         // The default feed and settlement expires at the same time\n         // Publish another feed to have a valid price to exit\n         publish_feed(bitusd_id(db), feedproducer_id(db), current_feed);\n\n\n         ///////\n         // Advance time to trigger the conclusion of the force settlement\n         ///////\n         generate_blocks(db.head_block_time() + fc::hours(6));\n         set_expiration(db, trx);\n         trx.clear();\n\n\n         //////\n         // Check\n         //////\n         // Rachel's settlement should have completed and should no longer be present\n         BOOST_CHECK(!db.find(rachel_settle_id));\n\n         // Check Rachel's balance\n         // Rachel redeemed some smart asset and should get the equivalent collateral amount (according to the feed price)\n         // minus the force_settlement_offset_fee - force_settlement_fee\n         // Rachel redeemed  20 USD (2000 satoshi bitUSD) and should get\n         // 100 satoshi Core - 5 satoshi Core - 2 satoshi Core; 3% * (100 - 5) = 2.85 trunacted to 2 satoshi Core\n         uint64_t rachel_settle_core = rachel_settle_amount * 1 / 20; // Settle amount * feed price\n         uint64_t rachel_fso_fee_core = rachel_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT; // 5 satoshi Core\n         uint64_t rachel_fso_remainder_core = rachel_settle_core - rachel_fso_fee_core; // 95 satoshi Core\n         uint64_t rachel_fsf_fee_core =\n                 (rachel_fso_remainder_core) * usd_fsf_percent / GRAPHENE_100_PERCENT; // 2 satoshi Core\n         uint64_t expected_rachel_core =\n                 rachel_settle_core - rachel_fso_fee_core - rachel_fsf_fee_core; // 93 satoshi Core\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), expected_rachel_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         // Rachel redeemed 20 usd from the blockchain, and the blockchain closed this amount from Paul's debt to it\n         BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount, call_paul_id(db).debt.value);\n         // The call order has the original amount of collateral less what was redeemed by Rachel\n         BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core, call_paul_id(db).collateral.value);\n\n         // Check Michael's balance\n         // Rachel's redemption should not have affected Michael's balance\n         BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core - michael_initial_core);\n\n         // Check Michael's debt to the blockchain\n         // Rachel's redemption should not have affected Michael's debt to the blockchain\n         BOOST_CHECK_EQUAL(michael_initial_usd, call_michael_id(db).debt.value);\n         BOOST_CHECK_EQUAL(michael_initial_core, call_michael_id(db).collateral.value);\n\n         // The supply of USD equals the amount borrowed/created by Paul and Michael\n         // minus the amount redeemed/destroyed by Rachel\n         BOOST_CHECK_EQUAL(bitusd_id(db).dynamic_data(db).current_supply.value,\n                           paul_initial_usd + michael_initial_usd - rachel_settle_amount);\n\n         // Check the asset owner's vesting fees\n         // The market fee reward should be zero because the market fee reward % is 0\n         const auto assetowner_fs_fees_usd = get_market_fee_reward(assetowner, bitusd);\n         BOOST_CHECK_EQUAL(assetowner_fs_fees_usd, 0);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == rachel_fsf_fee_core);\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * This test evaluates:\n    *\n    * - collecting collateral-denominated fees before and after BSIP87,\n    * - applying different force-settlement fee percentages,\n    * - accumulating fees from multiple force-settlements,\n    * - changing the backing asset of a smart asset is prohibited when there are unclaimed collateral-denominated fees.\n    *\n    * There are five actors: asset owner, paul, rachel, michael, yanna, vikram\n    *\n    * Before HARDFORK_CORE_BSIP87_TIME\n    *\n    * 1. Asset owner creates the smart coin called bitUSD\n    *\n    * NOTE: To avoid rounding issues in the test, 1 satoshi of the smart asset will be worth more than 1 satoshi\n    * of the backing asset.  This allows force settlements of the smart asset to yield more satoshis of the backing asset\n    * with controllable truncation and rounding that will not affect the tests.\n    * 2. The feed price is 1 satoshi bitUSD for 20 satoshi Core = 0.01 bitUSD for 0.00020 Core = 50 bitUSD for 1 Core\n    *\n    * 3. Paul borrows 100 bitUSD (10000 satoshis of bitUSD) from the blockchain\n    * 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD\n    * 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position\n    * 6. Asset owner attempts and fails to claim the collateral fees\n    *\n    *\n    * 7. Activate HARDFORK_CORE_BSIP87_TIME\n    *\n    *\n    * After HARDFORK_CORE_BSIP87_TIME\n    *\n    * 8. Paul gives Michael 30 bitUSD and retains 50 bitUSD\n    * 9. Michael force-settles 5 bitUSD which should be collected from Paul's debt position\n    *\n    * 10. Asset owner sets the force-fee percentage to 3%\n    * 11. Paul gives Yanna 40 bitUSD and retains 10 bitUSD\n    * 12. Yanna force-settles 10 bitUSD which should be collected from Paul's debt position\n    *\n    * 13. Asset owner updates the force-settlement fee to 4%\n    * 14. Paul gives Vikram 10 bitUSD and retains 0 bitUSD\n    * 15. Vikram force-settles 10 bitUSD which should be collected from Paul's debt position\n    *\n    * 16. Asset owner attempts and fails to change the backing of the smart asset because of its outstanding supply\n    * 17. All current holders of bitUSD close their bitUSD positions\n    * 18. Asset owner attempts and fails to change the backing of the smart asset because of unclaimed collateral fees\n    * 19. Asset owner claims all of the unclaimed collateral fees\n    * 20. Asset owner attempts and succeeds in changing the backing of the smart asset\n    */\n   BOOST_AUTO_TEST_CASE(force_settle_fee_2_test) {\n      try {\n         ///////\n         // Initialize the scenario\n         ///////\n         // Get around Graphene issue #615 feed expiration bug\n         generate_blocks(HARDFORK_615_TIME);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Create actors\n         ACTORS((assetowner)(feedproducer)(paul)(rachel)(michael)(yanna)(vikram));\n\n         // Fund actors\n         uint64_t initial_balance_core = 10000000;\n         transfer(committee_account, assetowner.get_id(), asset(initial_balance_core));\n         transfer(committee_account, feedproducer.get_id(), asset(initial_balance_core));\n         transfer(committee_account, michael_id, asset(initial_balance_core));\n         transfer(committee_account, paul.get_id(), asset(initial_balance_core));\n\n         ///////\n         // 1. Create assets\n         ///////\n         const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee %\n         const uint16_t usd_fsf_percent_0 = 0 * GRAPHENE_1_PERCENT; // 0% Force-settlement fee %\n\n         // Attempt and fail to create the smart asset with a force-settlement fee % before HARDFORK_CORE_BSIP87_TIME\n         trx.clear();\n         REQUIRE_EXCEPTION_WITH_TEXT(create_smart_asset(\"USDBIT\", assetowner_id, usd_fso_percent, usd_fsf_percent_0),\n                                     \"cannot be set before Hardfork BSIP87\");\n\n\n         // Create the smart asset without a force-settlement fee %\n         trx.clear();\n         create_smart_asset(\"USDBIT\", assetowner_id, usd_fso_percent);\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bitusd = get_asset(\"USDBIT\");\n         const auto &core = asset_id_type()(db);\n         const int64_t core_unit = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n         const int64_t bitusd_unit = asset::scaled_precision(bitusd.precision).value; // 100 satoshi USDBIT in 1 USDBIT\n\n\n         ///////\n         // 2. Publish a feed for the smart asset\n         ///////\n         update_feed_producers(bitusd.get_id(), {feedproducer_id});\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         // Requirement of 20x collateral in satoshis: 1 satoshi bitUSD for 20 satoshi Core\n         // -> 0.01 bitUSD for 0.00020 Core = 100 bitUSD for 2 Core = 50 bitUSD for 1 Core\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(20);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         ///////\n         // 3. Paul borrows 100 bitUSD\n         ///////\n         // Paul will borrow bitUSD by providing 2x collateral required: 2 * 20 = 40\n         int64_t paul_initial_usd = 100 * bitusd_unit; // 10000\n         int64_t paul_initial_core = paul_initial_usd * 2 * 20; // 400000\n         const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd),\n                                                      core.amount(paul_initial_core));\n         call_order_id_type call_paul_id = call_paul.get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(paul, bitusd), paul_initial_usd);\n\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD\n         ///////\n         int64_t rachel_initial_usd = 20 * bitusd_unit;\n         transfer(paul.get_id(), rachel.get_id(), asset(rachel_initial_usd, bitusd.get_id()));\n\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position\n         ///////\n         const int64_t rachel_settle_amount = 2 * bitusd_unit;\n         operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount));\n\n         force_settlement_id_type rachel_settle_id { *result.get<extendable_operation_result>()\n                                                           .value.new_objects->begin() };\n         BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount);\n\n         // Advance time to complete the force settlement and to update the price feed\n         generate_blocks(db.head_block_time() + fc::hours(26));\n         set_expiration(db, trx);\n         trx.clear();\n         publish_feed(bitusd, feedproducer_id(db), current_feed);\n         trx.clear();\n\n         // Rachel's settlement should have completed and should no longer be present\n         BOOST_CHECK(!db.find(rachel_settle_id));\n\n         // Check Rachel's balance\n         // Rachel redeemed some smart asset and should get the equivalent collateral amount (according to the feed price)\n         // minus the force_settlement_offset_fee - force_settlement_fee\n         // Rachel redeemed 2 bitUSD and should get 4000 satoshi Core - 200 satoshi Core - 0 satoshi  Core\n         uint64_t rachel_settle_core = rachel_settle_amount * 20; // Settle amount * feed price\n         uint64_t rachel_fso_fee_core = rachel_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT;\n         uint64_t rachel_fso_remainder_core = rachel_settle_core - rachel_fso_fee_core;\n         uint64_t rachel_fsf_fee_core = (rachel_fso_remainder_core) * 0 / GRAPHENE_100_PERCENT;\n         uint64_t expected_rachel_core = rachel_settle_core - rachel_fso_fee_core - rachel_fsf_fee_core;\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(rachel, core), expected_rachel_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         // Rachel redeemed 2 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it\n         BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount, call_paul_id(db).debt.value);\n         // The call order has the original amount of collateral less what was redeemed by Rachel\n         BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core, call_paul_id(db).collateral.value);\n\n\n         ///////\n         // 6. Asset owner attempts to claim the collateral fees.\n         // Although no collateral-denominated fees should be present, the error should indicate the\n         // that claiming such fees are not yet active.\n         ///////\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); // There should be no fees\n         trx.clear();\n         asset_claim_fees_operation claim_op;\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = core.amount(5 * core_unit);\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Collateral-denominated fees are not yet active\");\n\n         // Early proposals to claim should also fail\n         proposal_create_operation cop;\n         cop.review_period_seconds = 86400;\n         uint32_t buffer_seconds = 60 * 60;\n         cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.proposed_ops.emplace_back(claim_op);\n\n         trx.clear();\n         trx.operations.push_back(cop);\n         // sign(trx, smartissuer_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Collateral-denominated fees are not yet active\");\n\n\n         ///////\n         // 7. Activate HARDFORK_CORE_BSIP87_TIME\n         ///////\n         generate_blocks(HARDFORK_CORE_BSIP87_TIME);\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         // Update the price feed\n         publish_feed(bitusd, feedproducer_id(db), current_feed);\n         trx.clear();\n\n\n         ///////\n         // 8. Paul gives Michael 30 bitUSD and retains 50 bitUSD\n         ///////\n         int64_t michael_initial_usd = 30 * bitusd_unit;\n         transfer(paul.get_id(), michael.get_id(), asset(michael_initial_usd, bitusd.get_id()));\n\n         // Check Michael's balance\n         BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd - michael_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 9. Michael force-settles 5 bitUSD which should be collected from Paul's debt position\n         ///////\n         const int64_t michael_settle_amount = 5 * bitusd_unit;\n         result = force_settle(michael, bitusd.amount(michael_settle_amount));\n\n         force_settlement_id_type michael_settle_id { *result.get<extendable_operation_result>()\n                                                           .value.new_objects->begin() };\n         BOOST_CHECK_EQUAL(michael_settle_id(db).balance.amount.value, michael_settle_amount);\n\n         // Advance time to complete the force settlement and to update the price feed\n         generate_blocks(db.head_block_time() + fc::hours(26));\n         set_expiration(db, trx);\n         trx.clear();\n         publish_feed(bitusd, feedproducer_id(db), current_feed);\n\n         // Michael's settlement should have completed and should no longer be present\n         BOOST_CHECK(!db.find(michael_settle_id));\n\n         // Check Michael's balance\n         // Michael redeemed some smart asset and should get the equivalent collateral amount (according to the feed price)\n         // minus the force_settlement_offset_fee - force_settlement_fee\n         // Michael redeemed 5 bitUSD and should get 10000 satoshi Core - 500 satoshi Core - 0 satoshi  Core\n         uint64_t michael_settle_core = michael_settle_amount * 20; // Settle amount * feed price\n         uint64_t michael_fso_fee_core = michael_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT;\n         uint64_t michael_fso_remainder_core = michael_settle_core - michael_fso_fee_core;\n         uint64_t michael_fsf_fee_core = (michael_fso_remainder_core) * usd_fsf_percent_0 / GRAPHENE_100_PERCENT;\n         uint64_t expected_michael_core = michael_settle_core - michael_fso_fee_core - michael_fsf_fee_core;\n         BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd - michael_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core + expected_michael_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd - michael_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         // Michael redeemed 5 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it\n         BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount - michael_settle_amount,\n                           call_paul_id(db).debt.value);\n         // The call order has the original amount of collateral less what was redeemed by Rachel, Michael\n         BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core,\n                           call_paul_id(db).collateral.value);\n\n         // The asset's force settlement fee % should still not be set\n         BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         // There should be no accumulated asset fees\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0);\n\n\n         ///////\n         // 10. Asset owner sets the force-fee percentage to 3%\n         ///////\n         const uint16_t usd_fsf_percent_3 = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87)\n         asset_update_bitasset_operation uop;\n         uop.issuer = assetowner.id;\n         uop.asset_to_update = bitusd.get_id();\n         uop.new_options = bitusd.bitasset_data(db).options;\n         uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_3;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n\n         // The force settlement fee % should be set\n         BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         BOOST_CHECK_EQUAL( usd_fsf_percent_3,\n                            *bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent\n                          );\n\n         ///////\n         // 11. Paul gives Yanna 40 bitUSD and retains 10 bitUSD\n         ///////\n         int64_t yanna_initial_usd = 40 * bitusd_unit;\n         transfer(paul.get_id(), yanna.get_id(), asset(yanna_initial_usd, bitusd.get_id()));\n\n         // Check Yanna's balance\n         BOOST_CHECK_EQUAL(get_balance(yanna, bitusd), yanna_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(yanna, core), 0);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd),\n                           paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 12. Yanna force-settles 10 bitUSD which should be collected from Paul's debt position\n         ///////\n         const int64_t yanna_settle_amount = 10 * bitusd_unit;\n         result = force_settle(yanna, bitusd.amount(yanna_settle_amount));\n\n         force_settlement_id_type yanna_settle_id { *result.get<extendable_operation_result>()\n                                                           .value.new_objects->begin() };\n         BOOST_CHECK_EQUAL(yanna_settle_id(db).balance.amount.value, yanna_settle_amount);\n\n         // Advance time to complete the force settlement and to update the price feed\n         generate_blocks(db.head_block_time() + fc::hours(26));\n         set_expiration(db, trx);\n         trx.clear();\n         publish_feed(bitusd, feedproducer_id(db), current_feed);\n\n         // Yanna's settlement should have completed and should no longer be present\n         BOOST_CHECK(!db.find(yanna_settle_id));\n\n         // Check Yanna's balance\n         // Yanna redeemed some smart asset and should get the equivalent collateral amount (according to the feed price)\n         // minus the force_settlement_offset_fee - force_settlement_fee\n         // Yanna redeemed 10 bitUSD and should get 20000 satoshi Core - 1000 satoshi Core - 570 satoshi  Core; (20000 - 1000) * 3% = 570\n         uint64_t yanna_settle_core = yanna_settle_amount * 20; // Settle amount * feed price\n         uint64_t yanna_fso_fee_core = yanna_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT;\n         uint64_t yanna_fso_remainder_core = yanna_settle_core - yanna_fso_fee_core;\n         uint64_t yanna_fsf_fee_core = (yanna_fso_remainder_core) * usd_fsf_percent_3 / GRAPHENE_100_PERCENT;\n         uint64_t expected_yanna_core = yanna_settle_core - yanna_fso_fee_core - yanna_fsf_fee_core;\n         BOOST_CHECK_EQUAL(get_balance(yanna, bitusd), yanna_initial_usd - yanna_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(yanna, core), 0 + expected_yanna_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd),\n                           paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         // Yanna redeemed 10 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it\n         BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount - michael_settle_amount - yanna_settle_amount,\n                           call_paul_id(db).debt.value);\n         // The call order has the original amount of collateral less what was redeemed by Rachel, Michael, Yanna\n         BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core - yanna_fso_remainder_core,\n                           call_paul_id(db).collateral.value);\n\n         // The asset's force settlement fee % should be valid\n         BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         // There should be some accumulated collateral-deonominated fees\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == yanna_fsf_fee_core);\n\n\n         ///////\n         // 13. Asset owner updates the force-settlement fee to 4%\n         ///////\n         const uint16_t usd_fsf_percent_4 = 4 * GRAPHENE_1_PERCENT; // 4% Force-settlement fee % (BSIP87)\n         uop = asset_update_bitasset_operation();\n         uop.issuer = assetowner.id;\n         uop.asset_to_update = bitusd.get_id();\n         uop.new_options = bitusd.bitasset_data(db).options;\n         uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_4;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n\n         // The force settlement fee % should be set\n         BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         BOOST_CHECK_EQUAL( usd_fsf_percent_4,\n                            *bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent\n                          );\n\n         ///////\n         // 14. Paul gives Vikram 10 bitUSD and retains 0 bitUSD\n         ///////\n         int64_t vikram_initial_usd = 10 * bitusd_unit;\n         transfer(paul.get_id(), vikram.get_id(), asset(vikram_initial_usd, bitusd.get_id()));\n\n         // Check Yanna's balance\n         BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), vikram_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(vikram, core), 0);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd),\n                           paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd -\n                           vikram_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 15. Vikram force-settles 10 bitUSD which should be collected from Paul's debt position\n         ///////\n         const int64_t vikram_settle_amount = 10 * bitusd_unit;\n         result = force_settle(vikram, bitusd.amount(vikram_settle_amount));\n\n         force_settlement_id_type vikram_settle_id { *result.get<extendable_operation_result>()\n                                                           .value.new_objects->begin() };\n         BOOST_CHECK_EQUAL(vikram_settle_id(db).balance.amount.value, vikram_settle_amount);\n\n         // Advance time to complete the force settlement and to update the price feed\n         generate_blocks(db.head_block_time() + fc::hours(26));\n         set_expiration(db, trx);\n         trx.clear();\n         publish_feed(bitusd, feedproducer_id(db), current_feed);\n\n         // Vikrams's settlement should have completed and should no longer be present\n         BOOST_CHECK(!db.find(vikram_settle_id));\n\n         // Check Vikrams's balance\n         // Vikram redeemed some smart asset and should get the equivalent collateral amount (according to the feed price)\n         // minus the force_settlement_offset_fee - force_settlement_fee\n         // Vikram redeemed 10 bitUSD and should get 20000 satoshi Core - 1000 satoshi Core - 760 satoshi  Core; (20000 - 1000) * 4% = 760\n         uint64_t vikram_settle_core = vikram_settle_amount * 20; // Settle amount * feed price\n         uint64_t vikram_fso_fee_core = vikram_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT;\n         uint64_t vikram_fso_remainder_core = vikram_settle_core - vikram_fso_fee_core;\n         uint64_t vikram_fsf_fee_core = (vikram_fso_remainder_core) * usd_fsf_percent_4 / GRAPHENE_100_PERCENT;\n         uint64_t expected_vikram_core = vikram_settle_core - vikram_fso_fee_core - vikram_fsf_fee_core;\n         BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), vikram_initial_usd - vikram_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(vikram, core), 0 + expected_vikram_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd),\n                           paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd -\n                           vikram_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         // Vikram redeemed 10 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it\n         BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount - michael_settle_amount - yanna_settle_amount -\n                           vikram_settle_amount,\n                           call_paul_id(db).debt.value);\n         // The call order has the original amount of collateral less what was redeemed by Rachel, Michael, Yanna, Vikram\n         BOOST_CHECK_EQUAL(\n                 paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core - yanna_fso_remainder_core -\n                 vikram_fso_remainder_core,\n                 call_paul_id(db).collateral.value);\n\n         // The asset's force settlement fee % should still not be set\n         BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         // There should be some accumulated collateral-deonominated fees\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         const uint64_t expected_accumulation_fsf_core_amount = yanna_fsf_fee_core + vikram_fsf_fee_core;\n         BOOST_CHECK(\n                 bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == expected_accumulation_fsf_core_amount);\n\n\n         ///////\n         // 16. Asset owner attempts and fails to change the backing of the smart asset\n         // because of its outstanding supply\n         ///////\n         // Create a new user-issued asset\n         trx.clear();\n         ACTOR(jill);\n         trx.clear();\n         price core_exchange_rate(asset(1, asset_id_type(1)), asset(1));\n         uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, core_exchange_rate, 2, market_fee_percent);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n         const asset_object& jillcoin = get_asset(\"JCOIN\");\n\n\n         // Attempt to change the backing of the smart asset to the new user-issued asset\n         trx.clear();\n         asset_update_bitasset_operation change_backing_asset_op;\n         change_backing_asset_op.asset_to_update = bitusd.id;\n         change_backing_asset_op.issuer = assetowner.id;\n         change_backing_asset_op.new_options.short_backing_asset = jillcoin.id;\n         trx.operations.push_back(change_backing_asset_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"there is already a current supply\");\n\n\n         ///////\n         // 17. All current holdings of bitUSD are removed\n         ///////\n         // Rachel, Michael, and Yanna return their remaining bitUSD to Paul\n         trx.clear();\n         transfer(rachel.get_id(), paul.get_id(), bitusd.amount(get_balance(rachel, bitusd)));\n         transfer(michael.get_id(), paul.get_id(), bitusd.amount(get_balance(michael, bitusd)));\n         transfer(yanna.get_id(), paul.get_id(), bitusd.amount(get_balance(yanna, bitusd)));\n\n         // Vikram has no bitUSD to transfer\n         BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), 0);\n\n         // Paul closes his debt to the blockchain\n         cover(paul, bitusd.amount(call_paul_id(db).debt), core.amount(call_paul_id(db).collateral.value));\n\n         // Check the bitUSD holdings of the actors\n         BOOST_CHECK_EQUAL(get_balance(assetowner, bitusd), 0);\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 0);\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 0);\n         BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0);\n         BOOST_CHECK_EQUAL(get_balance(yanna, bitusd), 0);\n         BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), 0);\n\n\n         ///////\n         // 18. Asset owner attempts and fails to change the backing of the smart asset\n         // because of unclaimed collateral fees\n         ///////\n         // Repeat check of the accumulated fees\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(\n                 bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == expected_accumulation_fsf_core_amount);\n\n         trx.clear();\n         trx.operations.push_back(change_backing_asset_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Must claim collateral-denominated fees\");\n\n\n         ///////\n         // 19. Asset owner claims all of the unclaimed collateral fees\n         ///////\n         trx.clear();\n         claim_op = asset_claim_fees_operation();\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = core.amount(expected_accumulation_fsf_core_amount);\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0);\n\n\n         ///////\n         // 20. Asset owner attempts and succeeds in changing the backing of the smart asset\n         ///////\n         // Confirm that the asset is backed by CORE\n         const asset_bitasset_data_object& bitusd_bitasset_data = (*bitusd.bitasset_data_id)(db);\n         BOOST_CHECK(bitusd_bitasset_data.options.short_backing_asset == core.id);\n\n         trx.clear();\n         trx.operations.push_back(change_backing_asset_op);\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n\n         // Confirm the change to the backing asset\n         BOOST_CHECK(bitusd_bitasset_data.options.short_backing_asset == jillcoin.id);\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Attempt to claim invalid fees\n    */\n   BOOST_AUTO_TEST_CASE(force_settle_fee_invalid_claims_test) {\n      try {\n         INVOKE(force_settle_fee_1_test);\n\n         GET_ACTOR(assetowner);\n\n         // Check the asset owner's accumulated asset fees\n         const auto &core = asset_id_type()(db);\n         const int64_t core_unit = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n         const asset_object& bitusd = get_asset(\"USDBIT\");\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees > 0);\n         share_type rachel_fsf_fee_core = bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees;\n\n         // Attempt to claim negative fees\n         trx.clear();\n         asset_claim_fees_operation claim_op;\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = core.amount(-5 * core_unit);\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"amount_to_claim.amount > 0\");\n\n         // Attempt to claim 0 fees\n         trx.clear();\n         claim_op = asset_claim_fees_operation();\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = core.amount(0 * core_unit);\n         trx.operations.push_back(claim_op);\n         set_expiration(db, trx);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"amount_to_claim.amount > 0\");\n\n         // Attempt to claim excessive claim fee\n         trx.clear();\n         claim_op = asset_claim_fees_operation();\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = rachel_fsf_fee_core + 1;\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Attempt to claim more backing-asset fees\");\n\n         // Attempt to claim with an invalid asset asset type\n         trx.clear();\n         ACTOR(jill);\n         price price(asset(1, asset_id_type(1)), asset(1));\n         uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2, market_fee_percent);\n         generate_block(); trx.clear(); set_expiration(db, trx);\n         const asset_object& jillcoin = get_asset(\"JCOIN\");\n\n         trx.clear();\n         claim_op = asset_claim_fees_operation();\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = jillcoin.amount(rachel_fsf_fee_core.value);\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"is not backed by asset\");\n\n         // Attempt to claim part of all that can be claimed\n         share_type partial_claim_core = 1; // 1 satoshi\n         share_type expected_remainder_core = rachel_fsf_fee_core - partial_claim_core;\n         FC_ASSERT(expected_remainder_core.value > 0); // Remainder should be positive\n         trx.clear();\n         claim_op = asset_claim_fees_operation();\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = partial_claim_core;\n         trx.operations.push_back(claim_op);\n         set_expiration(db, trx);\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == expected_remainder_core);\n\n         // Attempt to claim all that can be claimed\n         generate_block();\n         trx.clear();\n         claim_op = asset_claim_fees_operation();\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = expected_remainder_core;\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0);\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test 100% force settlement fee.\n    *\n    * There are two primary actors: paul, rachel\n    *\n    * 1. Asset owner creates the smart coin called bitUSD\n    * 2. The feed price is 1 satoshi bitUSD for 20 satoshi Core = 0.01 bitUSD for 0.00020 Core = 50 bitUSD for 1 Core\n    * 3. Paul borrows 100 bitUSD (10000 satoshis of bitUSD) from the blockchain with a low amount of collateral\n    * 4. Paul gives Rachel 20 bitUSD\n    * 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position\n    * because of its relatively lower collateral ratio\n    *\n    * The force-settlement by Rachel should account for both the force-settlement offset fee,\n    * and the new force settlement fee from BSIP87.\n    */\n   BOOST_AUTO_TEST_CASE(force_settle_fee_extreme_1_test) {\n      try {\n         ///////\n         // Initialize the scenario\n         ///////\n         // Advance to the when the force-settlement fee activates\n         generate_blocks(HARDFORK_CORE_BSIP87_TIME);\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         // Create actors\n         ACTORS((assetowner)(feedproducer)(paul)(rachel));\n\n         // Fund actors\n         uint64_t initial_balance_core = 10000000;\n         transfer(committee_account, assetowner.get_id(), asset(initial_balance_core));\n         transfer(committee_account, feedproducer.get_id(), asset(initial_balance_core));\n         transfer(committee_account, paul.get_id(), asset(initial_balance_core));\n\n         // 1. Create assets\n         const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee %\n         const uint16_t usd_fsf_percent = 100 * GRAPHENE_1_PERCENT; // 100% Force-settlement fee % (BSIP87)\n         create_smart_asset(\"USDBIT\", assetowner.get_id(), usd_fso_percent, usd_fsf_percent);\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bitusd = get_asset(\"USDBIT\");\n         const int64_t bitusd_unit = asset::scaled_precision(bitusd.precision).value; // 100 satoshi USDBIT in 1 USDBIT\n         const auto &core = asset_id_type()(db);\n\n\n         ///////\n         // 2. Publish a feed for the smart asset\n         ///////\n         update_feed_producers(bitusd.get_id(), {feedproducer_id});\n         price_feed current_feed;\n         current_feed.maintenance_collateral_ratio = 1750;\n         current_feed.maximum_short_squeeze_ratio = 1100;\n         // Requirement of 20x collateral in satoshis: 1 satoshi bitUSD for 20 satoshi Core\n         // -> 0.01 bitUSD for 0.00020 Core = 100 bitUSD for 2 Core = 50 bitUSD for 1 Core\n         current_feed.settlement_price = bitusd.amount(1) / core.amount(20);\n         publish_feed(bitusd, feedproducer, current_feed);\n\n\n         ///////\n         // 3. Paul borrows 100 bitUSD\n         ///////\n         // Paul will borrow bitUSD by providing 2x collateral required: 2 * 20 = 40\n         int64_t paul_initial_usd = 100 * bitusd_unit; // 10000\n         int64_t paul_initial_core = paul_initial_usd * 2 * 20; // 400000\n         const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd),\n                                                      core.amount(paul_initial_core));\n         call_order_id_type call_paul_id = call_paul.get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(paul, bitusd), paul_initial_usd);\n\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD\n         ///////\n         int64_t rachel_initial_usd = 20 * bitusd_unit;\n         transfer(paul.get_id(), rachel.get_id(), asset(rachel_initial_usd, bitusd.get_id()));\n\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n\n         ///////\n         // 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position\n         ///////\n         const int64_t rachel_settle_amount = 2 * bitusd_unit; // 200 satoshi bitusd\n         operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount));\n\n         force_settlement_id_type rachel_settle_id { *result.get<extendable_operation_result>()\n                                                           .value.new_objects->begin() };\n         BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount);\n\n         // Advance time to complete the force settlement and to update the price feed\n         generate_blocks(db.head_block_time() + fc::hours(26));\n         set_expiration(db, trx);\n         trx.clear();\n         publish_feed(bitusd, feedproducer_id(db), current_feed);\n         trx.clear();\n\n         // Rachel's settlement should have completed and should no longer be present\n         BOOST_CHECK(!db.find(rachel_settle_id));\n\n         // Check Rachel's balance\n         // Rachel redeemed some smart asset and should get the equivalent collateral amount (according to the feed price)\n         // minus the force_settlement_offset_fee - force_settlement_fee\n         // uint64_t rachel_settle_core = 4000; // rachel_settle_amount * 20\n         // uint64_t rachel_fso_fee_core = 200; // rachel_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT\n         uint64_t rachel_fso_remainder_core = 3800; // rachel_settle_core - rachel_fso_fee_core\n         uint64_t rachel_fsf_fee_core = 3800; // (rachel_fso_remainder_core) * usd_fsf_percent / GRAPHENE_100_PERCENT\n         // Rachel redeemed 2 bitUSD and should get 4000 satoshi Core - 200 satoshi Core - 3800 satoshi  Core\n         uint64_t expected_rachel_core = 0; // rachel_settle_core - rachel_fso_fee_core - rachel_fsf_fee_core\n         BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount);\n         BOOST_CHECK_EQUAL(get_balance(rachel, core), expected_rachel_core);\n\n         // Check Paul's balance\n         BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd);\n         BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core);\n\n         // Check Paul's debt to the blockchain\n         // Rachel redeemed 2 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it\n         BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount, call_paul_id(db).debt.value);\n         // The call order has the original amount of collateral less what was redeemed by Rachel\n         BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core, call_paul_id(db).collateral.value);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == rachel_fsf_fee_core);\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test the ability to create and update assets with force-settlement fee % before HARDFORK_CORE_BSIP87_TIME\n    *\n    *\n    * Before HARDFORK_CORE_BSIP87_TIME\n    *\n    * 1. Asset owner fails to create the smart coin called USDBIT with a force-settlement fee %\n    * 2. Asset owner fails to create the smart coin called USDBIT with a force-settlement fee % in a proposal\n    * 3. Asset owner succeeds to create the smart coin called USDBIT without a force-settlement fee %\n    *\n    * 4. Asset owner fails to update the smart coin with a force-settlement fee %\n    * 5. Asset owner fails to update the smart coin with a force-settlement fee % in a proposal\n    *\n    * 6. Asset owner fails to claim collateral-denominated fees\n    * 7. Asset owner fails to claim collateral-denominated fees in a proposal\n    *\n    *\n    * 8. Activate HARDFORK_CORE_BSIP87_TIME\n    *\n    *\n    * After HARDFORK_CORE_BSIP87_TIME\n    *\n    * 9. Asset owner succeeds to create the smart coin called CNYBIT with a force-settlement fee %\n    * 10. Asset owner succeeds to create the smart coin called RUBBIT with a force-settlement fee % in a proposal\n    *\n    * 11. Asset owner succeeds to update the smart coin called CNYBIT with a force-settlement fee %\n    * 12. Asset owner succeeds to update the smart coin called RUBBIT with a force-settlement fee % in a proposal\n    */\n   BOOST_AUTO_TEST_CASE(prevention_before_hardfork_test) {\n      try {\n         ///////\n         // Initialize the scenario\n         ///////\n         // Get around Graphene issue #615 feed expiration bug\n         generate_blocks(HARDFORK_615_TIME);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Create actors\n         ACTORS((assetowner));\n\n         // Fund actors\n         uint64_t initial_balance_core = 10000000;\n         transfer(committee_account, assetowner.get_id(), asset(initial_balance_core));\n\n         // Confirm before hardfork activation\n         BOOST_CHECK(db.head_block_time() < HARDFORK_CORE_BSIP87_TIME);\n\n\n         ///////\n         // 1. Asset owner fails to create the smart coin called bitUSD with a force-settlement fee %\n         ///////\n         const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee %\n         const uint16_t usd_fsf_percent_0 = 0 * GRAPHENE_1_PERCENT; // 0% Force-settlement fee %\n\n         // Attempt to create the smart asset with a force-settlement fee %\n         // The attempt should fail because it is before HARDFORK_CORE_BSIP87_TIME\n         trx.clear();\n         REQUIRE_EXCEPTION_WITH_TEXT(create_smart_asset(\"USDBIT\", assetowner_id, usd_fso_percent, usd_fsf_percent_0),\n                                     \"cannot be set before Hardfork BSIP87\");\n\n\n         ///////\n         // 2. Asset owner fails to create the smart coin called bitUSD with a force-settlement fee % in a proposal\n         ///////\n         {\n            asset_create_operation create_op = create_smart_asset_op(\"USDBIT\", assetowner_id, usd_fso_percent,\n                                                                     usd_fsf_percent_0);\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(create_op);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP87\");\n         }\n\n\n         ///////\n         // 3. Asset owner succeeds to create the smart coin called bitUSD without a force-settlement fee %\n         ///////\n         trx.clear();\n         create_smart_asset(\"USDBIT\", assetowner_id, usd_fso_percent);\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bitusd = get_asset(\"USDBIT\");\n         const auto &core = asset_id_type()(db);\n\n\n         ///////\n         // 4. Asset owner fails to update the smart coin with a force-settlement fee %\n         ///////\n         const uint16_t usd_fsf_percent_3 = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87)\n         asset_update_bitasset_operation uop;\n         uop.issuer = assetowner.id;\n         uop.asset_to_update = bitusd.get_id();\n         uop.new_options = bitusd.bitasset_data(db).options;\n         uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_3;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP87\");\n\n         // The force settlement fee % should not be set\n         BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n\n\n         ///////\n         // 5. Asset owner fails to update the smart coin with a force-settlement fee % in a proposal\n         ///////\n         {\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP87\");\n\n            // The force settlement fee % should not be set\n            BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         }\n\n\n         ///////\n         // 6. Asset owner fails to claim collateral-denominated fees\n         ///////\n         // Although no collateral-denominated fees should be present, the error should indicate the\n         // that claiming such fees are not yet active.\n         BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); // There should be no fees\n         trx.clear();\n         asset_claim_fees_operation claim_op;\n         claim_op.issuer = assetowner.id;\n         claim_op.extensions.value.claim_from_asset_id = bitusd.id;\n         claim_op.amount_to_claim = core.amount(5);\n         trx.operations.push_back(claim_op);\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Collateral-denominated fees are not yet active\");\n\n\n         ///////\n         // 7. Asset owner fails to claim collateral-denominated fees in a proposal\n         ///////\n         proposal_create_operation cop;\n         cop.review_period_seconds = 86400;\n         uint32_t buffer_seconds = 60 * 60;\n         cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.proposed_ops.emplace_back(claim_op);\n\n         trx.clear();\n         trx.operations.push_back(cop);\n         // sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"Collateral-denominated fees are not yet active\");\n\n\n\n         ///////\n         // 8. Activate HARDFORK_CORE_BSIP87_TIME\n         ///////\n         BOOST_CHECK(db.head_block_time() < HARDFORK_CORE_BSIP87_TIME); // Confirm still before hardfork activation\n         generate_blocks(HARDFORK_CORE_BSIP87_TIME);\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n\n         ///////\n         // 9. Asset owner succeeds to create the smart coin called CNYBIT with a force-settlement fee %\n         ///////\n         const uint16_t fsf_percent_1 = 1 * GRAPHENE_1_PERCENT; // 1% Force-settlement fee % (BSIP87)\n         const uint16_t fsf_percent_5 = 1 * GRAPHENE_1_PERCENT; // 5% Force-settlement fee % (BSIP87)\n         trx.clear();\n         create_smart_asset(\"CNYBIT\", assetowner_id, usd_fso_percent, fsf_percent_1);\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bitcny = get_asset(\"CNYBIT\");\n\n         // The force settlement fee % should be set\n         BOOST_CHECK(bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         BOOST_CHECK_EQUAL(fsf_percent_1, *bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent);\n\n\n         ///////\n         // 10. Asset owner succeeds to create the smart coin called RUBBIT with a force-settlement fee % in a proposal\n         ///////\n         {\n            // Create the proposal\n            asset_create_operation create_op = create_smart_asset_op(\"RUBBIT\", assetowner_id, usd_fso_percent,\n                                                                     fsf_percent_1);\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(create_op);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            processed_transaction processed = PUSH_TX(db, trx);\n\n\n            // Approve the proposal\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = assetowner_id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(assetowner_id);\n            trx.clear();\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, assetowner_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n\n            // Advance to the activation of the proposal\n            generate_blocks(cop.expiration_time);\n            set_expiration(db, trx);\n         }\n         const auto &bitrub = get_asset(\"RUBBIT\");\n\n         // The force settlement fee % should be set\n         BOOST_CHECK(bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         BOOST_CHECK_EQUAL(fsf_percent_1, *bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent);\n\n\n         ///////\n         // 11. Asset owner succeeds to update the smart coin called CNYBIT with a force-settlement fee %\n         ///////\n         uop = asset_update_bitasset_operation();\n         uop.issuer = assetowner.id;\n         uop.asset_to_update = bitcny.get_id();\n         uop.new_options = bitcny.bitasset_data(db).options;\n         uop.new_options.extensions.value.force_settle_fee_percent = fsf_percent_5;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n\n         // The force settlement fee % should be set\n         BOOST_CHECK(bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         BOOST_CHECK_EQUAL(fsf_percent_5, *bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent);\n\n\n         ///////\n         // 12. Asset owner succeeds to update the smart coin called RUBBIT with a force-settlement fee % in a proposal\n         ///////\n         {\n            // Create the proposal\n            uop = asset_update_bitasset_operation();\n            uop.issuer = assetowner.id;\n            uop.asset_to_update = bitrub.get_id();\n            uop.new_options = bitrub.bitasset_data(db).options;\n            uop.new_options.extensions.value.force_settle_fee_percent = fsf_percent_5;\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            processed_transaction processed = PUSH_TX(db, trx);\n\n\n            // Approve the proposal\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = assetowner_id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(assetowner_id);\n            trx.clear();\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, assetowner_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n            // Advance to the activation of the proposal\n            generate_blocks(cop.expiration_time);\n            set_expiration(db, trx);\n         }\n\n         // The force settlement fee % should be set\n         BOOST_CHECK(bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid());\n         BOOST_CHECK_EQUAL(fsf_percent_5, *bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent);\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/force_settle_match_tests.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(force_settle_match_tests, database_fixture)\n\n/***\n * BSIP38 \"target_collateral_ratio\" test after hf core-2481:\n *   matching a taker settle order with multiple maker call orders\n */\nBOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(buyer2)(buyer3)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, buyer2_id, asset(init_balance));\n   transfer(committee_account, buyer3_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175%\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000));\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n   price mc( asset(10*175), asset(1*100, usd_id) );\n\n   // This sell order above MSSP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->get_id();\n   // This buy order at MSSP will be matched only if no margin call (margin call takes precedence)\n   limit_order_id_type buy_med = create_sell_order(buyer2, asset(33000), bitusd.amount(3000))->get_id();\n   // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence)\n   limit_order_id_type buy_high = create_sell_order(buyer3, asset(111), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer3, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( init_balance - 33000, get_balance(buyer2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );\n\n   // call and call2's CR is quite high, and debt amount is quite a lot,\n   // assume neither of them will be completely filled\n   price match_price( bitusd.amount(1) / core.amount(11) );\n   share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   BOOST_CHECK_GT( call_to_cover.value, 0 );\n   BOOST_CHECK_GT( call2_to_cover.value, 0 );\n   BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value );\n   // even though call2 has a higher CR, since call's TCR is less than call2's TCR,\n   // so we expect call will cover less when called\n   BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value );\n\n   // Create a force settlement, will be matched with several call orders\n   auto result = force_settle( seller, bitusd.amount(700*4) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n\n   // buy orders won't change\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n   BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 33000 );\n   BOOST_CHECK_EQUAL( db.find( buy_high )->for_sale.value, 111 );\n\n   // the settle order will match with call, at mssp: 1/11 = 1000/11000\n   const call_order_object* tmp_call = db.find( call_id );\n   BOOST_CHECK( tmp_call != nullptr );\n\n   // call will receive call_to_cover, pay 11*call_to_cover\n   share_type call_to_pay = call_to_cover * 11;\n   share_type call_to_settler = (call_to_cover * 10 * 107 + 99) / 100; // round up, favors settle order\n   BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call.debt.value * 10 * 1750 < call.collateral.value * 1000 );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // the settle order then will match with call2, at mssp: 1/11 = 1000/11000\n   const call_order_object* tmp_call2 = db.find( call2_id );\n   BOOST_CHECK( tmp_call2 != nullptr );\n\n   // call2 will receive call2_to_cover, pay 11*call2_to_cover\n   share_type call2_to_pay = call2_to_cover * 11;\n   share_type call2_to_settler = (call2_to_cover * 10 * 107 + 99) / 100; // round up, favors settle order\n   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2.collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call2.debt.value * 10 * 2000 < call2.collateral.value * 1000 );\n   idump( (call2) );\n   // borrower2's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // check the settle order's balance\n   BOOST_CHECK_EQUAL( 700 * 4 - call2_to_cover.value - call_to_cover.value,\n                      settle_id(db).balance.amount.value );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4\n   int64_t expected_seller_core_balance = call_to_settler.value + call2_to_settler.value;\n   BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) );\n\n   // asset's force_settled_volume does not change\n   BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * After hf core-2481, matching small taker settle orders with a big maker call order.\n * Also tests tiny call orders.\n */\nBOOST_AUTO_TEST_CASE(hf2481_small_settle_call)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/175 CORE/USD = 6/70, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 285% collateral\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(7), asset(1), 1700);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 285% collateral\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(14), asset(2), 1700);\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(100000));\n\n   // adjust price feed to get call orders into margin call territory\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 10/1, mssp = 100/11, mcop = 1000/107, mcpr = 110/107\n\n   BOOST_CHECK_EQUAL( 100000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 7, call2.debt.value );\n   BOOST_CHECK_EQUAL( 1, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 14, call3.debt.value );\n   BOOST_CHECK_EQUAL( 2, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 1, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 7, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 2, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 14, get_balance(borrower3, bitusd) );\n\n   BOOST_CHECK_EQUAL( 100000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // Create a force settlement, will be matched with the call order\n   share_type amount_to_settle = 117;\n   auto result = force_settle( seller, bitusd.amount(amount_to_settle) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle_id ) == nullptr );\n\n   // the settle order will match with call2, at mssp: 100/11,\n   // since call2 is too small, so it pays all\n   BOOST_CHECK( db.find( call2_id ) == nullptr );\n   share_type expected_amount_to_settle = 110; // 117 - 7\n\n   // the settle order will match with call3, at mssp: 100/11,\n   // since call3 has TCR, it pays some collateral and stays there\n   BOOST_REQUIRE( db.find( call3_id ) != nullptr );\n\n   BOOST_CHECK_EQUAL( 5, call3.debt.value );\n   BOOST_CHECK_EQUAL( 1, call3.collateral.value );\n\n   expected_amount_to_settle = 101; // 117 - 7 - 9\n\n   // the settle order will match with call, at mssp: 100/11\n   BOOST_CHECK( db.find( call_id ) != nullptr );\n\n   // check\n   share_type call_to_settler = expected_amount_to_settle * 107 / 1000; // round down, favors call order : 10\n   share_type call_to_cover = (call_to_settler * 1000 + 106 ) / 107; // stabilize : 101 -> 94\n   share_type call_to_pay = call_to_cover * 11 / 100; // round down, favors call order : 10, fee = 0\n   BOOST_CHECK_EQUAL( 100000 - call_to_cover.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 99890, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94, the rest 7 be canceled\n   int64_t expected_seller_core_balance = 1 + 1 + call_to_settler.value;\n   BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) );\n\n   // asset's force_settled_volume does not change\n   BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value );\n\n   // Settle again\n   share_type amount_to_settle2 = 100;\n   result = force_settle( seller, bitusd.amount(amount_to_settle2) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle2_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle2_id ) == nullptr );\n\n   // the settle order will match with call, at mssp: 100/11\n   BOOST_CHECK( db.find( call_id ) != nullptr );\n\n   // check\n   share_type call_to_settler2 = amount_to_settle2 * 107 / 1000; // round down, favors call order : 10\n   share_type call_to_cover2 = (call_to_settler2 * 1000 + 106 ) / 107; // stabilize : 100 -> 94\n   share_type call_to_pay2 = call_to_cover2 * 11 / 100; // round down, favors call order : 10, fee = 0\n   BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94\n   expected_seller_core_balance += call_to_settler2.value;\n   BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) );\n\n   // asset's force_settled_volume does not change\n   BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value );\n\n   {\n      // increase mssr and mcfr\n      // settlement price = 10/1, mssp = 100/13, mcop = 1000/103, mcpr = 130/103\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; // 130%\n      uop.new_options.extensions.value.margin_call_fee_ratio = 270; // 27%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   // Settle again with a much smaller amount\n   share_type amount_to_settle3 = 9;\n   result = force_settle( seller, bitusd.amount(amount_to_settle3) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle3_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle3_id ) == nullptr );\n\n   // the settle order will match with call, at mssp\n   BOOST_CHECK( db.find( call_id ) != nullptr );\n\n   // check\n   share_type call_to_settler3 = amount_to_settle3 * 103 / 1000; // round down, favors call order : 0\n   BOOST_CHECK_EQUAL( 0, call_to_settler3.value );\n   // the settle order will be cancelled\n   BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94\n   expected_seller_core_balance += call_to_settler3.value;\n   BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) );\n\n   // asset's force_settled_volume does not change\n   BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value );\n\n   // Settle again with a tiny amount that would receive nothing\n   share_type amount_to_settle4 = 5;\n   result = force_settle( seller, bitusd.amount(amount_to_settle4) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle4_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle4_id ) == nullptr );\n\n   // the settle order will match with call, at mssp\n   BOOST_CHECK( db.find( call_id ) != nullptr );\n\n   // no data change\n   BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   // check seller balance\n   BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94\n   // expected_seller_core_balance does not change\n   BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) );\n\n   // asset's force_settled_volume does not change\n   BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * BSIP38 \"target_collateral_ratio\" test after hf core-2481:\n *   matching taker call orders with maker settle orders\n */\nBOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175%\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000));\n   // create a small position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7, no tcr\n   const call_order_object& call4 = *borrow( borrower4, bitusd.amount(10), asset(160) );\n   call_order_id_type call4_id = call4.get_id();\n\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n   BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) );\n\n   // This sell order above MSSP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n\n   // Create a sell order which will be matched with several call orders later, price 1/9\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(500), core.amount(4500) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 500 );\n\n   // Create a force settlement, will be matched with several call orders later\n   auto result = force_settle( seller, bitusd.amount(2400) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n\n   // prepare price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   price mc( asset(10*175), asset(1*100, usd_id) );\n\n   // call and call2's CR is quite high, and debt amount is quite a lot,\n   // assume neither of them will be completely filled\n   price match_price = sell_id(db).sell_price * ratio_type(107,110);\n   share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   BOOST_CHECK_GT( call_to_cover.value, 0 );\n   BOOST_CHECK_GT( call2_to_cover.value, 0 );\n   BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value );\n   // even though call2 has a higher CR, since call's TCR is less than call2's TCR,\n   // so we expect call will cover less when called\n   BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value );\n\n   call_order_object call2_copy = call2;\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11, mcop = 10/107, mcpr = 110/107\n\n   // firstly the limit order will match with call, at limit order's price: 1/9\n   BOOST_CHECK( db.find( call_id ) != nullptr );\n\n   // call will receive call_to_cover, pay 9*call_to_cover\n   share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller\n   BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call.debt.value * 10 * 1750 < call.collateral.value * 1000 );\n   idump( (call_to_pay)(call_to_cover)(call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // the limit order then will match with call2, at limit order's price: 1/9\n   const call_order_object* tmp_call2 = db.find( call2_id );\n   BOOST_CHECK( tmp_call2 != nullptr );\n\n   // if the limit is big enough, call2 will receive call2_to_cover, pay 9*call2_to_cover\n   // however it's not the case, so call2 will receive less\n   call2_to_cover = 500 - call_to_cover;\n   share_type call2_to_pay = call2_to_cover * 9 * 110 / 107; // round down since it's larger\n\n   // borrower2's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n\n   // call4 will match with the settle order, since it has no tcr, it will be fully closed\n   // match price is 1/11\n   const call_order_object* tmp_call4 = db.find( call4_id );\n   BOOST_CHECK( tmp_call4 == nullptr );\n\n   // borrower4 balance changes\n   BOOST_CHECK_EQUAL( init_balance - 110, get_balance(borrower4, core) );\n   BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) );\n\n   // call2 is still in margin call territory after matched with limit order, now it matches with settle order\n   price call_pays_price( asset(1, usd_id), asset(11) );\n   call2_copy.debt -= call2_to_cover;\n   call2_copy.collateral -= call2_to_pay;\n   auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750,mc);\n   BOOST_CHECK_GT( call2_to_cover2.value, 0 );\n   share_type call2_to_pay2 = call2_to_cover2 * 11;\n   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value - call2_to_cover2.value, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value - call2_to_pay2.value, call2.collateral.value );\n   idump( (call2_to_pay)(call2_to_cover)(call2_to_pay2)(call2_to_cover2)(call2) );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // sell_id is completely filled\n   BOOST_CHECK( !db.find( sell_id ) );\n\n   // settle order is not fully filled\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 93, get_balance(seller, bitusd) ); // 3000 - 7 - 500 - 2400\n   BOOST_CHECK_EQUAL( 4500 + 107 + (call2_to_cover2.value * 107 + 9) / 10, // round up\n                      get_balance(seller, core) ); // 500*9 + 10*10.7 + call2_cover2 * 10.7\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // Can not reduce CR of a call order to trigger a margin call but not get fully filled and final CR <= ICR\n   BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10000, usd_id), asset(160000), 1700), fc::exception );\n\n   // Can not create a new call order that is partially called instantly if final CR <= ICR\n   BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(160000), 1700), fc::exception );\n\n   idump( (settle_id(db))(get_balance(seller, core)) );\n\n   // Can not create a new call order that is undercollateralized\n   BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10, usd_id), asset(10) ), fc::exception );\n\n   idump( (settle_id(db))(get_balance(seller, core)) );\n\n   // Can not reduce CR of a call order to make it undercollateralized\n   BOOST_CHECK_THROW( borrow( borrower3_id(db), asset(0, usd_id), asset(-24000) ), fc::exception );\n\n   idump( (settle_id(db))(get_balance(seller, core)) );\n\n   // Can not create a new call order that would trigger a black swan event\n   BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(10000) ), fc::exception );\n\n   // Able to reduce CR of a call order to trigger a margin call if final CR is above ICR\n   borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700 );\n\n   // Able to create a new call order that is partially called instantly if final CR is above ICR\n   borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700 );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Request force settlement before hard fork, match taker call orders with maker settle orders at hard fork time\n */\nBOOST_AUTO_TEST_CASE(hf2481_cross_test)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.feed_lifetime_sec = mi * 10;\n      uop.new_options.force_settlement_delay_sec = mi * 10;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175%\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000));\n   call_order_id_type call3_id = call3.get_id();\n   // create a small position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7, no tcr\n   const call_order_object& call4 = *borrow( borrower4, bitusd.amount(10), asset(160) );\n   call_order_id_type call4_id = call4.get_id();\n\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n   BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) );\n\n   // This sell order above MSSP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n\n   // Create a sell order which will be matched with several call orders later, price 1/9\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(500), core.amount(4500) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 500 );\n\n   // Create a force settlement, will be matched with several call orders later\n   auto result = force_settle( seller, bitusd.amount(2400) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n\n   BOOST_CHECK_EQUAL( 2400, settle_id(db).balance.amount.value );\n\n   // prepare price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   price mc( asset(10*175), asset(1*100, usd_id) );\n\n   // call and call2's CR is quite high, and debt amount is quite a lot,\n   // assume neither of them will be completely filled\n   price match_price = sell_id(db).sell_price * ratio_type(107,110);\n   share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   BOOST_CHECK_GT( call_to_cover.value, 0 );\n   BOOST_CHECK_GT( call2_to_cover.value, 0 );\n   BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value );\n   // even though call2 has a higher CR, since call's TCR is less than call2's TCR,\n   // so we expect call will cover less when called\n   BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value );\n\n   call_order_object call2_copy = call2;\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11, mcop = 10/107, mcpr = 110/107\n\n   generate_block();\n\n   // firstly the limit order will match with call, at limit order's price: 1/9\n   const call_order_object* tmp_call = db.find( call_id );\n   BOOST_CHECK( tmp_call != nullptr );\n\n   // call will receive call_to_cover, pay 9*call_to_cover\n   share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller\n   BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call_id(db).collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call_id(db).debt.value * 10 * 1750 < call_id(db).collateral.value * 1000 );\n   idump( (call_to_pay)(call_to_cover)(call_id(db)) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower_id, asset_id_type()) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, usd_id) );\n\n   // the limit order then will match with call2, at limit order's price: 1/9\n   const call_order_object* tmp_call2 = db.find( call2_id );\n   BOOST_CHECK( tmp_call2 != nullptr );\n\n   // if the limit is big enough, call2 will receive call2_to_cover, pay 9*call2_to_cover\n   // however it's not the case, so call2 will receive less\n   call2_to_cover = 500 - call_to_cover;\n   share_type call2_to_pay = call2_to_cover * 9 * 110 / 107; // round down since it's larger\n\n   // borrower2's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2_id, asset_id_type()) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2_id, usd_id) );\n\n   // sell_id is completely filled\n   BOOST_CHECK( !db.find( sell_id ) );\n\n   // all call orders are still there\n   BOOST_CHECK( db.find( call_id ) != nullptr );\n   BOOST_CHECK( db.find( call2_id ) != nullptr );\n   BOOST_CHECK( db.find( call3_id ) != nullptr );\n   BOOST_CHECK( db.find( call4_id ) != nullptr );\n\n   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2_id(db).collateral.value );\n\n   idump( (call2_to_pay)(call2_to_cover)(call2_id(db)) );\n\n   // settle order does not change\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n   BOOST_CHECK_EQUAL( 2400, settle_id(db).balance.amount.value );\n\n   // check borrower4's balances\n   BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4_id, asset_id_type()) );\n   BOOST_CHECK_EQUAL( 10, get_balance(borrower4_id, usd_id) );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 93, get_balance(seller_id, usd_id) ); // 3000 - 7 - 500 - 2400\n   BOOST_CHECK_EQUAL( 4500, get_balance(seller_id, asset_id_type()) ); // 500*9\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3_id(db).collateral.value );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // pass the hard fork time\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // call4 will match with the settle order, since it has no tcr, it will be fully closed\n   // match price is 1/11\n   const call_order_object* tmp_call4 = db.find( call4_id );\n   BOOST_CHECK( tmp_call4 == nullptr );\n\n   // borrower4 balance changes\n   BOOST_CHECK_EQUAL( init_balance - 110, get_balance(borrower4_id, asset_id_type()) );\n   BOOST_CHECK_EQUAL( 10, get_balance(borrower4_id, usd_id) );\n\n   // call2 is still in margin call territory after matched with limit order, now it matches with settle order\n   price call_pays_price( asset(1, usd_id), asset(11) );\n   call2_copy.debt -= call2_to_cover;\n   call2_copy.collateral -= call2_to_pay;\n   auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750,mc);\n   BOOST_CHECK_GT( call2_to_cover2.value, 0 );\n   share_type call2_to_pay2 = call2_to_cover2 * 11;\n   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value - call2_to_cover2.value, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value - call2_to_pay2.value, call2_id(db).collateral.value );\n   idump( (call2_to_pay)(call2_to_cover)(call2_to_pay2)(call2_to_cover2)(call2_id(db)) );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3_id(db).collateral.value );\n\n   // settle order is not fully filled\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n   BOOST_CHECK_EQUAL( 2400 - 10 - call2_to_cover2.value, settle_id(db).balance.amount.value ); // call4, call2\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 93, get_balance(seller_id, usd_id) ); // 3000 - 7 - 500 - 2400\n   BOOST_CHECK_EQUAL( 4500 + 107 + (call2_to_cover2.value * 107 + 9) / 10, // round up\n                      get_balance(seller_id, asset_id_type()) ); // 500*9 + 10*10.7 + call2_cover2 * 10.7\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Matching taker call orders with maker settle orders and triggers blackswan event\n */\nBOOST_AUTO_TEST_CASE(call_settle_blackswan)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n  // 3 passes. With no matching limit order, or with a small or big matching limit order.\n  for( int i = 0; i < 3; ++ i )\n  {\n   idump( (i) );\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   transfer(committee_account, borrower5_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700, tcr 200% > 175%\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500), 2000);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/175 CORE/USD = 100/700, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(25000));\n   call_order_id_type call3_id = call3.get_id();\n   // create a small position with 320% collateral, call price is 16/175 CORE/USD = 64/700, no tcr\n   const call_order_object& call4 = *borrow( borrower4, bitusd.amount(1000), asset(160) );\n   call_order_id_type call4_id = call4.get_id();\n   // create yet another position with 900% collateral, call price is 45/175 CORE/USD = 180/700, no tcr\n   const call_order_object& call5 = *borrow( borrower5, bitusd.amount(100000), asset(45000));\n   call_order_id_type call5_id = call5.get_id();\n\n   transfer(borrower, seller, bitusd.amount(100000));\n   transfer(borrower2, seller, bitusd.amount(100000));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 100000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 300000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) );\n   BOOST_CHECK_EQUAL( init_balance - 45000, get_balance(borrower5, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n   BOOST_CHECK_EQUAL( 1000, get_balance(borrower4, bitusd) );\n   BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) );\n\n   share_type expected_seller_usd_balance = 300000;\n\n   // This sell order above MCOP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(700), core.amount(150))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 );\n   expected_seller_usd_balance -= 700;\n\n   BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(1000))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n\n   // Create a sell order which will be matched with several call orders later, price 100/9\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(100000), core.amount(9000) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 100000 );\n   expected_seller_usd_balance -= 100000;\n\n   // Create another sell order which will trigger a blackswan event if matched, price 100/21\n   limit_order_id_type sell_swan;\n   if( i == 1 )\n   {\n      sell_swan = create_sell_order(seller, bitusd.amount(100), core.amount(21) )->get_id();\n      BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 );\n      expected_seller_usd_balance -= 100;\n   }\n   else if( i == 2 )\n   {\n      sell_swan = create_sell_order(seller, bitusd.amount(10000), core.amount(2100) )->get_id();\n      BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 );\n      expected_seller_usd_balance -= 10000;\n   }\n\n   // Create a force settlement, will be matched with several call orders later\n   auto result = force_settle( seller, bitusd.amount(40000) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n   expected_seller_usd_balance -= 40000;\n\n   // Create another force settlement\n   result = force_settle( seller, bitusd.amount(10000) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle2_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle2_id ) != nullptr );\n   expected_seller_usd_balance -= 10000;\n\n   // Create the third force settlement which is small\n   result = force_settle( seller, bitusd.amount(3) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle3_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle3_id ) != nullptr );\n   expected_seller_usd_balance -= 3;\n\n   // Check seller balance\n   BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // Create the fourth force settlement which is a little bigger but still small\n   // Note: different execution path than settle3\n   result = force_settle( seller, bitusd.amount(5) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle4_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle4_id ) != nullptr );\n   expected_seller_usd_balance -= 5;\n\n   // Check seller balance\n   BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   call_order_object call_copy = call;\n   call_order_object call2_copy = call2;\n   call_order_object call3_copy = call3;\n   call_order_object call4_copy = call4;\n   call_order_object call5_copy = call5;\n\n   // prepare price feed to get call, call2, call3 and call4 (but not call5) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(20);\n   price mc( asset(20*175), asset(100*100, usd_id) );\n\n   // since the sell limit order's price is low, and TCR is set for both call and call2,\n   // call and call2 will match with the sell limit order\n   price match_price = sell_id(db).sell_price * ratio_type(107,110);\n   share_type call_to_cover = call_copy.get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   share_type call2_to_cover = call2_copy.get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc);\n   BOOST_CHECK_GT( call_to_cover.value, 0 );\n   BOOST_CHECK_GT( call2_to_cover.value, 0 );\n   BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value );\n   // even though call2 has a higher CR, since call's TCR is less than call2's TCR,\n   // so we expect call will cover less when called\n   BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value );\n\n   // adjust price feed to get call, call2, call3 and call4 (but not call5) into margin call territory\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 100/20, mssp = 100/22, mcop = 500/107, mcpr = 110/107\n\n   share_type expected_margin_call_fees = 0;\n\n   // firstly the sell limit order will match with call, at limit order's price: 100/9\n   // call will receive call_to_cover, limit order gets call_to_cover*9/100,\n   // call pays call_to_cover*9*110/100/107 = call_to_cover * 99 / 1070\n   share_type call_to_pay = (call_to_cover * 99 + 1069) / 1070; // round up since it's smaller\n   // Note: no stabilization here\n\n   call_copy.debt -= call_to_cover;\n   call_copy.collateral -= call_to_pay;\n\n   share_type sell_receives1 = (call_to_cover * 9 + 99 ) / 100; // round up since the call order is smaller\n   share_type margin_call_fee_limit_1 = call_to_pay - sell_receives1;\n   expected_margin_call_fees += margin_call_fee_limit_1;\n\n   // the limit order then will match with call2, at limit order's price: 100/9\n   // if the limit is big enough, call2 will receive call2_to_cover,\n   // however it's not the case, so call2 will receive less\n   call2_to_cover = 100000 - call_to_cover;\n   share_type sell_receives2 = call2_to_cover * 9 / 100; // round down since the call order is larger\n   share_type call2_to_cover_old = call2_to_cover;\n   call2_to_cover = (sell_receives2 * 100 + 8) / 9; // stabilize. Note: from sell_receives2 but not call2_to_pay\n   share_type call2_to_pay = call2_to_cover * 99 / 1070; // round down since it's larger\n   share_type sell_refund = call2_to_cover_old - call2_to_cover;\n\n   call2_copy.debt -= call2_to_cover;\n   call2_copy.collateral -= call2_to_pay;\n\n   share_type margin_call_fee_limit_2 = call2_to_pay - sell_receives2;\n   expected_margin_call_fees += margin_call_fee_limit_2;\n\n   // sell_id is completely filled\n   BOOST_CHECK( !db.find( sell_id ) );\n\n   // now call4 has the lowest CR\n   // call4 will match with the settle order, since it is small and has too few collateral, it will be fully closed\n   // and it will lose all collateral, 160\n   // call_pays_price is 100/16, settle_receives_price is (100/16)*(110/107) = 1375/214\n   share_type settle_receives4 = 156; // round_up( 1000 * 214 / 1375 )\n   share_type margin_call_fee_settle_4 = 4; // 160 - 157\n   expected_margin_call_fees += margin_call_fee_settle_4;\n   // borrower4 balance does not change\n   BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) );\n   BOOST_CHECK_EQUAL( 1000, get_balance(borrower4, bitusd) );\n\n   // now call2 has the lowest CR\n   // call2 is still in margin call territory after matched with limit order, now it matches with settle orders\n   // the settle orders are too small to fill call2\n   share_type call2_to_cover1 = 39000; // 40000 - 1000\n   share_type settle_receives2 = call2_to_cover1 * call2_copy.collateral * 107\n                                 / (call2_copy.debt * 110); // round down\n   share_type call2_to_cover1_old = call2_to_cover1;\n   // stabilize\n   call2_to_cover1 = (settle_receives2 * call2_copy.debt * 110 + call2_copy.collateral * 107 - 1)\n                     / ( call2_copy.collateral * 107 );\n   share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down\n   share_type settle_refund = call2_to_cover1_old - call2_to_cover1;\n\n   share_type margin_call_fee_settle_2 = call2_to_pay1 - settle_receives2;\n   expected_margin_call_fees += margin_call_fee_settle_2;\n\n   idump( (\"before_match_settle_call2\")(call2_copy) );\n\n   call2_copy.debt -= call2_to_cover1;\n   call2_copy.collateral -= call2_to_pay1;\n\n   idump( (\"after_match_settle_call2\")(call2_copy) );\n\n   // call2 matches with the other settle order\n   share_type call2_to_cover2 = 10000;\n   share_type settle2_receives2 = call2_to_cover2 * call2_copy.collateral * 107\n                                 / (call2_copy.debt * 110); // round down\n   share_type call2_to_cover2_old = call2_to_cover2;\n   // stabilize\n   call2_to_cover2 = (settle2_receives2 * call2_copy.debt * 110 + call2_copy.collateral * 107 - 1)\n                     / ( call2_copy.collateral * 107 );\n   share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down\n   share_type settle2_refund = call2_to_cover2_old - call2_to_cover2;\n\n   share_type margin_call_fee_settle2_2 = call2_to_pay2 - settle2_receives2;\n   expected_margin_call_fees += margin_call_fee_settle2_2;\n\n   call2_copy.debt -= call2_to_cover2;\n   call2_copy.collateral -= call2_to_pay2;\n\n   // settle orders are fully filled\n   BOOST_CHECK( db.find( settle_id ) == nullptr );\n   BOOST_CHECK( db.find( settle2_id ) == nullptr );\n   // settle3 is canceled\n   BOOST_CHECK( db.find( settle3_id ) == nullptr );\n   share_type settle3_refund = 3;\n   // settle4 is canceled\n   BOOST_CHECK( db.find( settle4_id ) == nullptr );\n   share_type settle4_refund = 5;\n\n   // blackswan event occurs\n   BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n   BOOST_CHECK( db.find( call_id ) == nullptr );\n   BOOST_CHECK( db.find( call2_id ) == nullptr );\n   BOOST_CHECK( db.find( call3_id ) == nullptr );\n   BOOST_CHECK( db.find( call4_id ) == nullptr );\n   BOOST_CHECK( db.find( call5_id ) == nullptr );\n\n   share_type expected_gs_fund = 0;\n\n   idump( (call2_copy) );\n\n   // call2 has the lowest CR below required\n   share_type call2_to_gs_fund = (call2_copy.collateral * 10 + 10) / 11; // MSSR = 11/10, round up here\n   share_type margin_call_fee_gs_2 = call2_copy.collateral - call2_to_gs_fund;\n   expected_margin_call_fees += margin_call_fee_gs_2;\n   expected_gs_fund += call2_to_gs_fund;\n   // GS price (margin calls to pay) = call2_copy.collateral / call2_copy.debt\n   // GS price (all positions to fund) = (call2_copy.collateral * 10) / (call2_copy.debt * 11)\n\n   // borrower2 balance does not change\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n\n   // call3 is in margin call territory\n   share_type call3_to_pay_gs = ( call3_copy.debt * call2_copy.collateral + call2_copy.debt - 1 ) / call2_copy.debt;\n   share_type call3_to_gs_fund = ( call3_copy.debt * call2_copy.collateral * 10 + call2_copy.debt * 11 - 1 )\n                                 / (call2_copy.debt * 11);\n   share_type margin_call_fee_gs_3 = call3_to_pay_gs - call3_to_gs_fund;\n   expected_margin_call_fees += margin_call_fee_gs_3;\n   expected_gs_fund += call3_to_gs_fund;\n\n   // borrower3 balance changes -- some collateral returned\n   BOOST_CHECK_EQUAL( init_balance - call3_to_pay_gs.value, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n\n   // call is not in margin call territory\n   share_type call_to_gs_fund = ( call_copy.debt * call2_copy.collateral * 10 + call2_copy.debt * 11 - 1 )\n                                 / (call2_copy.debt * 11);\n   share_type call_to_pay_gs = call_to_gs_fund;\n   expected_gs_fund += call_to_gs_fund;\n   // no fee\n\n   // borrower balance changes -- some collateral returned\n   BOOST_CHECK_EQUAL( init_balance - call_to_pay.value - call_to_pay_gs.value, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // call5 is not in margin call territory\n   share_type call5_to_gs_fund = ( call5_copy.debt * call2_copy.collateral * 10 + call2_copy.debt * 11 - 1 )\n                                 / (call2_copy.debt * 11);\n   share_type call5_to_pay_gs = call5_to_gs_fund;\n   expected_gs_fund += call5_to_gs_fund;\n   // no fee\n\n   // borrower5 balance changes -- some collateral returned\n   BOOST_CHECK_EQUAL( init_balance - call5_to_pay_gs.value, get_balance(borrower5, core) );\n   BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) );\n\n   // check seller balance\n   expected_seller_usd_balance += (sell_refund + settle_refund + settle2_refund + settle3_refund + settle4_refund);\n   // 1000*9 + 160*107/110 + 49000 * call2_cr * 107/110\n   share_type expected_seller_core_balance = sell_receives1 + sell_receives2 + settle_receives4\n                                             + settle_receives2 + settle2_receives2;\n\n   BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( expected_seller_core_balance.value, get_balance(seller, core) );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // sell_high is not matched\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 );\n\n   // sell_swan is not matched\n   if( i == 1 )\n      BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 );\n   else if( i == 2 )\n      BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 );\n\n   // check gs fund\n   BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value );\n   // force_settled_volume is 0\n   BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).force_settled_volume.value, 0 );\n\n   // check margin call fees\n   BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                      expected_margin_call_fees.value );\n\n   // generate a block\n   BOOST_TEST_MESSAGE( \"Generate a block\" );\n   generate_block();\n   BOOST_TEST_MESSAGE( \"Check again\" );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // sell_high is not matched\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 );\n\n   // sell_swan is not matched\n   if( i == 1 )\n      BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 );\n   else if( i == 2 )\n      BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 );\n\n   // check gs fund\n   BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value );\n   // force_settled_volume is 0\n   BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).force_settled_volume.value, 0 );\n\n   // check margin call fees\n   BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                      expected_margin_call_fees.value );\n\n   // reset\n   db.pop_block();\n\n  } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Match taker call orders with maker settle orders,\n * then it is able to match taker call orders with maker limit orders again\n */\nBOOST_AUTO_TEST_CASE(call_settle_limit_settle)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type core_id;\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3%\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 360% collateral, call price is 18/175 CORE/USD = 72/700, no tcr\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(18000) );\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 800% collateral, call price is 40/175 CORE/USD = 160/700, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(40000) );\n   call_order_id_type call3_id = call3.get_id();\n\n   transfer(borrower, seller, bitusd.amount(100000));\n   transfer(borrower2, seller, bitusd.amount(100000));\n   transfer(borrower3, seller2, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 100000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 18000, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 40000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 18000, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n\n   // Create a sell order which will trigger a blackswan event if matched, price 100/16\n   limit_order_id_type sell_swan = create_sell_order(seller2, bitusd.amount(10000), core.amount(1600) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 );\n\n   // Create a force settlement, will be matched with several call orders later\n   auto result = force_settle( seller, bitusd.amount(200000) );\n   BOOST_REQUIRE( result.is_type<extendable_operation_result>() );\n   BOOST_REQUIRE( result.get<extendable_operation_result>().value.new_objects.valid() );\n   BOOST_REQUIRE( !result.get<extendable_operation_result>().value.new_objects->empty() );\n   force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n   BOOST_CHECK( db.find( settle_id ) != nullptr );\n   BOOST_CHECK_EQUAL( 200000, settle_id(db).balance.amount.value );\n\n   // Check balances\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 90000, get_balance(seller2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) );\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(16);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 100/16, mssp = 1000/176, mcop = 100/16 * 100/107 = 625/107, mcpr = 110/107\n\n   const auto& check_result = [&]\n   {\n      // matching call with sell_swan would trigger a black swan event, so it's skipped\n      // so matching call with settle\n      // the settle order is bigger so call is fully filled\n      BOOST_CHECK( !db.find( call_id ) );\n      // call pays 15000, gets 100000\n      // settle receives round_up(15000 * 107 / 110) = 14591, margin call fee = 409\n\n      // now it is able to match call2 with sell_swan\n      // call2 is bigger, sell_swan is fully filled\n      BOOST_CHECK( !db.find( sell_swan ) );\n      // sell_swan pays 10000, gets 1600\n      // call2 pays round_down(1600 * 110 / 107) = 1644, margin call fee = 44\n\n      // now match call2 with settle\n      // the settle order is bigger so call2 is fully filled\n      BOOST_CHECK( !db.find( call2_id ) );\n      // call2 gets 90000, pays round_up(90000 * (16/100) * (11/10)) = 15840\n      // settle receives round_up(90000 * (16/100) * (107/100)) = 15408, margin call fee = 432\n\n      // the settle order is not fully filled\n      BOOST_CHECK_EQUAL( 10000, settle_id(db).balance.amount.value );\n\n      // no change to call3\n      BOOST_CHECK_EQUAL( 100000, call3_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 40000, call3_id(db).collateral.value );\n\n      // blackswan event did not occur\n      BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n\n      // check balances\n      BOOST_CHECK_EQUAL( 0, get_balance(seller_id, usd_id) );\n      BOOST_CHECK_EQUAL( 14591+15408, get_balance(seller_id, core_id) );\n      BOOST_CHECK_EQUAL( 90000, get_balance(seller2_id, usd_id) );\n      BOOST_CHECK_EQUAL( 1600, get_balance(seller2_id, core_id) );\n      BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower_id, core_id) );\n      BOOST_CHECK_EQUAL( init_balance - 1644 - 15840, get_balance(borrower2_id, core_id) );\n      BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower3_id, core_id) );\n   };\n\n   // check\n   check_result();\n\n   // generate a block\n   BOOST_TEST_MESSAGE( \"Generate a block\" );\n   generate_block();\n   BOOST_TEST_MESSAGE( \"Check again\" );\n\n   // check\n   check_result();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/grouped_orders_api_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 manikey123, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/app/api.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::app;\n\nBOOST_FIXTURE_TEST_SUITE(grouped_orders_api_tests, database_fixture)\nBOOST_AUTO_TEST_CASE(api_limit_get_grouped_limit_orders) {\n   try\n   {\n   app.enable_plugin(\"grouped_orders\");\n   graphene::app::orders_api orders_api(app);\n   optional<price> start;\n\n   //account_id_type() do 3 ops\n   create_bitasset(\"USD\", account_id_type());\n   create_account(\"dan\");\n   create_account(\"bob\");\n   asset_id_type bit_jmj_id = create_bitasset(\"JMJBIT\").get_id();\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n   auto core = std::string( asset_id_type() );\n   GRAPHENE_CHECK_THROW(orders_api.get_grouped_limit_orders(core, core, 10, start, 260), fc::exception);\n   auto orders =orders_api.get_grouped_limit_orders(core, std::string( bit_jmj_id ), 10,start,240);\n   BOOST_REQUIRE_EQUAL( orders.size(), 0u);\n   }catch (fc::exception &e)\n   {\n    edump((e.to_detail_string()));\n    throw;\n   }\n}\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/history_api_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::app;\nBOOST_FIXTURE_TEST_SUITE( history_api_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE(get_account_history) {\n   try {\n      graphene::app::history_api hist_api(app);\n\n      //account_id_type() do 3 ops\n      create_bitasset(\"USD\", account_id_type());\n      create_account( \"dan\", account_id_type()(db), GRAPHENE_WITNESS_ACCOUNT(db) );\n      create_account( \"bob\", account_id_type()(db), GRAPHENE_TEMP_ACCOUNT(db) );\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      int asset_create_op_id = operation::tag<asset_create_operation>::value;\n      int account_create_op_id = operation::tag<account_create_operation>::value;\n\n      //account_id_type() did 3 ops and includes id0\n      vector<operation_history_object> histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n      BOOST_CHECK_EQUAL(histories[2].op.which(), asset_create_op_id);\n      BOOST_CHECK( histories[2].block_time == db.head_block_time() );\n      BOOST_CHECK( !histories[2].is_virtual );\n\n      // 1 account_create op larger than id1\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1),\n                                                      100, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK(histories[0].id.instance() != 0);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // Limit 2 returns 2 result\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(),\n                                                      2, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK(histories[1].id.instance() != 0);\n      BOOST_CHECK_EQUAL(histories[1].op.which(), account_create_op_id);\n\n      // bob has 1 op\n      histories = hist_api.get_account_history(\"bob\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // witness-account has 0 op\n      histories = hist_api.get_account_history(\"witness-account\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // get_block_operation_history\n      auto head_block_num = db.head_block_num();\n      histories = hist_api.get_block_operation_history(head_block_num);\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      histories = hist_api.get_block_operation_history(head_block_num, 1u);\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n\n      // get_block_operations_by_time\n      auto time1 = db.head_block_time();\n      histories = hist_api.get_block_operations_by_time(time1);\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n\n      histories = hist_api.get_block_operations_by_time(time1 + fc::seconds(1));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n\n      histories = hist_api.get_block_operations_by_time(time1 - fc::seconds(1));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      generate_block();\n      auto time2 = db.head_block_time();\n\n      histories = hist_api.get_block_operations_by_time(time1);\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n\n      histories = hist_api.get_block_operations_by_time(time1 - fc::seconds(1));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      histories = hist_api.get_block_operations_by_time(time2);\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n\n      create_bitasset(\"USX\", account_id_type());\n      generate_block();\n      auto time3 = db.head_block_time();\n\n      histories = hist_api.get_block_operations_by_time(time2);\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n\n      histories = hist_api.get_block_operations_by_time(time3);\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n\n      histories = hist_api.get_block_operations_by_time(time3 + fc::seconds(1));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(get_account_history_virtual_operation_test)\n{ try {\n      graphene::app::history_api hist_api(app);\n\n      asset_id_type usd_id = create_user_issued_asset(\"USD\").get_id();\n\n      ACTORS( (dan)(bob) );\n      fund( dan, asset(100) );\n      issue_uia( bob_id, asset(100, usd_id) );\n\n      create_sell_order( dan_id, asset(100), asset(100, usd_id) );\n      create_sell_order( bob_id, asset(100, usd_id), asset(100) );\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      auto fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n      vector<operation_history_object> histories = hist_api.get_account_history(\"dan\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n\n      BOOST_REQUIRE_GT( histories.size(), 0 );\n      BOOST_CHECK_EQUAL( histories.front().op.which(), fill_order_op_id );\n      BOOST_CHECK( histories.front().block_time == db.head_block_time() );\n      BOOST_CHECK( histories.front().is_virtual );\n\n      // Create a limit order that expires in 300 seconds\n      create_sell_order( dan_id, asset(10, usd_id), asset(10), db.head_block_time() + 300 );\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      auto order_create_op_id = operation::tag<limit_order_create_operation>::value;\n\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n\n      BOOST_REQUIRE_GT( histories.size(), 0 );\n      BOOST_CHECK_EQUAL( histories.front().op.which(), order_create_op_id );\n      BOOST_CHECK( histories.front().block_time == db.head_block_time() );\n      BOOST_CHECK( !histories.front().is_virtual );\n\n      // Let the limit order expire\n      generate_blocks( db.head_block_time() + 300 );\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      auto order_cancel_op_id = operation::tag<limit_order_cancel_operation>::value;\n\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n\n      BOOST_REQUIRE_GT( histories.size(), 0 );\n      BOOST_CHECK_EQUAL( histories.front().op.which(), order_cancel_op_id );\n      BOOST_CHECK( histories.front().is_virtual );\n      BOOST_CHECK( histories.front().result.is_type<asset>() );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(get_account_history_notify_all_on_creation) {\n   try {\n      // Pass hard fork time\n      generate_blocks(HARDFORK_CORE_265_TIME);\n      set_expiration( db, trx );\n\n      graphene::app::history_api hist_api(app);\n\n      //account_id_type() do 3 ops\n      create_bitasset(\"USD\", account_id_type());\n      create_account( \"dan\", account_id_type()(db), GRAPHENE_WITNESS_ACCOUNT(db) );\n      create_account( \"bob\", account_id_type()(db), GRAPHENE_TEMP_ACCOUNT(db) );\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      int asset_create_op_id = operation::tag<asset_create_operation>::value;\n      int account_create_op_id = operation::tag<account_create_operation>::value;\n\n      //account_id_type() did 3 ops and includes id0\n      vector<operation_history_object> histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n      BOOST_CHECK_EQUAL(histories[2].op.which(), asset_create_op_id);\n\n      // 1 account_create op larger than id1\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1),\n                                                      100, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK(histories[0].id.instance() != 0);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // Limit 2 returns 2 result\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(),\n                                                      2, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK(histories[1].id.instance() != 0);\n      BOOST_CHECK_EQUAL(histories[1].op.which(), account_create_op_id);\n\n      // bob has 1 op\n      histories = hist_api.get_account_history(\"bob\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // witness-account has 1 op\n      histories = hist_api.get_account_history(\"witness-account\", operation_history_id_type(),\n                                                      100, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(get_account_history_additional) {\n   try {\n      graphene::app::history_api hist_api(app);\n\n      // A = account_id_type() with records { 5, 3, 1, 0 }, and\n      // B = dan with records { 6, 4, 2, 1 }\n      // account_id_type() and dan share operation id 1(account create) - share can be also in id 0\n\n      // no history at all in the chain\n      vector<operation_history_object> histories =\n            hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 4, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      create_bitasset(\"USD\", account_id_type()); // create op 0\n      generate_block();\n      // what if the account only has one history entry and it is 0?\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      const account_object& dan = create_account(\"dan\"); // create op 1\n\n      create_bitasset(\"CNY\", dan.get_id()); // create op 2\n      create_bitasset(\"BTC\", account_id_type()); // create op 3\n      create_bitasset(\"XMR\", dan.get_id()); // create op 4\n      create_bitasset(\"EUR\", account_id_type()); // create op 5\n      create_bitasset(\"OIL\", dan.get_id()); // create op 6\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      // f(A, 0, 4, 9) = { 5, 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(9));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      // f(A, 0, 4, 6) = { 5, 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(6));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      // f(A, 0, 4, 5) = { 5, 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(5));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      // f(A, 0, 4, 4) = { 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(4));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n      // f(A, 0, 4, 3) = { 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(3));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n      // f(A, 0, 4, 2) = { 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n      // f(A, 0, 4, 1) = { 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type(1));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n      // f(A, 0, 4, 0) = { 5, 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 4, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      // f(A, 1, 5, 9) = { 5, 3 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(9));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      // f(A, 1, 5, 6) = { 5, 3 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(6));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      // f(A, 1, 5, 5) = { 5, 3 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(5));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      // f(A, 1, 5, 4) = { 3 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(4));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n\n      // f(A, 1, 5, 3) = { 3 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(3));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n\n      // f(A, 1, 5, 2) = { }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // f(A, 1, 5, 1) = { }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(1));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // f(A, 1, 5, 0) = { 5, 3 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 5,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      // f(A, 0, 3, 9) = { 5, 3, 1 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(9));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n      // f(A, 0, 3, 6) = { 5, 3, 1 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(6));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n      // f(A, 0, 3, 5) = { 5, 3, 1 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(5));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n      // f(A, 0, 3, 4) = { 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(4));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n      // f(A, 0, 3, 3) = { 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(3));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n\n      // f(A, 0, 3, 2) = { 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n      // f(A, 0, 3, 1) = { 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type(1));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n      // f(A, 0, 3, 0) = { 5, 3, 1 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 3, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n      // f(B, 0, 4, 9) = { 6, 4, 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(9));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      // f(B, 0, 4, 6) = { 6, 4, 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(6));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      // f(B, 0, 4, 5) = { 4, 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(5));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n      // f(B, 0, 4, 4) = { 4, 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(4));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n\n      // f(B, 0, 4, 3) = { 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(3));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n\n      // f(B, 0, 4, 2) = { 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 1u);\n\n      // f(B, 0, 4, 1) = { 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type(1));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 1u);\n\n      // f(B, 0, 4, 0) = { 6, 4, 2, 1 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(), 4, operation_history_id_type());\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      // f(B, 2, 4, 9) = { 6, 4 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(9));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n      // f(B, 2, 4, 6) = { 6, 4 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(6));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n      // f(B, 2, 4, 5) = { 4 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(5));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n\n      // f(B, 2, 4, 4) = { 4 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(4));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n\n      // f(B, 2, 4, 3) = { }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(3));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // f(B, 2, 4, 2) = { }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // f(B, 2, 4, 1) = { }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(1));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // f(B, 2, 4, 0) = { 6, 4 }\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(2), 4, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n      // 0 limits\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(0), 0, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(3), 0,\n                                                        operation_history_id_type(9));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // non existent account\n      histories = hist_api.get_account_history(\"1.2.18\", operation_history_id_type(0), 4,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // create a new account C = alice { 7 }\n      create_account(\"alice\");\n\n      generate_block();\n\n      // f(C, 0, 4, 10) = { 7 }\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(0), 4,\n                                                        operation_history_id_type(10));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 7u);\n\n      // f(C, 8, 4, 10) = { }\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(8), 4,\n                                                        operation_history_id_type(10));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // f(A, 0, 10, 0) = { 7, 5, 3, 1, 0 }\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 5u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 7u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[4].id.instance(), 0u);\n\n   }\n   catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(get_account_history_by_time) {\n   try {\n      graphene::app::history_api hist_api(app);\n\n      auto time1 = db.head_block_time();\n\n      // A = account_id_type() with records { 5, 3, 1, 0 }, and\n      // B = dan with records { 6, 4, 2, 1 }\n      // account_id_type() and dan share operation id 1(account create) - share can be also in id 0\n\n      // no history at all in the chain\n      vector<operation_history_object> histories = hist_api.get_account_history_by_time(\"1.2.0\");\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      create_bitasset(\"USD\", account_id_type()); // create op 0\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      auto time2 = db.head_block_time();\n\n      // what if the account only has one history entry and it is 0?\n      histories = hist_api.get_account_history_by_time(\"1.2.0\");\n      BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", 0);\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", 2, time1);\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", 10, time2);\n      BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", {}, time2);\n      BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      BOOST_CHECK_THROW( hist_api.get_account_history_by_time( \"1.2.0\", 102 ), fc::exception );\n\n      const account_object& dan = create_account(\"dan\"); // create op 1\n\n      create_bitasset(\"CNY\", dan.get_id()); // create op 2\n      create_bitasset(\"BTC\", account_id_type()); // create op 3\n      create_bitasset(\"XMR\", dan.get_id()); // create op 4\n      create_bitasset(\"EUR\", account_id_type()); // create op 5\n      create_bitasset(\"OIL\", dan.get_id()); // create op 6\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      auto time3 = db.head_block_time();\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", {}, time2);\n      BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", {}, time2 + fc::seconds(1));\n      BOOST_REQUIRE_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", {}, time3);\n      BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", 2, time3);\n      BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\", 2);\n      BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      histories = hist_api.get_account_history_by_time(\"1.2.0\");\n      BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 5u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\", {}, time3);\n      BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\", 5, time3);\n      BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\", 5);\n      BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\");\n      BOOST_REQUIRE_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\", 2, time3);\n      BOOST_REQUIRE_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\", 2, time2);\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"dan\", {}, time1);\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      histories = hist_api.get_account_history_by_time(\"nathan\", 2);\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n   }\n   catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(track_account) {\n   try {\n      graphene::app::history_api hist_api(app);\n\n      // account_id_type() is not tracked\n\n      // account_id_type() creates alice(not tracked account)\n      create_account(\"alice\");\n\n      //account_id_type() creates some ops\n      create_bitasset(\"CNY\", account_id_type());\n      create_bitasset(\"USD\", account_id_type());\n\n      // account_id_type() creates dan(account tracked)\n      const account_object& dan = create_account(\"dan\");\n      auto dan_id = dan.get_id();\n\n      // dan makes 1 op\n      create_bitasset(\"EUR\", dan_id);\n\n      generate_block();\n\n      // anything against account_id_type() should be {}\n      vector<operation_history_object> histories =\n            hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 1,\n                                                        operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // anything against alice should be {}\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(1), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(1), 1,\n                                                        operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // dan should have history\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);\n\n      // create more ops, starting with an untracked account\n      create_bitasset( \"BTC\", account_id_type() );\n      create_bitasset( \"GBP\", dan_id );\n\n      generate_block();\n\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);\n\n      db.pop_block();\n\n      // Try again, should result in same object IDs\n      create_bitasset( \"BTC\", account_id_type() );\n      create_bitasset( \"GBP\", dan_id );\n\n      generate_block();\n\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(track_account2) {\n   try {\n      graphene::app::history_api hist_api(app);\n\n      // account_id_type() is tracked\n\n      // account_id_type() creates alice(tracked account)\n      const account_object& alice = create_account(\"alice\");\n      auto alice_id = alice.get_id();\n\n      //account_id_type() creates some ops\n      create_bitasset(\"CNY\", account_id_type());\n      create_bitasset(\"USD\", account_id_type());\n\n      // alice makes 1 op\n      create_bitasset(\"EUR\", alice_id);\n\n      // account_id_type() creates dan(account not tracked)\n      create_account(\"dan\");\n\n      generate_block();\n\n      // all account_id_type() should have 4 ops {4,2,1,0}\n      vector<operation_history_object> histories = hist_api.get_account_history(\"committee-account\",\n            operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);\n      BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);\n      BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);\n\n      // all alice account should have 2 ops {3, 0}\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n      BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);\n\n      // alice first op should be {0}\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(0), 1,\n                                                        operation_history_id_type(1));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n\n      // alice second op should be {3}\n      histories = hist_api.get_account_history(\"alice\", operation_history_id_type(1), 1,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);\n\n      // anything against dan should be {}\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(1), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n      histories = hist_api.get_account_history(\"dan\", operation_history_id_type(1), 1, operation_history_id_type(2));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(min_blocks_to_keep_test) {\n   try {\n\n      graphene::app::history_api hist_api(app);\n\n      generate_block();\n      generate_block();\n      generate_block();\n      generate_block();\n      generate_block();\n\n      vector<operation_history_object> histories =\n            hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10, operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // max-ops-per-account = 2\n      // min-blocks-to-keep = 3\n      // max-ops-per-acc-by-min-blocks = 5\n\n      //account_id_type() creates some ops\n      create_bitasset(\"USA\", account_id_type());\n      create_bitasset(\"USB\", account_id_type());\n      create_bitasset(\"USC\", account_id_type());\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_REQUIRE_EQUAL(histories.size(), 3u);\n      operation_history_id_type oldest_op_hist_id { histories.back().id };\n      BOOST_CHECK( db.find(oldest_op_hist_id) );\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 3u);\n\n      create_bitasset(\"USD\", account_id_type());\n      create_bitasset(\"USE\", account_id_type());\n      create_bitasset(\"USF\", account_id_type());\n      create_bitasset(\"USG\", account_id_type());\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 5u);\n      BOOST_CHECK( !db.find(oldest_op_hist_id) );\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 4u);\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n\n      generate_block();\n      histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(0), 10,\n                                                        operation_history_id_type(0));\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(get_account_history_operations) {\n   try {\n      graphene::app::history_api hist_api(app);\n\n      //account_id_type() do 3 ops\n      create_bitasset(\"CNY\", account_id_type());\n      create_account(\"sam\");\n      create_account(\"alice\");\n\n      generate_block();\n      fc::usleep(fc::milliseconds(100));\n\n      int asset_create_op_id = operation::tag<asset_create_operation>::value;\n      int account_create_op_id = operation::tag<account_create_operation>::value;\n\n      //account_id_type() did 1 asset_create op\n      vector<operation_history_object> histories = hist_api.get_account_history_operations(\n            \"committee-account\", asset_create_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), asset_create_op_id);\n\n      //account_id_type() did 2 account_create ops\n      histories = hist_api.get_account_history_operations(\n            \"committee-account\", account_create_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n      BOOST_CHECK_EQUAL(histories.size(), 2u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // No asset_create op larger than id1\n      histories = hist_api.get_account_history_operations(\n            \"committee-account\", asset_create_op_id, operation_history_id_type(), operation_history_id_type(1), 100);\n      BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n      // Limit 1 returns 1 result\n      histories = hist_api.get_account_history_operations(\n            \"committee-account\", account_create_op_id, operation_history_id_type(),operation_history_id_type(), 1);\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // alice has 1 op\n      histories = hist_api.get_account_history_operations(\n         \"alice\", account_create_op_id, operation_history_id_type(),operation_history_id_type(), 100);\n      BOOST_CHECK_EQUAL(histories.size(), 1u);\n      BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n      // create a bunch of accounts\n      for(int i = 0; i < 80; ++i)\n      {\n         std::string acct_name = \"mytempacct\" + std::to_string(i);\n         create_account(acct_name);\n      }\n      generate_block();\n\n      // history is set to limit transactions to 75 (see database_fixture.cpp)\n      // so asking for more should only return 75 (and not throw exception,\n      // see https://github.com/bitshares/bitshares-core/issues/1490\n      histories = hist_api.get_account_history_operations(\n            \"committee-account\", account_create_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n      BOOST_CHECK_EQUAL(histories.size(), 75u);\n      if (histories.size() > 0)\n         BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n\n   } catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n//new test case for increasing the limit based on the config file\nBOOST_AUTO_TEST_CASE(api_limit_get_account_history_operations) {\n try {\n   graphene::app::history_api hist_api(app);\n   //account_id_type() do 3 ops\n   create_bitasset(\"CNY\", account_id_type());\n   create_account(\"sam\");\n   create_account(\"alice\");\n\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n\n   int asset_create_op_id = operation::tag<asset_create_operation>::value;\n   int account_create_op_id = operation::tag<account_create_operation>::value;\n\n   //account_id_type() did 1 asset_create op\n   vector<operation_history_object> histories = hist_api.get_account_history_operations(\n      \"committee-account\", asset_create_op_id, operation_history_id_type(), operation_history_id_type(), 200);\n   BOOST_CHECK_EQUAL(histories.size(), 1u);\n   BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);\n   BOOST_CHECK_EQUAL(histories[0].op.which(), asset_create_op_id);\n\n   //account_id_type() did 2 account_create ops\n   histories = hist_api.get_account_history_operations(\n      \"committee-account\", account_create_op_id, operation_history_id_type(), operation_history_id_type(), 200);\n   BOOST_CHECK_EQUAL(histories.size(), 2u);\n   BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n   // No asset_create op larger than id1\n   histories = hist_api.get_account_history_operations(\n      \"committee-account\", asset_create_op_id, operation_history_id_type(), operation_history_id_type(1), 200);\n   BOOST_CHECK_EQUAL(histories.size(), 0u);\n\n   // Limit 1 returns 1 result\n   histories = hist_api.get_account_history_operations(\n      \"committee-account\", account_create_op_id, operation_history_id_type(),operation_history_id_type(), 1);\n   BOOST_CHECK_EQUAL(histories.size(), 1u);\n   BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n   // alice has 1 op\n   histories = hist_api.get_account_history_operations(\n      \"alice\", account_create_op_id, operation_history_id_type(),operation_history_id_type(), 200);\n   BOOST_CHECK_EQUAL(histories.size(), 1u);\n   BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n   // create a bunch of accounts\n   for(int i = 0; i < 126; ++i)\n   {\n      std::string acct_name = \"mytempacct\" + std::to_string(i);\n      create_account(acct_name);\n   }\n   generate_block();\n\n   // history is set to limit transactions to 125 (see database_fixture.cpp)\n   // so asking for more should only return 125 (and not throw exception,\n   // see https://github.com/bitshares/bitshares-core/issues/1490\n   GRAPHENE_CHECK_THROW( hist_api.get_account_history_operations(\"commitee-account\", account_create_op_id,\n                               operation_history_id_type(),operation_history_id_type(), 301),\n                         fc::exception );\n   histories = hist_api.get_account_history_operations(\"committee-account\", account_create_op_id,\n                         operation_history_id_type(), operation_history_id_type(), 200);\n   BOOST_REQUIRE_EQUAL( histories.size(), 125u );\n }\n catch (fc::exception &e)\n {\n   edump((e.to_detail_string()));\n   throw;\n }\n}\n\nBOOST_AUTO_TEST_CASE(api_limit_get_account_history) {\n try{\n   graphene::app::history_api hist_api(app);\n   //account_id_type() do 3 ops\n   create_bitasset(\"USD\", account_id_type());\n   create_account(\"dan\");\n   create_account(\"bob\");\n\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n\n   int asset_create_op_id = operation::tag<asset_create_operation>::value;\n   int account_create_op_id = operation::tag<account_create_operation>::value;\n   //account_id_type() did 3 ops and includes id0\n   vector<operation_history_object> histories =\n         hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 210, operation_history_id_type());\n\n   BOOST_CHECK_EQUAL(histories.size(), 3u);\n   BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u);\n   BOOST_CHECK_EQUAL(histories[2].op.which(), asset_create_op_id);\n\n   // 1 account_create op larger than id1\n   histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(1), 210, operation_history_id_type());\n   BOOST_CHECK_EQUAL(histories.size(), 1u);\n   BOOST_CHECK(histories[0].id.instance() != 0u);\n   BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n\n   // Limit 2 returns 2 result\n   histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 2, operation_history_id_type());\n   BOOST_CHECK_EQUAL(histories.size(), 2u);\n   BOOST_CHECK(histories[1].id.instance() != 0u);\n   BOOST_CHECK_EQUAL(histories[1].op.which(), account_create_op_id);\n   // bob has 1 op\n   histories = hist_api.get_account_history(\"bob\", operation_history_id_type(), 210, operation_history_id_type());\n   BOOST_CHECK_EQUAL(histories.size(), 1u);\n   BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id);\n\n   // create a bunch of accounts\n   for(int i = 0; i < 126; ++i)\n   {\n      std::string acct_name = \"mytempacct\" + std::to_string(i);\n      create_account(acct_name);\n   }\n   generate_block();\n\n   GRAPHENE_CHECK_THROW( hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 260,\n                               operation_history_id_type()),\n                         fc::exception );\n   histories = hist_api.get_account_history(\"1.2.0\", operation_history_id_type(), 210, operation_history_id_type());\n   BOOST_REQUIRE_EQUAL( histories.size(), 125u );\n } catch (fc::exception &e) {\n   edump((e.to_detail_string()));\n   throw;\n }\n}\nBOOST_AUTO_TEST_CASE(api_limit_get_relative_account_history) {\n try{\n   graphene::app::history_api hist_api(app);\n   //account_id_type() do 3 ops\n   create_bitasset(\"USD\", account_id_type());\n   create_account(\"dan\");\n   create_account(\"bob\");\n\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n\n   GRAPHENE_CHECK_THROW(hist_api.get_relative_account_history(\"1.2.0\", 126, 260, 0), fc::exception);\n   vector<operation_history_object> histories = hist_api.get_relative_account_history(\"1.2.0\", 126, 210, 0);\n   BOOST_REQUIRE_EQUAL( histories.size(), 0u );\n\n } catch (fc::exception &e) {\n   edump((e.to_detail_string()));\n   throw;\n }\n}\n\nBOOST_AUTO_TEST_CASE(api_limit_get_account_history_by_operations) {\n try {\n   graphene::app::history_api hist_api(app);\n   flat_set<uint16_t> operation_types;\n   //account_id_type() do 3 ops\n   create_bitasset(\"USD\", account_id_type());\n   create_account(\"dan\");\n   create_account(\"bob\");\n   generate_block();\n   fc::usleep(fc::milliseconds(100));\n   GRAPHENE_CHECK_THROW(hist_api.get_account_history_by_operations(\"1.2.0\", operation_types, 0, 260), fc::exception);\n   auto histories = hist_api.get_account_history_by_operations(\"1.2.0\", operation_types, 0, 210);\n   BOOST_REQUIRE_EQUAL( histories.total_count, 3u );\n }\n catch (fc::exception &e) {\n   edump((e.to_detail_string()));\n   throw;\n }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/htlc_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 jmjatlanta and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n// below are for random bytes for htlc\n#include <vector>\n#include <random>\n#include <climits>\n#include <algorithm>\n#include <functional>\n// for htlc timeout\n#include <chrono>\n#include <thread>\n\n#include <boost/test/unit_test.hpp>\n\n#include <boost/container/flat_set.hpp>\n\n#include <fc/optional.hpp>\n\n#include <graphene/protocol/htlc.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/htlc_object.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( htlc_tests, database_fixture )\n\nvoid generate_random_preimage(uint16_t key_size, std::vector<char>& vec)\n{\n\tstd::independent_bits_engine<std::default_random_engine, sizeof(unsigned), unsigned int> rbe;\n\tstd::generate(begin(vec), end(vec), std::ref(rbe));\n\treturn;\n}\n\nvoid advance_past_htlc_first_hardfork(database_fixture* db_fixture)\n{\n   db_fixture->generate_blocks(HARDFORK_CORE_1468_TIME);\n   set_expiration(db_fixture->db, db_fixture->trx);\n   db_fixture->set_htlc_committee_parameters();\n   set_expiration(db_fixture->db, db_fixture->trx);\n}\n\nBOOST_AUTO_TEST_CASE( htlc_expires )\n{\ntry {\n   ACTORS((alice)(bob));\n\n   int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n\n   advance_past_htlc_first_hardfork(this);\n\n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(256);\n   generate_random_preimage(preimage_size, pre_image);\n\n   graphene::chain::htlc_id_type alice_htlc_id;\n   // cler everything out\n   generate_block();\n   trx.clear();\n   // Alice puts a contract on the blockchain\n   {\n      graphene::chain::htlc_create_operation create_operation;\n      BOOST_TEST_MESSAGE(\"Alice (who has 100 coins, is transferring 3 coins to Bob\");\n      create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 60;\n      create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      processed_transaction alice_trx = PUSH_TX(db, trx, ~0);\n      trx.clear();\n      generate_block();\n      alice_htlc_id = alice_trx.operation_results[0].get<object_id_type>();\n      generate_block();\n   }\n\n   // verify funds on hold... 100 - 3 = 97, minus the 4 coin fee = 93\n   BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 93 * GRAPHENE_BLOCKCHAIN_PRECISION );\n\n   // make sure Bob (or anyone) can see the details of the transaction\n   graphene::app::database_api db_api(db);\n   auto obj = db_api.get_objects( { object_id_type(alice_htlc_id) }).front();\n   graphene::chain::htlc_object htlc = obj.template as<graphene::chain::htlc_object>(GRAPHENE_MAX_NESTED_OBJECTS);\n\n   // someone else attempts to extend it (bob says he's alice, but he's not)\n   {\n      graphene::chain::htlc_extend_operation bad_extend;\n      bad_extend.htlc_id = alice_htlc_id;\n      bad_extend.seconds_to_add = 10;\n      bad_extend.fee = db.get_global_properties().parameters.current_fees->calculate_fee(bad_extend);\n      bad_extend.update_issuer = alice_id;\n      trx.operations.push_back(bad_extend);\n      sign(trx, bob_private_key);\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, database::skip_nothing ), fc::exception );\n      trx.clear();\n   }\n   // someone else attempts to extend it (bob wants to extend Alice's contract)\n   {\n      graphene::chain::htlc_extend_operation bad_extend;\n      bad_extend.htlc_id = alice_htlc_id;\n      bad_extend.seconds_to_add = 10;\n      bad_extend.fee = db.get_global_properties().parameters.current_fees->calculate_fee(bad_extend);\n      bad_extend.update_issuer = bob_id;\n      trx.operations.push_back(bad_extend);\n      sign(trx, bob_private_key);\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0 ), fc::exception );\n      trx.clear();\n   }\n   // attempt to extend it with too much time\n   {\n      graphene::chain::htlc_extend_operation big_extend;\n      big_extend.htlc_id = alice_htlc_id;\n      big_extend.seconds_to_add = db.get_global_properties().parameters.extensions.value\n            .updatable_htlc_options->max_timeout_secs + 10;\n      big_extend.fee = db.get_global_properties().parameters.current_fees->calculate_fee(big_extend);\n      big_extend.update_issuer = alice_id;\n      trx.operations.push_back(big_extend);\n      sign(trx, alice_private_key);\n      GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      trx.clear();\n   }\n\n   // attempt to extend properly\n   {\n      graphene::chain::htlc_extend_operation extend;\n      extend.htlc_id = alice_htlc_id;\n      extend.seconds_to_add = 10;\n      extend.fee = db.get_global_properties().parameters.current_fees->calculate_fee(extend);\n      extend.update_issuer = alice_id;\n      trx.operations.push_back(extend);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n\n   // let it expire (wait for timeout)\n   generate_blocks( db.head_block_time() + fc::seconds(120) );\n   // verify funds return (minus the fees)\n   BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 92 * GRAPHENE_BLOCKCHAIN_PRECISION );\n   // verify Bob cannot execute the contract after the fact\n} FC_LOG_AND_RETHROW()\n}\n\n/****\n * @brief helper to create htlc_create_operation\n */\nhtlc_create_operation create_htlc(const database& db, const account_id_type& from, const account_id_type& to,\n      const asset& amount, const graphene::protocol::htlc_hash& preimage_hash, uint16_t preimage_size,\n      uint64_t seconds, const fc::optional<memo_data>& memo = fc::optional<memo_data>())\n{\n   htlc_create_operation ret_val;\n   ret_val.from = from;\n   ret_val.to = to;\n   ret_val.amount = amount;\n   ret_val.preimage_hash = preimage_hash;\n   ret_val.preimage_size = preimage_size;\n   ret_val.claim_period_seconds = seconds;\n   ret_val.extensions.value.memo = memo;\n   ret_val.fee = db.get_global_properties().parameters.current_fees->calculate_fee(ret_val);\n   return ret_val;\n}\n\n/****\n * @brief helper to create a proposal\n */\nproposal_create_operation create_proposal(const database& db, const account_id_type& from, \n      const htlc_create_operation& op, const fc::time_point_sec& expiration)\n{\n      proposal_create_operation ret_val;\n      ret_val.fee_paying_account = from;\n      ret_val.expiration_time = expiration;\n      ret_val.proposed_ops.emplace_back(op);\n      ret_val.fee = db.get_global_properties().parameters.current_fees->calculate_fee(ret_val);\n      return ret_val;\n}\n\nBOOST_AUTO_TEST_CASE( htlc_hf_bsip64 )\n{\ntry {\n   ACTORS((alice)(bob)(joker));\n\n   int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n   transfer( committee_account, joker_id, graphene::chain::asset(init_balance) );\n   transfer( committee_account, bob_id, graphene::chain::asset(init_balance) );\n\n   advance_past_htlc_first_hardfork(this);\n\n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(256);\n   generate_random_preimage(preimage_size, pre_image);\n\n   graphene::chain::htlc_id_type alice_htlc_id;\n   // cler everything out\n   generate_block();\n   trx.clear();\n\n   /***\n    * Proposals before the hardfork\n    */\n   {\n      BOOST_TEST_MESSAGE(\n            \"Alice is creating a proposal with an HTLC that contains a memo before the hardfork (should fail)\");\n      memo_data data;\n      data.from = alice_public_key;\n      data.to = bob_public_key;\n      data.set_message( alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60, data);\n      graphene::chain::proposal_create_operation prop1 = create_proposal(\n            db, alice_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop1);\n      sign(trx, alice_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"memo\");\n      trx.clear();\n   }   \n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating a proposal with HASH160 (should fail)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::hash160>(pre_image), 0, 60);\n      graphene::chain::proposal_create_operation prop1 = create_proposal(\n            db, alice_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop1);\n      sign(trx, alice_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"HASH160\");\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating a proposal with a preimage size of 0 (should pass)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60);\n      graphene::chain::proposal_create_operation prop1 = create_proposal(\n            db, alice_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop1);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n   /***\n    * HTLC Contracts (non-proposals) before hardfork\n    */\n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating an HTLC that contains a memo before the hardfork (should fail)\");\n      memo_data data;\n      data.from = alice_public_key;\n      data.to = bob_public_key;\n      data.set_message( alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60, data);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"memo\");\n      trx.clear();\n   }   \n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating an HTLC with HASH160 (should fail)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::hash160>(pre_image), 0, 60);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"HASH160\");\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating an HTLC with a preimage size of 0 (should pass)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      htlc_id_type htlc_id { PUSH_TX(db, trx, ~0).operation_results[0].get<object_id_type>() };\n      trx.clear();\n      BOOST_TEST_MESSAGE(\"Bob attempts to redeem, but can't because preimage size is 0 (should fail)\");\n      graphene::chain::htlc_redeem_operation redeem;\n      redeem.htlc_id = htlc_id;\n      redeem.preimage = pre_image;\n      redeem.redeemer = bob_id;\n      redeem.fee = db.current_fee_schedule().calculate_fee(redeem);\n      trx.operations.push_back(redeem);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"Preimage size mismatch\");\n      trx.clear();\n   }\n\n   // Alice creates an asset\n   BOOST_TEST_MESSAGE(\"Create ALICECOIN so transfer_restricted can be controlled\");\n   const asset_id_type uia_id = create_user_issued_asset( \"ALICECOIN\", alice, transfer_restricted).get_id();\n   BOOST_TEST_MESSAGE(\"Issuing ALICECOIN to Bob\");\n   issue_uia(bob, asset(10000, uia_id) );\n   // verify transfer restrictions are in place\n   REQUIRE_EXCEPTION_WITH_TEXT(transfer(bob, joker, asset(1, uia_id)), \"transfer\");\n   trx.operations.clear();\n\n   {\n      BOOST_TEST_MESSAGE(\"Bob wants to transfer ALICECOIN, which is transfer_restricted (allowed before HF)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id,\n         asset(3, uia_id), hash_it<fc::sha256>(pre_image), preimage_size, 60);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\n            \"Bob wants to transfer ALICECOIN within a proposal, always allowed, although will fail later\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, asset(3,uia_id),\n            hash_it<fc::sha256>(pre_image), preimage_size, 60);\n      graphene::chain::proposal_create_operation prop_create = create_proposal(\n            db, bob_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop_create);\n      sign(trx, bob_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n\n   BOOST_TEST_MESSAGE(\"Fast Forwarding beyond HF BSIP64\");\n   generate_blocks( HARDFORK_CORE_BSIP64_TIME + 60 );\n   set_expiration( db, trx );\n\n   /***\n    * Proposals after the hardfork\n    */\n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating a proposal with an HTLC that contains a memo (should pass)\");\n      memo_data data;\n      data.from = alice_public_key;\n      data.to = bob_public_key;\n      data.set_message( alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60, data);\n      graphene::chain::proposal_create_operation prop1 = create_proposal(\n            db, alice_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop1);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }   \n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating a proposal with HASH160 (should pass)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::hash160>(pre_image), 0, 60);\n      graphene::chain::proposal_create_operation prop1 = create_proposal(\n            db, alice_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop1);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating a proposal with a preimage size of 0 (should pass)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60);\n      graphene::chain::proposal_create_operation prop1 = create_proposal(\n            db, alice_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop1);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n   /***\n    * HTLC Contracts (non-proposals) after hardfork\n    */\n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating an HTLC that contains a memo after the hardfork (should pass)\");\n      memo_data data;\n      data.from = alice_public_key;\n      data.to = bob_public_key;\n      data.set_message( alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::sha256>(pre_image), 0, 60, data);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }   \n   {\n      BOOST_TEST_MESSAGE(\"Alice is creating an HTLC with HASH160 (should pass)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, \n            asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it<fc::hash160>(pre_image), 0, 60);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"Bob wants to transfer ALICECOIN, which is transfer_restricted (no longer allowed)\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id,\n         asset(3, uia_id), hash_it<fc::sha256>(pre_image), preimage_size, 60);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"transfer_restricted\");\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\n            \"Bob wants to transfer ALICECOIN within a proposal, always allowed, although will fail later\");\n      graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, asset(3,uia_id),\n            hash_it<fc::sha256>(pre_image), preimage_size, 60);\n      graphene::chain::proposal_create_operation prop_create = create_proposal(\n            db, bob_id, create_operation, db.head_block_time() + 100);\n      trx.operations.push_back(prop_create);\n      sign(trx, bob_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.clear();\n   }\n   {\n      BOOST_TEST_MESSAGE(\"A memo field should include a charge per kb (uses fee from transfer_operation)\");\n      htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3), hash_it<fc::sha256>(pre_image),\n            preimage_size, 60);\n      asset no_memo_fee = op.fee;\n      memo_data data;\n      data.from = alice_public_key;\n      data.to = bob_public_key;\n      data.set_message( alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n      op.extensions.value.memo = data;\n      asset with_memo_fee = db.current_fee_schedule().calculate_fee(op);\n      BOOST_CHECK_GT( with_memo_fee.amount.value, no_memo_fee.amount.value );\n   }\n   // After HF 64, a zero in the preimage_size means 2 things, no preimage (can happen, but who would do such a\n   // thing), or simply skip the size validation. To test, we must attempt to redeem both cases\n   {\n      // case 1: 0 preimage with 0 preimage_size\n      BOOST_TEST_MESSAGE(\"Attempt to create an HTLC with no preimage (should pass)\");\n      htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION),\n            hash_it<fc::sha256>(std::vector<char>()), 0, 60);\n      trx.operations.push_back(op);\n      sign(trx, alice_private_key);\n      graphene::protocol::processed_transaction results = PUSH_TX(db, trx, ~0);\n      trx.operations.clear();\n      htlc_id_type htlc_id { results.operation_results[0].get<object_id_type>() };\n      BOOST_TEST_MESSAGE(\"Attempt to redeem HTLC that has no preimage, but include one anyway (should fail)\");\n      htlc_redeem_operation redeem;\n      redeem.htlc_id = htlc_id;\n      redeem.preimage = pre_image;\n      redeem.redeemer = bob_id;\n      redeem.fee = db.current_fee_schedule().calculate_fee(redeem);\n      trx.operations.push_back(redeem);\n      sign(trx, bob_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"Provided preimage does not generate\");\n      trx.operations.clear();\n      BOOST_TEST_MESSAGE(\"Attempt to redeem HTLC that has no preimage (should pass)\");\n      redeem.preimage = std::vector<char>();\n      redeem.fee = db.current_fee_schedule().calculate_fee(redeem);\n      trx.operations.push_back(redeem);\n      sign(trx, bob_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.operations.clear();\n   }\n   {\n      // case 2: a real preimage with 0 preimage size\n      htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION),\n            hash_it<fc::hash160>(pre_image), 0, 60);\n      trx.operations.push_back(op);\n      sign(trx, alice_private_key);\n      graphene::protocol::processed_transaction results = PUSH_TX(db, trx, ~0);\n      trx.operations.clear();\n      htlc_id_type htlc_id { results.operation_results[0].get<object_id_type>() };\n      BOOST_TEST_MESSAGE(\"Attempt to redeem with no preimage (should fail)\");\n      htlc_redeem_operation redeem;\n      redeem.htlc_id = htlc_id;\n      redeem.preimage = std::vector<char>();\n      redeem.redeemer = bob_id;\n      redeem.fee = db.current_fee_schedule().calculate_fee(redeem);\n      trx.operations.push_back(redeem);\n      sign(trx, bob_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"Provided preimage does not generate\");\n      trx.operations.clear();\n      BOOST_TEST_MESSAGE(\"Attempt to redeem with no preimage size, but incorrect preimage\");\n      redeem.preimage = std::vector<char>{'H','e','l','l','o'};\n      redeem.fee = db.current_fee_schedule().calculate_fee(redeem);\n      trx.operations.push_back(redeem);\n      sign(trx, bob_private_key);\n      REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), \"Provided preimage does not generate\");\n      trx.operations.clear();\n      BOOST_TEST_MESSAGE(\"Attempt to redeem with no preimage size (should pass)\");\n      redeem.preimage = pre_image;\n      redeem.fee = db.current_fee_schedule().calculate_fee(redeem);\n      trx.operations.push_back(redeem);\n      sign(trx, bob_private_key);\n      PUSH_TX(db, trx, ~0);\n      trx.operations.clear();\n   }\n} FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( htlc_fulfilled )\n{\ntry {\n   ACTORS((alice)(bob)(joker));\n\n   int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n   transfer( committee_account, bob_id, graphene::chain::asset(init_balance) );\n   transfer( committee_account, joker_id, graphene::chain::asset(init_balance) );\n\n   advance_past_htlc_first_hardfork(this);\n   \n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(preimage_size);\n   generate_random_preimage(preimage_size, pre_image);\n\n   graphene::chain::htlc_id_type alice_htlc_id;\n   // clear everything out\n   generate_block();\n   trx.clear();\n   // Alice puts a contract on the blockchain\n   {\n      graphene::chain::htlc_create_operation create_operation;\n\n      create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 86400;\n      create_operation.preimage_hash = hash_it<fc::sha1>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n      trx.operations.push_back( create_operation );\n      sign(trx, alice_private_key);\n      processed_transaction alice_trx = PUSH_TX(db, trx, ~0);\n      trx.clear();\n      generate_block();\n      alice_htlc_id = alice_trx.operation_results[0].get<object_id_type>();\n   }\n\n   // make sure Alice's money gets put on hold (100 - 20 - 4(fee) )\n   BOOST_CHECK_EQUAL( get_balance( alice_id, graphene::chain::asset_id_type()), 76 * GRAPHENE_BLOCKCHAIN_PRECISION );\n\n   // extend the timeout so that Bob has more time\n   {\n      graphene::chain::htlc_extend_operation extend_operation;\n      extend_operation.htlc_id = alice_htlc_id;\n      extend_operation.seconds_to_add = 86400;\n      extend_operation.update_issuer = alice_id;\n      extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation );\n      trx.operations.push_back( extend_operation );\n      sign( trx, alice_private_key );\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n      generate_blocks( db.head_block_time() + fc::seconds(87000) );\n      set_expiration( db, trx );\n   }\n\n   // make sure Alice's money is still on hold, and account for extra fee\n   BOOST_CHECK_EQUAL( get_balance( alice_id, graphene::chain::asset_id_type()), 72 * GRAPHENE_BLOCKCHAIN_PRECISION );\n\n   // grab number of history objects to make sure everyone gets notified\n   size_t alice_num_history = get_operation_history(alice_id).size();\n   size_t bob_num_history = get_operation_history(bob_id).size();\n   size_t joker_num_history = get_operation_history(joker_id).size();\n\n   // joker sends a redeem operation to claim the funds for bob\n   {\n      graphene::chain::htlc_redeem_operation update_operation;\n      update_operation.redeemer = joker_id;\n      update_operation.htlc_id = alice_htlc_id;\n      update_operation.preimage = pre_image;\n      update_operation.fee = db.current_fee_schedule().calculate_fee( update_operation );\n      trx.operations.push_back( update_operation );\n      sign(trx, joker_private_key);\n      PUSH_TX( db, trx, ~0 );\n      generate_block();\n      trx.clear();\n   }\n   // verify funds end up in Bob's account (100 + 20 )\n   BOOST_CHECK_EQUAL( get_balance(bob_id,   graphene::chain::asset_id_type()), 120 * GRAPHENE_BLOCKCHAIN_PRECISION );\n   // verify funds remain out of Alice's acount ( 100 - 20 - 4 )\n   BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 72 * GRAPHENE_BLOCKCHAIN_PRECISION );\n   // verify all three get notified\n   BOOST_CHECK_EQUAL( get_operation_history(alice_id).size(), alice_num_history + 1);\n   BOOST_CHECK_EQUAL( get_operation_history(bob_id).size(), bob_num_history + 1);\n   BOOST_CHECK_EQUAL( get_operation_history(joker_id).size(), joker_num_history + 1);\n} FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( other_peoples_money )\n{\ntry {\n   advance_past_htlc_first_hardfork(this);\n\t\n   ACTORS((alice)(bob));\n\n   int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION );\n\n   transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n\n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(256);\n   generate_random_preimage(preimage_size, pre_image);\n\n   graphene::chain::htlc_id_type alice_htlc_id;\n   // cler everything out\n   generate_block();\n   trx.clear();\n   // Bob attempts to put a contract on the blockchain using Alice's funds\n   {\n      graphene::chain::htlc_create_operation create_operation;\n      create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 3;\n      create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n      trx.operations.push_back(create_operation);\n      sign(trx, bob_private_key);\n      GRAPHENE_CHECK_THROW( PUSH_TX( db, trx ), fc::exception);\n      trx.clear();\n   }\n   // now try the same but with Alice's signature (should work)\n   {\n      graphene::chain::htlc_create_operation create_operation;\n      create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 3;\n      create_operation.preimage_hash = hash_it<fc::ripemd160>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n} FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( htlc_hardfork_test )\n{ \n   try {\n\n      {\n         // try to set committee parameters before hardfork\n         proposal_create_operation cop = proposal_create_operation::committee_proposal(\n               db.get_global_properties().parameters, db.head_block_time());\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n         committee_member_update_global_parameters_operation cmuop;\n         graphene::chain::htlc_options new_params;\n         new_params.max_preimage_size = 2048;\n         new_params.max_timeout_secs = 60 * 60 * 24 * 28;\n         cmuop.new_parameters.extensions.value.updatable_htlc_options = new_params;\n         cop.proposed_ops.emplace_back(cmuop);\n         trx.operations.push_back(cop);\n         // update with signatures\n         proposal_update_operation uop;\n         uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         uop.active_approvals_to_add = {get_account(\"init0\").get_id(), get_account(\"init1\").get_id(),\n                                       get_account(\"init2\").get_id(), get_account(\"init3\").get_id(),\n                                       get_account(\"init4\").get_id(), get_account(\"init5\").get_id(),\n                                       get_account(\"init6\").get_id(), get_account(\"init7\").get_id()};\n         trx.operations.push_back(uop);\n         sign( trx, init_account_priv_key );\n         BOOST_TEST_MESSAGE(\"Sending proposal.\");\n         GRAPHENE_CHECK_THROW(db.push_transaction(trx), fc::exception);\n         BOOST_TEST_MESSAGE(\"Verifying that proposal did not succeeed.\");\n         BOOST_CHECK(!db.get_global_properties().parameters.extensions.value.updatable_htlc_options.valid());\n         trx.clear();\n      }\n\n      {\n         BOOST_TEST_MESSAGE(\"Attempting to set HTLC fees before hard fork.\");\n\n         // get existing fee_schedule\n         const chain_parameters& existing_params = db.get_global_properties().parameters;\n         const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees);\n         // create a new fee_shedule\n         std::shared_ptr<fee_schedule_type> new_fee_schedule = std::make_shared<fee_schedule_type>();\n         new_fee_schedule->scale = existing_fee_schedule.scale;\n         // replace the old with the new\n         flat_map<uint64_t, graphene::chain::fee_parameters> params_map = get_htlc_fee_parameters();\n         for(auto param : existing_fee_schedule.parameters)\n         {\n            auto itr = params_map.find(param.which());\n            if (itr == params_map.end())\n               new_fee_schedule->parameters.insert(param);\n            else\n            {\n               new_fee_schedule->parameters.insert( (*itr).second);\n            }\n         }\n         proposal_create_operation cop = proposal_create_operation::committee_proposal(\n               db.get_global_properties().parameters, db.head_block_time());\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n         committee_member_update_global_parameters_operation uop;\n         uop.new_parameters.current_fees = new_fee_schedule;\n         cop.proposed_ops.emplace_back(uop);\n         cop.fee = asset( 100000 );\n         trx.operations.push_back( cop );\n         GRAPHENE_CHECK_THROW(db.push_transaction( trx ), fc::exception);\n         trx.clear();\n      }\n\n      // now things should start working...\n      BOOST_TEST_MESSAGE(\"Advancing to HTLC hardfork time.\");\n      advance_past_htlc_first_hardfork(this);\n\n      proposal_id_type good_proposal_id;\n      BOOST_TEST_MESSAGE( \"Creating a proposal to change the max_preimage_size to 2048 and set higher fees\" );\n      {\n         // get existing fee_schedule\n         const chain_parameters& existing_params = db.get_global_properties().parameters;\n         const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees);\n         // create a new fee_shedule\n         std::shared_ptr<fee_schedule_type> new_fee_schedule = std::make_shared<fee_schedule_type>();\n         new_fee_schedule->scale = existing_fee_schedule.scale;\n         // replace the old with the new\n         flat_map<uint64_t, graphene::chain::fee_parameters> params_map = get_htlc_fee_parameters();\n         for(auto param : existing_fee_schedule.parameters)\n         {\n            auto itr = params_map.find(param.which());\n            if (itr == params_map.end())\n               new_fee_schedule->parameters.insert(param);\n            else\n            {\n               new_fee_schedule->parameters.insert( (*itr).second);\n            }\n         }\n         proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties()\n               .parameters, db.head_block_time());\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10;\n         committee_member_update_global_parameters_operation uop;\n         graphene::chain::htlc_options new_params;\n         new_params.max_preimage_size = 2048;\n         new_params.max_timeout_secs = 60 * 60 * 24 * 28;\n         uop.new_parameters.extensions.value.updatable_htlc_options = new_params;\n         uop.new_parameters.current_fees = new_fee_schedule;\n         cop.proposed_ops.emplace_back(uop);\n         trx.operations.push_back(cop);\n         graphene::chain::processed_transaction proc_trx =db.push_transaction(trx);\n         good_proposal_id = proc_trx.operation_results[0].get<object_id_type>();\n      }\n\n      BOOST_TEST_MESSAGE( \"Updating proposal by signing with the committee_member private key\" );\n      {\n         proposal_update_operation uop;\n         uop.proposal = good_proposal_id;\n         uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         uop.active_approvals_to_add = {get_account(\"init0\").get_id(), get_account(\"init1\").get_id(),\n                                       get_account(\"init2\").get_id(), get_account(\"init3\").get_id(),\n                                       get_account(\"init4\").get_id(), get_account(\"init5\").get_id(),\n                                       get_account(\"init6\").get_id(), get_account(\"init7\").get_id()};\n         trx.operations.push_back(uop);\n         sign( trx, init_account_priv_key );\n         db.push_transaction(trx);\n         BOOST_CHECK(good_proposal_id(db).is_authorized_to_execute(db));\n      }\n      BOOST_TEST_MESSAGE( \"Verifying that the parameters didn't change immediately\" );\n\n      BOOST_CHECK_EQUAL(\n            db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u);\n\n      BOOST_TEST_MESSAGE( \"Generating blocks until proposal expires\" );\n      generate_blocks(good_proposal_id(db).expiration_time + 5);\n      BOOST_TEST_MESSAGE( \"Verify that the parameters still have not changed\" );\n      BOOST_CHECK_EQUAL(db.get_global_properties()\n            .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u);\n\n      BOOST_TEST_MESSAGE( \"Generating blocks until next maintenance interval\" );\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();   // get the maintenance skip slots out of the way\n\n      BOOST_TEST_MESSAGE( \"Verify that the change has been implemented\" );\n      \n      BOOST_CHECK_EQUAL(db.get_global_properties()\n            .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u);\n      const graphene::chain::fee_schedule& current_fee_schedule =\n            *(db.get_global_properties().parameters.current_fees);\n      const htlc_create_operation::fee_params_t& htlc_fee\n            = current_fee_schedule.get<htlc_create_operation>();\n      BOOST_CHECK_EQUAL(htlc_fee.fee, 2 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   } \n   FC_LOG_AND_RETHROW() \n}\n\nBOOST_AUTO_TEST_CASE( htlc_before_hardfork )\n{ try {\n   ACTORS((alice)(bob));\n\n   int64_t init_balance(100000);\n\n   transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n\n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(256);\n   generate_random_preimage(preimage_size, pre_image);\n\n   graphene::chain::htlc_id_type alice_htlc_id;\n   // clear everything out\n   generate_block();\n   trx.clear();\n\n   // Alice tries to put a contract on the blockchain\n   {\n      graphene::chain::htlc_create_operation create_operation;\n\n      create_operation.amount = graphene::chain::asset( 10000 );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 60;\n      create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      GRAPHENE_CHECK_THROW(PUSH_TX(db, trx, ~0), fc::exception);\n      trx.clear();\n   }\n\n   // Propose htlc_create\n   {\n      proposal_create_operation pco;\n      pco.expiration_time = db.head_block_time() + fc::minutes(1);\n      pco.fee_paying_account = alice_id;\n\n      graphene::chain::htlc_create_operation create_operation;\n      create_operation.amount = graphene::chain::asset( 10000 );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 60;\n      create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n\n      pco.proposed_ops.emplace_back( create_operation );\n      trx.operations.push_back( pco );\n      GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception );\n      trx.clear();\n   }\n\n   // Propose htlc_redeem\n   {\n      proposal_create_operation pco;\n      pco.expiration_time = db.head_block_time() + fc::minutes(1);\n      pco.fee_paying_account = alice_id;\n\n      graphene::chain::htlc_redeem_operation rop;\n      rop.redeemer = bob_id;\n      rop.htlc_id = alice_htlc_id;\n      string preimage_str = \"Arglebargle\";\n      rop.preimage.insert( rop.preimage.begin(), preimage_str.begin(), preimage_str.end() );\n\n      pco.proposed_ops.emplace_back( rop );\n      trx.operations.push_back( pco );\n      GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception );\n      trx.clear();\n   }\n\n   // Propose htlc_extend\n   {\n      proposal_create_operation pco;\n      pco.expiration_time = db.head_block_time() + fc::minutes(1);\n      pco.fee_paying_account = alice_id;\n\n      graphene::chain::htlc_extend_operation xop;\n      xop.htlc_id = alice_htlc_id;\n      xop.seconds_to_add = 100;\n      xop.update_issuer = alice_id;\n\n      pco.proposed_ops.emplace_back( xop );\n      trx.operations.push_back( pco );\n      GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception );\n      trx.clear();\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( fee_calculations )\n{\n   // create\n   {\n      htlc_create_operation::fee_params_t create_fee;\n      create_fee.fee = 2;\n      create_fee.fee_per_day = 2;\n      htlc_create_operation create;\n      // no days\n      create.claim_period_seconds = 0;\n      BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 2 );\n      // exactly 1 day\n      create.claim_period_seconds = 60 * 60 * 24;\n      BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 4 );\n      // tad over a day\n      create.claim_period_seconds++;\n      BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 6 );\n   }\n   // redeem\n   {\n      htlc_redeem_operation::fee_params_t redeem_fee;\n      redeem_fee.fee_per_kb = 2;\n      redeem_fee.fee = 2;\n      htlc_redeem_operation redeem;\n      // no preimage\n      redeem.preimage = std::vector<char>();\n      BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 2 ) ;\n      // exactly 1KB\n      std::string test(1024, 'a');\n      redeem.preimage = std::vector<char>( test.begin(), test.end() );\n      BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 4 ) ;\n      // just 1 byte over 1KB\n      std::string larger(1025, 'a');\n      redeem.preimage = std::vector<char>( larger.begin(), larger.end() );\n      BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 6 ) ;\n   }\n   // extend\n   {\n      htlc_extend_operation::fee_params_t extend_fee;\n      extend_fee.fee = 2;\n      extend_fee.fee_per_day = 2;\n      htlc_extend_operation extend;\n      // no days\n      extend.seconds_to_add = 0;\n      BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 2 );\n      // exactly 1 day\n      extend.seconds_to_add = 60 * 60 * 24;\n      BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 4 );\n      // 1 day and 1 second\n      extend.seconds_to_add++;\n      BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 6 );\n   }\n}\n\nBOOST_AUTO_TEST_CASE( htlc_blacklist )\n{\ntry {\n   ACTORS((nathan)(alice)(bob));\n\n   upgrade_to_lifetime_member( nathan );\n\n   // create a UIA\n   const asset_id_type uia_id = create_user_issued_asset( \"NATHANCOIN\", nathan, white_list ).get_id();\n   // Make a whitelist authority\n   {\n      BOOST_TEST_MESSAGE( \"Changing the whitelist authority\" );\n      asset_update_operation uop;\n      uop.issuer = nathan_id;\n      uop.asset_to_update = uia_id;\n      uop.new_options = uia_id(db).options;\n      uop.new_options.blacklist_authorities.insert(nathan_id);\n      trx.operations.push_back(uop);\n      PUSH_TX( db, trx, ~0 );\n      trx.operations.clear();\n   }\n\n\n   int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n   fund( alice, graphene::chain::asset(init_balance) );\n   fund( bob, graphene::chain::asset(init_balance) );\n\n   advance_past_htlc_first_hardfork(this);\n\n   // blacklist bob\n   {\n      graphene::chain::account_whitelist_operation op;\n      op.authorizing_account = nathan_id;\n      op.account_to_list = bob_id;\n      op.new_listing = graphene::chain::account_whitelist_operation::account_listing::black_listed;\n      op.fee = db.current_fee_schedule().calculate_fee( op );\n      trx.operations.push_back( op );\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n      generate_block();\n   }\n\n   issue_uia( alice_id, asset( init_balance, uia_id ) );\n\n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(preimage_size);\n   generate_random_preimage(preimage_size, pre_image);\n\n   // Alice attempts to put a contract on the blockchain\n   {\n      graphene::chain::htlc_create_operation create_operation;\n\n      create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION, uia_id );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 86400;\n      create_operation.preimage_hash = hash_it<fc::sha1>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n      trx.operations.push_back( create_operation );\n      sign(trx, alice_private_key);\n      // bob cannot accept it, so it fails\n      GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n      trx.clear();\n   }\n\n   // unblacklist Bob\n   {\n      graphene::chain::account_whitelist_operation op;\n      op.authorizing_account = nathan_id;\n      op.account_to_list = bob_id;\n      op.new_listing = graphene::chain::account_whitelist_operation::account_listing::no_listing;\n      op.fee = db.current_fee_schedule().calculate_fee( op );\n      trx.operations.push_back( op );\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n      generate_block();\n   }\n\n   graphene::chain::htlc_id_type alice_htlc_id;\n\n   // Alice again attempts to put a contract on the blockchain\n   {\n      graphene::chain::htlc_create_operation create_operation;\n\n      create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION, uia_id );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 86400;\n      create_operation.preimage_hash = hash_it<fc::sha1>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation );\n      trx.operations.push_back( create_operation );\n      sign(trx, alice_private_key);\n      // bob can now accept it, so it works\n      processed_transaction alice_trx = PUSH_TX( db, trx, ~0 );\n      trx.clear();\n      generate_block();\n      alice_htlc_id = alice_trx.operation_results[0].get<object_id_type>();\n   }\n\n   // blacklist bob\n   {\n      graphene::chain::account_whitelist_operation op;\n      op.authorizing_account = nathan_id;\n      op.account_to_list = bob_id;\n      op.new_listing = graphene::chain::account_whitelist_operation::account_listing::black_listed;\n      op.fee = db.current_fee_schedule().calculate_fee( op );\n      trx.operations.push_back( op );\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n      generate_block();\n   }\n\n   // bob can redeem even though he's blacklisted\n   {\n      graphene::chain::htlc_redeem_operation update_operation;\n      update_operation.redeemer = bob_id;\n      update_operation.htlc_id = alice_htlc_id;\n      update_operation.preimage = pre_image;\n      update_operation.fee = db.current_fee_schedule().calculate_fee( update_operation );\n      trx.operations.push_back( update_operation );\n      sign(trx, bob_private_key);\n      PUSH_TX( db, trx, ~0 );\n      generate_block();\n      trx.clear();\n   }\n\n} FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(htlc_database_api) {\ntry {\n\n   ACTORS((alice)(bob)(carl)(dan));\n\n   int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION);\n\n   transfer( committee_account, alice_id, graphene::chain::asset(init_balance) );\n\n   generate_blocks(HARDFORK_CORE_1468_TIME);\n   set_expiration( db, trx );\n\n   set_htlc_committee_parameters();\n\n   uint16_t preimage_size = 256;\n   std::vector<char> pre_image(256);\n   std::independent_bits_engine<std::default_random_engine, sizeof(unsigned), unsigned int> rbe;\n   std::generate(begin(pre_image), end(pre_image), std::ref(rbe));\n   graphene::chain::htlc_id_type alice_htlc_id_bob;\n   graphene::chain::htlc_id_type alice_htlc_id_carl;\n   graphene::chain::htlc_id_type alice_htlc_id_dan;\n\n   generate_block();\n   set_expiration( db, trx );\n   trx.clear();\n   // alice puts a htlc contract to bob\n   {\n      graphene::chain::htlc_create_operation create_operation;\n      BOOST_TEST_MESSAGE(\"Alice, who has 100 coins, is transferring 3 coins to Bob\");\n      create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = bob_id;\n      create_operation.claim_period_seconds = 60;\n      create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      processed_transaction alice_trx = PUSH_TX(db, trx, ~0);\n      trx.clear();\n      set_expiration( db, trx );\n      generate_block();\n      alice_htlc_id_bob = alice_trx.operation_results[0].get<object_id_type>();\n      generate_block();\n      set_expiration( db, trx );\n   }\n\n   trx.clear();\n   // alice puts a htlc contract to carl\n   {\n      graphene::chain::htlc_create_operation create_operation;\n      BOOST_TEST_MESSAGE(\"Alice, who has 100 coins, is transferring 3 coins to Carl\");\n      create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = carl_id;\n      create_operation.claim_period_seconds = 60;\n      create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      processed_transaction alice_trx = PUSH_TX(db, trx, ~0);\n      trx.clear();\n      set_expiration( db, trx );\n      generate_block();\n      alice_htlc_id_carl = alice_trx.operation_results[0].get<object_id_type>();\n      generate_block();\n      set_expiration( db, trx );\n   }\n\n   trx.clear();\n   // alice puts a htlc contract to dan\n   {\n      graphene::chain::htlc_create_operation create_operation;\n      BOOST_TEST_MESSAGE(\"Alice, who has 100 coins, is transferring 3 coins to Dan\");\n      create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION );\n      create_operation.to = dan_id;\n      create_operation.claim_period_seconds = 60;\n      create_operation.preimage_hash = hash_it<fc::sha256>( pre_image );\n      create_operation.preimage_size = preimage_size;\n      create_operation.from = alice_id;\n      create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation);\n      trx.operations.push_back(create_operation);\n      sign(trx, alice_private_key);\n      processed_transaction alice_trx = PUSH_TX(db, trx, ~0);\n      trx.clear();\n      set_expiration( db, trx );\n      generate_block();\n      alice_htlc_id_dan = alice_trx.operation_results[0].get<object_id_type>();\n      generate_block();\n      set_expiration( db, trx );\n   }\n\n   graphene::app::database_api db_api(db, &(this->app.get_options()) ) ;\n\n   auto htlc = db_api.get_htlc(alice_htlc_id_bob);\n   BOOST_CHECK_EQUAL( htlc->id.instance(), 0);\n   BOOST_CHECK_EQUAL( htlc->transfer.from.instance.value, 16 );\n   BOOST_CHECK_EQUAL( htlc->transfer.to.instance.value, 17 );\n\n   htlc = db_api.get_htlc(alice_htlc_id_carl);\n   BOOST_CHECK_EQUAL( htlc->id.instance(), 1);\n   BOOST_CHECK_EQUAL( htlc->transfer.from.instance.value, 16 );\n   BOOST_CHECK_EQUAL( htlc->transfer.to.instance.value, 18 );\n\n   htlc = db_api.get_htlc(alice_htlc_id_dan);\n   BOOST_CHECK_EQUAL( htlc->id.instance(), 2);\n   BOOST_CHECK_EQUAL( htlc->transfer.from.instance.value, 16 );\n   BOOST_CHECK_EQUAL( htlc->transfer.to.instance.value, 19 );\n\n   auto htlcs_alice = db_api.get_htlc_by_from(alice.name, graphene::chain::htlc_id_type(0), 100);\n   BOOST_CHECK_EQUAL( htlcs_alice.size(), 3 );\n   BOOST_CHECK_EQUAL( htlcs_alice[0].id.instance(), 0 );\n   BOOST_CHECK_EQUAL( htlcs_alice[1].id.instance(), 1 );\n   BOOST_CHECK_EQUAL( htlcs_alice[2].id.instance(), 2 );\n\n   htlcs_alice = db_api.get_htlc_by_from(alice.name, graphene::chain::htlc_id_type(1), 1);\n   BOOST_CHECK_EQUAL( htlcs_alice.size(), 1 );\n   BOOST_CHECK_EQUAL( htlcs_alice[0].id.instance(), 1 );\n\n   htlcs_alice = db_api.get_htlc_by_from(alice.name, graphene::chain::htlc_id_type(1), 2);\n   BOOST_CHECK_EQUAL( htlcs_alice.size(), 2 );\n   BOOST_CHECK_EQUAL( htlcs_alice[0].id.instance(), 1 );\n   BOOST_CHECK_EQUAL( htlcs_alice[1].id.instance(), 2 );\n\n   auto htlcs_bob = db_api.get_htlc_by_to(bob.name, graphene::chain::htlc_id_type(0), 100);\n   BOOST_CHECK_EQUAL( htlcs_bob.size(), 1 );\n   BOOST_CHECK_EQUAL( htlcs_bob[0].id.instance(), 0 );\n\n   auto htlcs_carl = db_api.get_htlc_by_to(carl.name, graphene::chain::htlc_id_type(0), 100);\n   BOOST_CHECK_EQUAL( htlcs_carl.size(), 1 );\n   BOOST_CHECK_EQUAL( htlcs_carl[0].id.instance(), 1 );\n\n   auto htlcs_dan = db_api.get_htlc_by_to(dan.name, graphene::chain::htlc_id_type(0), 100);\n   BOOST_CHECK_EQUAL( htlcs_dan.size(), 1 );\n   BOOST_CHECK_EQUAL( htlcs_dan[0].id.instance(), 2 );\n\n   auto full = db_api.get_full_accounts({alice.name}, false);\n   BOOST_CHECK_EQUAL( full[alice.name].htlcs_from.size(), 3 );\n\n   full = db_api.get_full_accounts({bob.name}, false);\n   BOOST_CHECK_EQUAL( full[bob.name].htlcs_to.size(), 1 );\n\n   auto list = db_api.list_htlcs(graphene::chain::htlc_id_type(0), 1);\n   BOOST_CHECK_EQUAL( list.size(), 1 );\n   BOOST_CHECK_EQUAL( list[0].id.instance(), 0 );\n\n   list = db_api.list_htlcs(graphene::chain::htlc_id_type(1), 1);\n   BOOST_CHECK_EQUAL( list.size(), 1 );\n   BOOST_CHECK_EQUAL( list[0].id.instance(), 1 );\n\n   list = db_api.list_htlcs(graphene::chain::htlc_id_type(2), 1);\n   BOOST_CHECK_EQUAL( list.size(), 1 );\n   BOOST_CHECK_EQUAL( list[0].id.instance(), 2 );\n\n   list = db_api.list_htlcs(graphene::chain::htlc_id_type(1), 2);\n   BOOST_CHECK_EQUAL( list.size(), 2 );\n   BOOST_CHECK_EQUAL( list[0].id.instance(), 1 );\n   BOOST_CHECK_EQUAL( list[1].id.instance(), 2 );\n\n   list = db_api.list_htlcs(graphene::chain::htlc_id_type(1), 3);\n   BOOST_CHECK_EQUAL( list.size(), 2 );\n   BOOST_CHECK_EQUAL( list[0].id.instance(), 1 );\n   BOOST_CHECK_EQUAL( list[1].id.instance(), 2 );\n\n   list = db_api.list_htlcs(graphene::chain::htlc_id_type(0), 100);\n   BOOST_CHECK_EQUAL( list.size(), 3 );\n   BOOST_CHECK_EQUAL( list[0].id.instance(), 0 );\n   BOOST_CHECK_EQUAL( list[1].id.instance(), 1 );\n   BOOST_CHECK_EQUAL( list[2].id.instance(), 2 );\n\n   list = db_api.list_htlcs(graphene::chain::htlc_id_type(10), 100);\n   BOOST_CHECK_EQUAL( list.size(), 0 );\n\n} catch (fc::exception &e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/liquidity_pool_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/liquidity_pool_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( liquidity_pool_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_hardfork_time_test )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_BSIP_86_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      const asset_object& lpa = create_user_issued_asset( \"LPATEST\", sam, charge_market_fee );\n\n      // Before the hard fork, unable to create a liquidity pool or transact against a liquidity pool,\n      // or do any of them with proposals\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), 0, 0 ),\n                         fc::exception );\n\n      liquidity_pool_id_type tmp_lp_id;\n      BOOST_CHECK_THROW( delete_liquidity_pool( sam_id, tmp_lp_id ), fc::exception );\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, tmp_lp_id, core.amount(100), usd.amount(100) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( sam_id, tmp_lp_id, lpa.amount(100) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( sam_id, tmp_lp_id, core.amount(100), usd.amount(100) ),\n                         fc::exception );\n\n      liquidity_pool_create_operation cop =\n                         make_liquidity_pool_create_op( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), 0, 0 );\n      BOOST_CHECK_THROW( propose( cop ), fc::exception );\n\n      liquidity_pool_delete_operation delop = make_liquidity_pool_delete_op( sam_id, tmp_lp_id );\n      BOOST_CHECK_THROW( propose( delop ), fc::exception );\n\n      liquidity_pool_deposit_operation depop =\n                         make_liquidity_pool_deposit_op( sam_id, tmp_lp_id, core.amount(100), usd.amount(100) );\n      BOOST_CHECK_THROW( propose( delop ), fc::exception );\n\n      liquidity_pool_withdraw_operation wop =\n                         make_liquidity_pool_withdraw_op( sam_id, tmp_lp_id, lpa.amount(100) );\n      BOOST_CHECK_THROW( propose( wop ), fc::exception );\n\n      liquidity_pool_exchange_operation exop =\n                         make_liquidity_pool_exchange_op( sam_id, tmp_lp_id, core.amount(100), usd.amount(100) );\n      BOOST_CHECK_THROW( propose( exop ), fc::exception );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_update_hardfork_time_test )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      const asset_object& lpa = create_user_issued_asset( \"LPATEST\", sam, charge_market_fee );\n\n      const liquidity_pool_object& lpo = create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa.get_id(),\n                                                                0, 0 );\n\n      // Before the hard fork, unable to update a liquidity pool\n      // or update with proposals\n      BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo.get_id(), 1, 0 ), fc::exception );\n\n      liquidity_pool_update_operation updop = make_liquidity_pool_update_op( sam_id, lpo.get_id(), 1, 0 );\n      BOOST_CHECK_THROW( propose( updop ), fc::exception );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_update_test )\n{\n   try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2604_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& lpa1 = create_user_issued_asset( \"LPATEST1\", sam, charge_market_fee );\n      const asset_object& lpa2 = create_user_issued_asset( \"LPATEST2\", ted, charge_market_fee );\n\n      const liquidity_pool_object& lpo1 = create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa1.get_id(),\n                                                                0, 0 );\n\n      BOOST_CHECK( lpo1.asset_a == core.id );\n      BOOST_CHECK( lpo1.asset_b == usd.id );\n      BOOST_CHECK( lpo1.balance_a == 0 );\n      BOOST_CHECK( lpo1.balance_b == 0 );\n      BOOST_CHECK( lpo1.share_asset == lpa1.id );\n      BOOST_CHECK( lpo1.taker_fee_percent == 0 );\n      BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 );\n      BOOST_CHECK( lpo1.virtual_value == 0 );\n\n      deposit_to_liquidity_pool( sam_id, lpo1.get_id(), asset(10), asset( 20, usd.get_id() ) );\n\n      BOOST_CHECK( lpo1.asset_a == core.id );\n      BOOST_CHECK( lpo1.asset_b == usd.id );\n      BOOST_CHECK( lpo1.balance_a == 10 );\n      BOOST_CHECK( lpo1.balance_b == 20 );\n      BOOST_CHECK( lpo1.share_asset == lpa1.id );\n      BOOST_CHECK( lpo1.taker_fee_percent == 0 );\n      BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 );\n      BOOST_CHECK( lpo1.virtual_value == 200 );\n\n      const liquidity_pool_object& lpo2 = create_liquidity_pool( ted_id, core.get_id(), usd.get_id(), lpa2.get_id(),\n                                                                1, 2 );\n\n      BOOST_CHECK( lpo2.asset_a == core.id );\n      BOOST_CHECK( lpo2.asset_b == usd.id );\n      BOOST_CHECK( lpo2.balance_a == 0 );\n      BOOST_CHECK( lpo2.balance_b == 0 );\n      BOOST_CHECK( lpo2.share_asset == lpa2.id );\n      BOOST_CHECK( lpo2.taker_fee_percent == 1 );\n      BOOST_CHECK( lpo2.withdrawal_fee_percent == 2 );\n      BOOST_CHECK( lpo2.virtual_value == 0 );\n\n      // Able to propose\n      {\n         liquidity_pool_update_operation updop = make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 1, 0 );\n         propose( updop );\n      }\n\n      // Unable to update a liquidity pool with invalid data\n      // update nothing\n      BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), {}, {} ), fc::exception );\n      BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), {}, {} ) ), fc::exception );\n      // non-zero withdrawal fee\n      BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), {}, 1 ), fc::exception );\n      BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), {}, 1 ) ), fc::exception );\n      BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), 0, 1 ), fc::exception );\n      BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 0, 1 ) ), fc::exception );\n      // taker fee exceeds 100%\n      BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), 10001, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), 10001, 0 ), fc::exception );\n      BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 10001, {} ) ), fc::exception);\n      BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 10001, 0 ) ), fc::exception );\n      // Owner mismatch (able to propose)\n      BOOST_CHECK_THROW( update_liquidity_pool( ted_id, lpo1.get_id(), 1, {} ), fc::exception );\n      propose( make_liquidity_pool_update_op( ted_id, lpo1.get_id(), 1, {} ) );\n      // Updating taker fee when withdrawal fee is non-zero (able to propose)\n      BOOST_CHECK_THROW( update_liquidity_pool( ted_id, lpo2.get_id(), 1, {} ), fc::exception );\n      propose( make_liquidity_pool_update_op( ted_id, lpo2.get_id(), 1, {} ) );\n\n      // Sam is able to update lpo1\n      update_liquidity_pool( sam_id, lpo1.get_id(), 2, 0 );\n      BOOST_CHECK( lpo1.asset_a == core.id );\n      BOOST_CHECK( lpo1.asset_b == usd.id );\n      BOOST_CHECK( lpo1.balance_a == 10 );\n      BOOST_CHECK( lpo1.balance_b == 20 );\n      BOOST_CHECK( lpo1.share_asset == lpa1.id );\n      BOOST_CHECK( lpo1.taker_fee_percent == 2 );\n      BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 );\n      BOOST_CHECK( lpo1.virtual_value == 200 );\n\n      update_liquidity_pool( sam_id, lpo1.get_id(), 1, {} );\n      BOOST_CHECK( lpo1.asset_a == core.id );\n      BOOST_CHECK( lpo1.asset_b == usd.id );\n      BOOST_CHECK( lpo1.balance_a == 10 );\n      BOOST_CHECK( lpo1.balance_b == 20 );\n      BOOST_CHECK( lpo1.share_asset == lpa1.id );\n      BOOST_CHECK( lpo1.taker_fee_percent == 1 );\n      BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 );\n      BOOST_CHECK( lpo1.virtual_value == 200 );\n\n      // Ted is able to update lpo2 if to update its withdrawal fee to 0\n      update_liquidity_pool( ted_id, lpo2.get_id(), 2, 0 );\n\n      BOOST_CHECK( lpo2.asset_a == core.id );\n      BOOST_CHECK( lpo2.asset_b == usd.id );\n      BOOST_CHECK( lpo2.balance_a == 0 );\n      BOOST_CHECK( lpo2.balance_b == 0 );\n      BOOST_CHECK( lpo2.share_asset == lpa2.id );\n      BOOST_CHECK( lpo2.taker_fee_percent == 2 );\n      BOOST_CHECK( lpo2.withdrawal_fee_percent == 0 );\n      BOOST_CHECK( lpo2.virtual_value == 0 );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_create_delete_proposal_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& lpa = create_user_issued_asset( \"LPATEST\", sam, charge_market_fee );\n      const asset_object& lpa1 = create_user_issued_asset( \"LPATESTA\", sam, charge_market_fee );\n      const asset_object& lpa2 = create_user_issued_asset( \"LPATESTB\", sam, charge_market_fee );\n      const asset_object& lpa3 = create_user_issued_asset( \"LPATESTC\", sam, charge_market_fee );\n      const asset_object& ted_lpa = create_user_issued_asset( \"LPATED\", ted, charge_market_fee );\n\n      const asset_object& mpa = create_bitasset( \"MPATEST\", sam_id );\n      const asset_object& pm = create_prediction_market( \"PMTEST\", sam_id );\n\n      BOOST_CHECK( !lpa1.is_liquidity_pool_share_asset() );\n\n      asset_id_type no_asset_id1( pm.id + 100 );\n      asset_id_type no_asset_id2( pm.id + 200 );\n      BOOST_REQUIRE( !db.find( no_asset_id1 ) );\n      BOOST_REQUIRE( !db.find( no_asset_id2 ) );\n\n      // Able to propose\n      {\n         liquidity_pool_create_operation cop =\n                         make_liquidity_pool_create_op( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), 0, 0 );\n         propose( cop );\n\n         liquidity_pool_id_type tmp_lp_id;\n\n         liquidity_pool_delete_operation delop = make_liquidity_pool_delete_op( sam_id, tmp_lp_id );\n         propose( delop );\n\n         liquidity_pool_deposit_operation depop =\n                         make_liquidity_pool_deposit_op( sam_id, tmp_lp_id, core.amount(100), usd.amount(100) );\n         propose( depop );\n\n         liquidity_pool_withdraw_operation wop =\n                         make_liquidity_pool_withdraw_op( sam_id, tmp_lp_id, lpa.amount(100) );\n         propose( wop );\n\n         liquidity_pool_exchange_operation exop =\n                         make_liquidity_pool_exchange_op( sam_id, tmp_lp_id, core.amount(100), usd.amount(100) );\n         propose( exop );\n      }\n\n      // Able to create liquidity pools with valid data\n      const liquidity_pool_object& lpo1 = create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa1.get_id(),\n                                                                 0, 0 );\n      BOOST_CHECK( lpo1.asset_a == core.id );\n      BOOST_CHECK( lpo1.asset_b == usd.id );\n      BOOST_CHECK( lpo1.balance_a == 0 );\n      BOOST_CHECK( lpo1.balance_b == 0 );\n      BOOST_CHECK( lpo1.share_asset == lpa1.id );\n      BOOST_CHECK( lpo1.taker_fee_percent == 0 );\n      BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 );\n      BOOST_CHECK( lpo1.virtual_value == 0 );\n\n      liquidity_pool_id_type lp_id1 = lpo1.get_id();\n      BOOST_CHECK( lpa1.is_liquidity_pool_share_asset() );\n      BOOST_CHECK( *lpa1.for_liquidity_pool == lp_id1 );\n\n      const liquidity_pool_object& lpo2 = create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa2.get_id(),\n                                                                 200, 300 );\n      BOOST_CHECK( lpo2.asset_a == core.id );\n      BOOST_CHECK( lpo2.asset_b == usd.id );\n      BOOST_CHECK( lpo2.balance_a == 0 );\n      BOOST_CHECK( lpo2.balance_b == 0 );\n      BOOST_CHECK( lpo2.share_asset == lpa2.id );\n      BOOST_CHECK( lpo2.taker_fee_percent == 200 );\n      BOOST_CHECK( lpo2.withdrawal_fee_percent == 300 );\n      BOOST_CHECK( lpo2.virtual_value == 0 );\n\n      liquidity_pool_id_type lp_id2 = lpo2.get_id();\n      BOOST_CHECK( lpa2.is_liquidity_pool_share_asset() );\n      BOOST_CHECK( *lpa2.for_liquidity_pool == lp_id2 );\n\n      const liquidity_pool_object& lpo3 = create_liquidity_pool( sam_id, usd.get_id(), mpa.get_id(), lpa3.get_id(),\n                                                                 50, 50 );\n\n      BOOST_CHECK( lpo3.asset_a == usd.id );\n      BOOST_CHECK( lpo3.asset_b == mpa.id );\n      BOOST_CHECK( lpo3.balance_a == 0 );\n      BOOST_CHECK( lpo3.balance_b == 0 );\n      BOOST_CHECK( lpo3.share_asset == lpa3.id );\n      BOOST_CHECK( lpo3.taker_fee_percent == 50 );\n      BOOST_CHECK( lpo3.withdrawal_fee_percent == 50 );\n      BOOST_CHECK( lpo3.virtual_value == 0 );\n\n      liquidity_pool_id_type lp_id3 = lpo3.get_id();\n      BOOST_CHECK( lpa3.is_liquidity_pool_share_asset() );\n      BOOST_CHECK( *lpa3.for_liquidity_pool == lp_id3 );\n\n      // Unable to create a liquidity pool with invalid data\n      // the same assets in pool\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), core.get_id(), lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, usd.get_id(), usd.get_id(), lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      // ID of the first asset is greater\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, usd.get_id(), core.get_id(), lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      // the share asset is one of the assets in pool\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, usd.get_id(), lpa.get_id(), lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, lpa.get_id(), pm.get_id(), lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      // percentage too big\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), 10001, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), 0, 10001 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), 10001, 10001 ),\n                         fc::exception );\n      // asset does not exist\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), no_asset_id1, 0, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), no_asset_id1, lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, no_asset_id1, no_asset_id2, lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      // the account does not own the share asset\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), ted_lpa.get_id(), 0, 0 ),\n                         fc::exception );\n      // the share asset is a MPA or a PM\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), mpa.get_id(), 0, 0 ),\n                         fc::exception );\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), pm.get_id(), 0, 0 ),\n                         fc::exception );\n      // the share asset is already bound to a liquidity pool\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa1.get_id(), 0, 0 ),\n                         fc::exception );\n      // current supply of the share asset is not zero\n      BOOST_CHECK_THROW( create_liquidity_pool( sam_id, core.get_id(), lpa.get_id(), usd.get_id(), 0, 0 ),\n                         fc::exception );\n\n      // Unable to issue a liquidity pool share asset\n      BOOST_CHECK_THROW( issue_uia( sam, lpa1.amount(1) ), fc::exception );\n\n      // Sam is able to delete an empty pool owned by him\n      generic_operation_result result = delete_liquidity_pool( sam_id, lpo1.get_id() );\n      BOOST_CHECK( !db.find( lp_id1 ) );\n      BOOST_CHECK( !lpa1.is_liquidity_pool_share_asset() );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n      BOOST_REQUIRE_EQUAL( result.updated_objects.size(), 1u );\n      BOOST_CHECK( *result.updated_objects.begin() == lpa1.id );\n      BOOST_REQUIRE_EQUAL( result.removed_objects.size(), 1u );\n      BOOST_CHECK( *result.removed_objects.begin() == lp_id1 );\n\n      // Other pools are still there\n      BOOST_CHECK( db.find( lp_id2 ) );\n      BOOST_CHECK( db.find( lp_id3 ) );\n\n      // Ted is not able to delete a pool that does not exist\n      BOOST_CHECK_THROW( delete_liquidity_pool( ted_id, lp_id1 ), fc::exception );\n      // Ted is not able to delete a pool owned by sam\n      BOOST_CHECK_THROW( delete_liquidity_pool( ted_id, lp_id2 ), fc::exception );\n\n      // the asset is now a simple asset, able to issue\n      issue_uia( sam, lpa1.amount(1) );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_deposit_withdrawal_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      additional_asset_options_t eur_options, usd_options;\n      eur_options.value.taker_fee_percent = 50; // 0.5% taker fee\n      usd_options.value.taker_fee_percent = 80; // 0.8% taker fee\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, charge_market_fee,\n                                                 price(asset(1, asset_id_type(1)), asset(1)),\n                                                 4, 20, eur_options ); // 0.2% maker fee\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, charge_market_fee,\n                                                 price(asset(1, asset_id_type(1)), asset(1)),\n                                                 4, 30, usd_options ); // 0.3% maker fee\n      const asset_object& lpa = create_user_issued_asset( \"LPATEST\", sam, charge_market_fee );\n\n      asset_id_type core_id = asset_id_type();\n      asset_id_type eur_id = eur.get_id();\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type lpa_id = lpa.get_id();\n\n      int64_t init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      int64_t expected_balance_sam_eur = init_amount;\n      int64_t expected_balance_sam_usd = init_amount;\n      int64_t expected_balance_sam_lpa = 0;\n      int64_t expected_balance_ted_eur = init_amount;\n      int64_t expected_balance_ted_usd = init_amount;\n      int64_t expected_balance_ted_lpa = 0;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, lpa_id ).amount.value, expected_balance_sam_lpa );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, lpa_id ).amount.value, expected_balance_ted_lpa );\n      };\n\n      check_balances();\n\n      int64_t expected_pool_balance_a = 0;\n      int64_t expected_pool_balance_b = 0;\n      int64_t expected_lp_supply = 0;\n\n      // create a liquidity pool\n      const liquidity_pool_object& lpo = create_liquidity_pool( sam_id, eur.get_id(), usd.get_id(), lpa.get_id(),\n                                                                200, 300 );\n      liquidity_pool_id_type lp_id = lpo.get_id();\n\n      BOOST_CHECK( lpo.asset_a == eur_id );\n      BOOST_CHECK( lpo.asset_b == usd_id );\n      BOOST_CHECK( lpo.share_asset == lpa_id );\n      BOOST_CHECK( lpo.taker_fee_percent == 200 );\n      BOOST_CHECK( lpo.withdrawal_fee_percent == 300 );\n\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      BOOST_CHECK( lpa.is_liquidity_pool_share_asset() );\n      BOOST_CHECK( *lpa.for_liquidity_pool == lp_id );\n\n      check_balances();\n\n      // Unable to deposit to a liquidity pool with invalid data\n      // non-positive amounts\n      for( int64_t i = -1; i <= 1; ++i )\n      {\n         for( int64_t j = -1; j <= 1; ++j )\n         {\n            if( i > 0 && j > 0 )\n               continue;\n            BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( i, eur_id ), asset( j, usd_id ) ),\n                               fc::exception );\n         }\n      }\n      // Insufficient balance\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id,\n                            asset( init_amount + 1, eur_id ), asset( 1, usd_id ) ), fc::exception );\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id,\n                            asset( 1, eur_id ), asset( init_amount + 1, usd_id ) ), fc::exception );\n      // asset ID mismatch\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 1, core_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 1, eur_id ), asset( 1, lpa_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 1, usd_id ), asset( 1, eur_id ) ),\n                         fc::exception );\n      // non-exist pool\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id+1, asset( 1, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      // pool empty but not owner depositting\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( ted_id, lp_id, asset( 1, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n\n      // The owner is able to do the initial deposit\n      generic_exchange_operation_result result;\n      result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 1000, eur_id ), asset( 1200, usd_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( 1000, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( 1200, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( 1200, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a = 1000;\n      expected_pool_balance_b = 1200;\n      expected_lp_supply = 1200;\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= 1000;\n      expected_balance_sam_usd -= 1200;\n      expected_balance_sam_lpa += 1200;\n      check_balances();\n\n      // unable to delete a pool that is not empty\n      BOOST_CHECK_THROW( delete_liquidity_pool( sam_id, lp_id ), fc::exception );\n\n      // Sam tries to deposit more\n      result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 200, eur_id ), asset( 120, usd_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( 100, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( 120, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( 120, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a += 100;\n      expected_pool_balance_b += 120;\n      expected_lp_supply += 120;\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= 100;\n      expected_balance_sam_usd -= 120;\n      expected_balance_sam_lpa += 120;\n      check_balances();\n\n      // Unable to reserve all the supply of the LP token\n      BOOST_CHECK_THROW( reserve_asset( sam_id, asset( expected_balance_sam_lpa, lpa_id ) ), fc::exception );\n\n      // Ted deposits\n      result = deposit_to_liquidity_pool( ted_id, lp_id, asset( 12347, eur_id ), asset( 56890, usd_id ) );\n\n      int64_t new_lp_supply = 14816; // 1320 * 12347 / 1100, round down\n      int64_t new_a = 12347;\n      int64_t new_b = 14816;\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( new_a, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( new_b, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( new_lp_supply, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a += new_a; // 1100 + 12347 = 13447\n      expected_pool_balance_b += new_b; // 1320 + 14816 = 16136\n      expected_lp_supply += new_lp_supply; // 16136\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_ted_eur -= new_a;\n      expected_balance_ted_usd -= new_b;\n      expected_balance_ted_lpa += new_lp_supply;\n      check_balances();\n\n      // Unable to withdraw with invalid data\n      // non-positive amount\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id, asset( -1, lpa_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id, asset( 0, lpa_id ) ),\n                         fc::exception );\n      // insufficient balance\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id, asset( expected_balance_ted_lpa + 1, lpa_id ) ),\n                         fc::exception );\n      // asset ID mismatch\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id, asset( 10, core_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id, asset( 10, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id, asset( 10, eur_id ) ),\n                         fc::exception );\n      // non-exist pool\n      BOOST_CHECK_THROW( withdraw_from_liquidity_pool( ted_id, lp_id+1, asset( 10, usd_id ) ),\n                         fc::exception );\n\n      // Ted reserve some LP token\n      reserve_asset( ted_id, asset( 14810, lpa_id ) );\n\n      expected_lp_supply -= 14810; // 16136 - 14810 = 1326\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_ted_lpa -= 14810; // 6\n      check_balances();\n\n      // Ted fails to deposit with too small amounts\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( ted_id, lp_id, asset( 8, eur_id ), asset( 8, usd_id ) ),\n                         fc::exception );\n\n      // Ted deposits again\n      result = deposit_to_liquidity_pool( ted_id, lp_id, asset( 12347, eur_id ), asset( 56890, usd_id ) );\n\n      new_lp_supply = 1217; // 1326 * 12347 / 13447, round down\n      new_a = 12342; // 1217 * 13447 / 1326, round up\n      new_b = 14810; // 1217 * 16136 / 1326, round up\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( new_a, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( new_b, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( new_lp_supply, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a += new_a; // 13447 + 12342 = 25789\n      expected_pool_balance_b += new_b; // 16136 + 14810 = 30946\n      expected_lp_supply += new_lp_supply; // 1326 + 1217 = 2543\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_ted_eur -= new_a;\n      expected_balance_ted_usd -= new_b;\n      expected_balance_ted_lpa += new_lp_supply;\n      check_balances();\n\n      // Ted withdraws some LP token\n      result = withdraw_from_liquidity_pool( ted_id, lp_id, asset( 7, lpa_id ) );\n\n      new_lp_supply = -7;\n      new_a = -68; // - (7 * 25789 / 2543, round down, = 70, deduct withdrawal fee 70 * 3%, round down, = 2)\n      new_b = -83; // - (7 * 30946 / 2543, round down, = 85, deduct withdrawal fee 85 * 3%, round down, = 2)\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 1u );\n      BOOST_CHECK( result.paid.front() == asset( -new_lp_supply, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 2u );\n      BOOST_CHECK( result.received.front() == asset( -new_a, eur_id ) );\n      BOOST_CHECK( result.received.back() == asset( -new_b, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 2u );\n      BOOST_CHECK( result.fees.front() == asset( 2, eur_id ) );\n      BOOST_CHECK( result.fees.back() == asset( 2, usd_id ) );\n\n      expected_pool_balance_a += new_a; // 25789 - 68 = 25721\n      expected_pool_balance_b += new_b; // 30946 - 83 = 30863\n      expected_lp_supply += new_lp_supply; // 2543 - 7 = 2536\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_ted_eur -= new_a;\n      expected_balance_ted_usd -= new_b;\n      expected_balance_ted_lpa += new_lp_supply;\n      check_balances();\n\n      // Ted reserve the rest LP token\n      reserve_asset( ted_id, asset( expected_balance_ted_lpa, lpa_id ) );\n\n      expected_lp_supply -= expected_balance_ted_lpa; // 1320\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_ted_lpa = 0;\n      check_balances();\n\n      // Sam withdraws all\n      result = withdraw_from_liquidity_pool( sam_id, lp_id, asset( 1320, lpa_id ) );\n\n      new_lp_supply = -1320;\n      new_a = -25721;\n      new_b = -30863;\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 1u );\n      BOOST_CHECK( result.paid.front() == asset( -new_lp_supply, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 2u );\n      BOOST_CHECK( result.received.front() == asset( -new_a, eur_id ) );\n      BOOST_CHECK( result.received.back() == asset( -new_b, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 2u );\n      BOOST_CHECK( result.fees.front() == asset( 0, eur_id ) );\n      BOOST_CHECK( result.fees.back() == asset( 0, usd_id ) );\n\n      expected_pool_balance_a = 0;\n      expected_pool_balance_b = 0;\n      expected_lp_supply = 0;\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= new_a;\n      expected_balance_sam_usd -= new_b;\n      expected_balance_sam_lpa += new_lp_supply; // 0\n      check_balances();\n\n      // prepare for asset update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = lpa_id;\n      auop.new_options = lpa_id(db).options;\n\n      // set max supply to a smaller number\n      auop.new_options.max_supply = 2000;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( lpa_id(db).options.max_supply.value, 2000 );\n\n      // Unable to do initial deposit if to create more than the max supply\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 2001, eur_id ), asset( 100, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 100, eur_id ), asset( 2001, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 2001, eur_id ), asset( 2001, usd_id ) ),\n                         fc::exception );\n\n      // Able to deposit less\n      result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 1000, eur_id ), asset( 1200, usd_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( 1000, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( 1200, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( 1200, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a = 1000;\n      expected_pool_balance_b = 1200;\n      expected_lp_supply = 1200;\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= 1000;\n      expected_balance_sam_usd -= 1200;\n      expected_balance_sam_lpa += 1200;\n      check_balances();\n\n      // Try to deposit more to create more than max supply, will be capped at max supply\n      result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 1000, eur_id ), asset( 1200, usd_id ) );\n\n      new_lp_supply = 800; // 2000 - 1200\n      new_a = 667; // 800 * 1000 / 1200, round up\n      new_b = 800;\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( new_a, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( new_b, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( new_lp_supply, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a += new_a;\n      expected_pool_balance_b += new_b;\n      expected_lp_supply = 2000;\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= new_a;\n      expected_balance_sam_usd -= new_b;\n      expected_balance_sam_lpa += new_lp_supply;\n      check_balances();\n\n      // Unable to deposit more\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 2, eur_id ), asset( 2, usd_id ) ),\n                         fc::exception );\n\n      // set max supply to a bigger number\n      auop.new_options.max_supply = 3000;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK_EQUAL( lpa_id(db).options.max_supply.value, 3000 );\n\n      // Able to deposit more\n      deposit_to_liquidity_pool( sam_id, lp_id, asset( 2, eur_id ), asset( 2, usd_id ) );\n\n      // update flag to disable creation of new supply\n      auop.new_options.flags |= disable_new_supply;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_CHECK( !lpa_id(db).can_create_new_supply() );\n\n      // Unable to deposit more\n      BOOST_CHECK_THROW( deposit_to_liquidity_pool( sam_id, lp_id, asset( 2, eur_id ), asset( 2, usd_id ) ),\n                         fc::exception );\n\n      generate_block();\n\n      graphene::market_history::liquidity_pool_ticker_id_type ticker_id( lp_id.instance );\n      const auto& ticker = db.get( ticker_id );\n      BOOST_CHECK_EQUAL( ticker._24h_deposit_count, 7u );\n      BOOST_CHECK_EQUAL( ticker.total_deposit_count, 7u );\n      BOOST_CHECK_EQUAL( ticker._24h_withdrawal_count, 2u );\n      BOOST_CHECK_EQUAL( ticker.total_withdrawal_count, 2u );\n\n      generate_blocks( db.head_block_time() + fc::days(2) );\n\n      BOOST_CHECK_EQUAL( ticker._24h_deposit_count, 0u );\n      BOOST_CHECK_EQUAL( ticker.total_deposit_count, 7u );\n      BOOST_CHECK_EQUAL( ticker._24h_withdrawal_count, 0u );\n      BOOST_CHECK_EQUAL( ticker.total_withdrawal_count, 2u );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      additional_asset_options_t eur_options, usd_options;\n      eur_options.value.taker_fee_percent = 50; // 0.5% taker fee\n      usd_options.value.taker_fee_percent = 80; // 0.8% taker fee\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, charge_market_fee,\n                                                 price(asset(1, asset_id_type(1)), asset(1)),\n                                                 4, 20, eur_options ); // 0.2% maker fee\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, charge_market_fee,\n                                                 price(asset(1, asset_id_type(1)), asset(1)),\n                                                 4, 30, usd_options ); // 0.3% maker fee\n      const asset_object& lpa = create_user_issued_asset( \"LPATEST\", sam, charge_market_fee );\n\n      asset_id_type core_id = asset_id_type();\n      asset_id_type eur_id = eur.get_id();\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type lpa_id = lpa.get_id();\n\n      int64_t init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      int64_t expected_balance_sam_eur = init_amount;\n      int64_t expected_balance_sam_usd = init_amount;\n      int64_t expected_balance_sam_lpa = 0;\n      int64_t expected_balance_ted_eur = init_amount;\n      int64_t expected_balance_ted_usd = init_amount;\n      int64_t expected_balance_ted_lpa = 0;\n\n      int64_t expected_accumulated_fees_eur = 0;\n      int64_t expected_accumulated_fees_usd = 0;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, lpa_id ).amount.value, expected_balance_sam_lpa );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, lpa_id ).amount.value, expected_balance_ted_lpa );\n      };\n\n      check_balances();\n\n      int64_t expected_pool_balance_a = 0;\n      int64_t expected_pool_balance_b = 0;\n      int64_t expected_lp_supply = 0;\n\n      // create a liquidity pool\n      const liquidity_pool_object& lpo = create_liquidity_pool( sam_id, eur.get_id(), usd.get_id(), lpa.get_id(),\n                                                                200, 300 );\n      liquidity_pool_id_type lp_id = lpo.get_id();\n\n      BOOST_CHECK( lpo.asset_a == eur_id );\n      BOOST_CHECK( lpo.asset_b == usd_id );\n      BOOST_CHECK( lpo.share_asset == lpa_id );\n      BOOST_CHECK( lpo.taker_fee_percent == 200 );\n      BOOST_CHECK( lpo.withdrawal_fee_percent == 300 );\n\n      BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      BOOST_CHECK( lpa.is_liquidity_pool_share_asset() );\n      BOOST_CHECK( *lpa.for_liquidity_pool == lp_id );\n\n      check_balances();\n\n      // Unable to exchange if the pool is not initialized\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 100, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n\n      // The owner do the initial deposit\n      generic_exchange_operation_result result;\n      result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 100, eur_id ), asset( 120, usd_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( 100, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( 120, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( 120, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a = 100;\n      expected_pool_balance_b = 120;\n      expected_lp_supply = 120;\n      BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= 100;\n      expected_balance_sam_usd -= 120;\n      expected_balance_sam_lpa += 120;\n      check_balances();\n\n      // Generates a block\n      generate_block();\n      set_expiration( db, trx );\n\n      // Deposit again with 900 EUR and 3000 USD, the pool only takes 900 EUR and 1080 USD\n      result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 900, eur_id ), asset( 3000, usd_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 2u );\n      BOOST_CHECK( result.paid.front() == asset( 900, eur_id ) );\n      BOOST_CHECK( result.paid.back() == asset( 1080, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( 1080, lpa_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 0u );\n\n      expected_pool_balance_a += 900;\n      expected_pool_balance_b += 1080;\n      expected_lp_supply += 1080;\n      BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_balance_sam_eur -= 900;\n      expected_balance_sam_usd -= 1080;\n      expected_balance_sam_lpa += 1080;\n      check_balances();\n\n\n      // Unable to exchange if data is invalid\n      // non-positive amounts\n      for( int64_t i = -1; i <= 1; ++i )\n      {\n         for( int64_t j = -1; j <= 1; ++j )\n         {\n            if( i > 0 && j > 0 )\n               continue;\n            BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( i, eur_id ), asset( j, usd_id ) ),\n                               fc::exception );\n         }\n      }\n      // Insufficient balance\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id,\n                            asset( init_amount + 1, eur_id ), asset( 1, usd_id ) ), fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id,\n                            asset( init_amount + 1, usd_id ), asset( 1, eur_id ) ), fc::exception );\n      // asset ID mismatch\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 100, core_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 100, eur_id ), asset( 1, lpa_id ) ),\n                         fc::exception );\n      // non-exist pool\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id+1, asset( 100, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n\n\n      // trying to buy an amount that is equal to or more than the balance in the pool\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 9000, eur_id ), asset( 1200, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 9000, usd_id ), asset( 1000, eur_id ) ),\n                         fc::exception );\n\n      // Calculates if Ted sells 1000 EUR to the pool\n      int64_t maker_fee = 2; // 1000 * 0.2%, eur\n      int64_t delta_a = 998; // 1000 - 2\n      // tmp_delta = 1200 - round_up( 1000 * 1200 / (1000+998) ) = 1200 - 601 = 599\n      int64_t delta_b = -588; // - ( 599 - round_down(599 * 2%) ) = - ( 599 - 11 ) = -588\n      int64_t pool_taker_fee = 11;\n      int64_t taker_fee = 4; // 588 * 0.8%, usd\n      int64_t ted_receives = 584; // 588 - 4\n\n      // Ted fails to exchange if asks for more\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 585, usd_id ) ),\n                         fc::exception );\n\n      // Setup market blacklists and whitelists\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.whitelist_markets.insert( core_id );\n         auop.new_options.blacklist_markets.insert( eur_id );\n         auop.new_options.blacklist_markets.insert( usd_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.whitelist_markets.insert( core_id );\n         auop.new_options.blacklist_markets.insert( eur_id );\n         auop.new_options.blacklist_markets.insert( usd_id );\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n\n      // Ted exchanges with the pool\n      // BTW reproduces bitshares-core issue #2350: white/blacklists not in effect\n      result = exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 584, usd_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 1u );\n      BOOST_CHECK( result.paid.front() == asset( 1000, eur_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( ted_receives, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 3u );\n      BOOST_CHECK( result.fees.front() == asset( maker_fee, eur_id ) );\n      BOOST_CHECK( result.fees.at(1) == asset( taker_fee, usd_id ) );\n      BOOST_CHECK( result.fees.back() == asset( pool_taker_fee, usd_id ) );\n\n      expected_pool_balance_a += delta_a; // 1000 + 998 = 1998\n      expected_pool_balance_b += delta_b; // 1200 - 588 = 612\n      BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_accumulated_fees_eur += maker_fee;\n      expected_accumulated_fees_usd += taker_fee;\n      BOOST_CHECK_EQUAL( eur_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur );\n      BOOST_CHECK_EQUAL( usd_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_usd );\n\n      expected_balance_ted_eur -= 1000;\n      expected_balance_ted_usd += ted_receives;\n      check_balances();\n\n      // Calculates if Ted sells 1000 USD to the pool\n      maker_fee = 3; // 1000 * 0.3%, usd\n      delta_b = 997; // 1000 - 3\n      // tmp_delta = 1998 - round_up( 1998 * 612 / (612+997) ) = 1998 - 760 = 1238\n      delta_a = -1214; // - ( 1238 - round_down(1238 * 2%) ) = - ( 1238 - 24 ) = -1214\n      pool_taker_fee = 24;\n      taker_fee = 6; // 1214 * 0.5%, eur\n      ted_receives = 1208; // 1214 - 6\n\n      // Ted fails to exchange if asks for more\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1209, eur_id ) ),\n                         fc::exception );\n\n      // Ted exchanges with the pool\n      // BTW reproduces bitshares-core issue #2350: white/blacklists not in effect\n      result = exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 600, eur_id ) );\n\n      BOOST_REQUIRE_EQUAL( result.paid.size(), 1u );\n      BOOST_CHECK( result.paid.front() == asset( 1000, usd_id ) );\n      BOOST_REQUIRE_EQUAL( result.received.size(), 1u );\n      BOOST_CHECK( result.received.front() == asset( ted_receives, eur_id ) );\n      BOOST_REQUIRE_EQUAL( result.fees.size(), 3u );\n      BOOST_CHECK( result.fees.front() == asset( maker_fee, usd_id ) );\n      BOOST_CHECK( result.fees.at(1) == asset( taker_fee, eur_id ) );\n      BOOST_CHECK( result.fees.back() == asset( pool_taker_fee, eur_id ) );\n\n      expected_pool_balance_a += delta_a; // 1998 - 1214 = 784\n      expected_pool_balance_b += delta_b; // 612 + 997 = 1609\n      BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a);\n      BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b);\n      BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b );\n      BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply );\n\n      expected_accumulated_fees_eur += taker_fee;\n      expected_accumulated_fees_usd += maker_fee;\n      BOOST_CHECK_EQUAL( eur_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur );\n      BOOST_CHECK_EQUAL( usd_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_usd );\n\n      expected_balance_ted_eur += ted_receives;\n      expected_balance_ted_usd -= 1000;\n      check_balances();\n\n      // Withdraw\n      result = withdraw_from_liquidity_pool( sam_id, lp_id, asset( 1000, lpa_id) );\n\n      // Generates a block\n      generate_block();\n      BOOST_CHECK_EQUAL( eur_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur );\n\n      graphene::market_history::liquidity_pool_ticker_id_type ticker_id( lp_id.instance );\n      const auto& ticker = db.get( ticker_id );\n      BOOST_CHECK_EQUAL( ticker._24h_exchange_a2b_count, 1u );\n      BOOST_CHECK_EQUAL( ticker.total_exchange_a2b_count, 1u );\n      BOOST_CHECK_EQUAL( ticker._24h_exchange_b2a_count, 1u );\n      BOOST_CHECK_EQUAL( ticker.total_exchange_b2a_count, 1u );\n      BOOST_CHECK_EQUAL( ticker._24h_deposit_count, 2u );\n      BOOST_CHECK( ticker._24h_deposit_amount_a == 1000u );\n      BOOST_CHECK( ticker._24h_deposit_amount_b == 1200u );\n      BOOST_CHECK( ticker._24h_deposit_share_amount == 1200u );\n      BOOST_CHECK_EQUAL( ticker.total_deposit_count, 2u );\n      BOOST_CHECK( ticker.total_deposit_amount_a == 1000u );\n      BOOST_CHECK( ticker.total_deposit_amount_b == 1200u );\n      BOOST_CHECK( ticker.total_deposit_share_amount == 1200u );\n      BOOST_CHECK_EQUAL( ticker._24h_withdrawal_count, 1u );\n      BOOST_CHECK_EQUAL( ticker.total_withdrawal_count, 1u );\n\n      // Check database API\n      graphene::app::database_api db_api( db, &( app.get_options() ) );\n\n      // get pool without statistics\n      auto pools = db_api.get_liquidity_pools( { lp_id } );\n      BOOST_REQUIRE_EQUAL( pools.size(), 1u );\n      BOOST_REQUIRE( pools.front().valid() );\n      BOOST_CHECK( !pools.front()->statistics.valid() );\n\n      // get pool with statistics\n      pools = db_api.get_liquidity_pools( { lp_id }, {}, true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 1u );\n      BOOST_REQUIRE( pools.front().valid() );\n      BOOST_REQUIRE( pools.front()->statistics.valid() );\n      BOOST_CHECK( pools.front()->statistics->id == ticker_id );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_exchange_a2b_count, 1u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->total_exchange_a2b_count, 1u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_exchange_b2a_count, 1u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->total_exchange_b2a_count, 1u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_deposit_count, 2u );\n      BOOST_CHECK( pools.front()->statistics->_24h_deposit_amount_a == 1000u );\n      BOOST_CHECK( pools.front()->statistics->_24h_deposit_amount_b == 1200u );\n      BOOST_CHECK( pools.front()->statistics->_24h_deposit_share_amount == 1200u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->total_deposit_count, 2u );\n      BOOST_CHECK( pools.front()->statistics->total_deposit_amount_a == 1000u );\n      BOOST_CHECK( pools.front()->statistics->total_deposit_amount_b == 1200u );\n      BOOST_CHECK( pools.front()->statistics->total_deposit_share_amount == 1200u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_withdrawal_count, 1u );\n      BOOST_CHECK_EQUAL( pools.front()->statistics->total_withdrawal_count, 1u );\n\n      generate_blocks( db.head_block_time() + fc::days(2) );\n\n      BOOST_CHECK_EQUAL( ticker._24h_exchange_a2b_count, 0u );\n      BOOST_CHECK_EQUAL( ticker.total_exchange_a2b_count, 1u );\n      BOOST_CHECK_EQUAL( ticker._24h_exchange_b2a_count, 0u );\n      BOOST_CHECK_EQUAL( ticker.total_exchange_b2a_count, 1u );\n      BOOST_CHECK_EQUAL( ticker._24h_deposit_count, 0u );\n      BOOST_CHECK( ticker._24h_deposit_amount_a == 0u );\n      BOOST_CHECK( ticker._24h_deposit_amount_b == 0u );\n      BOOST_CHECK( ticker._24h_deposit_share_amount == 0u );\n      BOOST_CHECK_EQUAL( ticker.total_deposit_count, 2u );\n      BOOST_CHECK( ticker.total_deposit_amount_a == 1000u );\n      BOOST_CHECK( ticker.total_deposit_amount_b == 1200u );\n      BOOST_CHECK( ticker.total_deposit_share_amount == 1200u );\n      BOOST_CHECK_EQUAL( ticker._24h_withdrawal_count, 0u );\n      BOOST_CHECK_EQUAL( ticker.total_withdrawal_count, 1u );\n\n      // Check history API\n      graphene::app::history_api hist_api(app);\n      auto head_time = db.head_block_time();\n\n      // all histories : 1:create, 2:deposit, 3:deposit, 4:exchange, 5:exchange, 6:withdrawal\n      // The 1st block: {1, 2}, the 2nd block: {3, 4, 5, 6}\n      auto histories = hist_api.get_liquidity_pool_history( lp_id );\n      BOOST_CHECK_EQUAL( histories.size(), 6u );\n\n      // limit = 3\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, {}, 3 );\n      BOOST_CHECK_EQUAL( histories.size(), 3u );\n\n      // only deposits\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, {}, {}, 61 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 2u );\n      auto second_time = histories[0].time;\n      auto first_time = histories[1].time;\n      auto late_time = second_time + fc::seconds(1);\n      auto early_time = first_time - fc::seconds(1);\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, {}, {}, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, {}, 1, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, first_time, {}, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, first_time, {}, {}, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, first_time, {}, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, early_time, {}, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, early_time, 1, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, early_time, 5, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, early_time, 1, 61 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n\n      // time is fine\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, first_time );\n      BOOST_CHECK_EQUAL( histories.size(), 4u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, first_time );\n      BOOST_CHECK_EQUAL( histories.size(), 4u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, first_time, early_time );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, early_time );\n      BOOST_CHECK_EQUAL( histories.size(), 6u );\n\n      // time too early\n      histories = hist_api.get_liquidity_pool_history( lp_id, head_time - fc::days(3) );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n\n      // time too late\n      histories = hist_api.get_liquidity_pool_history( lp_id, head_time, head_time - fc::days(1) );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n\n      // stop and start are the same, or stop is later than start\n      histories = hist_api.get_liquidity_pool_history( lp_id, second_time, second_time );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, first_time, second_time, 10 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n      histories = hist_api.get_liquidity_pool_history( lp_id, first_time, late_time, 10 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n\n      // time is fine, only exchanges\n      histories = hist_api.get_liquidity_pool_history( lp_id, {}, head_time - fc::days(3), {}, 63 );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n\n      // all histories : 1:create, 2:deposit, 3:deposit, 4:exchange, 5:exchange, 6:withdrawal\n      // The 1st block: {1, 2}, the 2nd block: {3, 4, 5, 6}\n\n      // start = 2, limit = 3, so result sequence == {2,1}\n      // note: range is (stop, start]\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, {}, 3 );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n\n      // start = 2, limit = 1, so result sequence == {2}\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, {}, 1 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n\n      // start = 2, limit = 50, stop = the 2nd block time or later, result sequence == {}\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, second_time, 50 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, late_time, 50 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n\n      // start = 1, limit = 10, stop = the 1st block time, result sequence == {}\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 1, first_time, 10 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n\n      // start = 4, limit is default, stop = the 1st block time, result sequence == {4,3}\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, first_time );\n      BOOST_CHECK_EQUAL( histories.size(), 2u );\n\n      // start = 4, limit is default, but exchange only, so result sequence == {4}\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, head_time - fc::days(3), {}, 63 );\n      BOOST_CHECK_EQUAL( histories.size(), 1u );\n\n      // start = 4, limit is default, stop = the 2nd block time or later, exchange only, result sequence == {}\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, second_time, {}, 63 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n      histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, late_time, {}, 63 );\n      BOOST_CHECK_EQUAL( histories.size(), 0u );\n\n      // Proceeds to the hard fork time that added white/blacklist checks for bitshares-core issue #2350\n      generate_blocks( HARDFORK_CORE_2350_TIME );\n\n      // Ted now fails to exchange due to the white/blacklists\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ),\n                         fc::exception );\n\n      // Remove market blacklists and whitelists\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.whitelist_markets.clear();\n         auop.new_options.blacklist_markets.clear();\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.whitelist_markets.clear();\n         auop.new_options.blacklist_markets.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n      // Setup a whitelist without EUR for USD\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.whitelist_markets.insert( core_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Now unable to exchange\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ),\n                         fc::exception );\n\n      // Add the USD:EUR market to the whitelist of USD\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.whitelist_markets.insert( eur_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n      // Setup a blacklist without EUR for USD\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.blacklist_markets.insert( usd_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n      // Add EUR to blacklist of USD\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.whitelist_markets.clear();\n         auop.new_options.blacklist_markets.insert( eur_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Now unable to exchange\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ),\n                         fc::exception );\n\n      // Remove the USD:EUR market from the blacklist of USD\n      {\n         asset_update_operation auop;\n\n         auop.issuer = usd_id(db).issuer;\n         auop.asset_to_update = usd_id;\n         auop.new_options = usd_id(db).options;\n         auop.new_options.blacklist_markets.erase( eur_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n      // Setup a whitelist without USD for EUR\n      {\n         asset_update_operation auop;\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.whitelist_markets.insert( core_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Now unable to exchange\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ),\n                         fc::exception );\n\n      // Add the USD:EUR market to the whitelist of EUR\n      {\n         asset_update_operation auop;\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.whitelist_markets.insert( usd_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n      // Setup a blacklist without USD for EUR\n      {\n         asset_update_operation auop;\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.blacklist_markets.insert( eur_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n      // Add EUR:USD to the blacklist of EUR\n      {\n         asset_update_operation auop;\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.whitelist_markets.clear();\n         auop.new_options.blacklist_markets.insert( usd_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Now unable to exchange\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ),\n                         fc::exception );\n      BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ),\n                         fc::exception );\n\n      // Remove the USD:EUR market from the blacklist of EUR\n      {\n         asset_update_operation auop;\n\n         auop.issuer = eur_id(db).issuer;\n         auop.asset_to_update = eur_id;\n         auop.new_options = eur_id(db).options;\n         auop.new_options.blacklist_markets.erase( usd_id );\n         trx.operations.clear();\n         trx.operations.push_back( auop );\n         PUSH_TX(db, trx, ~0);\n      }\n      // Able to exchange\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) );\n      exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_CASE( liquidity_pool_apis_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      const asset_object sam_eur = create_user_issued_asset( \"SAMEUR\", sam, charge_market_fee );\n      const asset_object sam_usd = create_user_issued_asset( \"SAMUSD\", sam, charge_market_fee );\n      const asset_object sam_lp1 = create_user_issued_asset( \"SAMLP1\", sam, charge_market_fee );\n      const asset_object sam_lp2 = create_user_issued_asset( \"SAMLP2\", sam, charge_market_fee );\n\n      const asset_object ted_eur = create_user_issued_asset( \"TEDEUR\", ted, charge_market_fee );\n      const asset_object ted_usd = create_user_issued_asset( \"TEDUSD\", ted, charge_market_fee );\n      const asset_object ted_lp1 = create_user_issued_asset( \"TEDLP1\", ted, charge_market_fee );\n      const asset_object ted_lp2 = create_user_issued_asset( \"TEDLP2\", ted, charge_market_fee );\n      const asset_object ted_lp3 = create_user_issued_asset( \"TEDLP3\", ted, charge_market_fee );\n\n      // create liquidity pools\n      const liquidity_pool_object sam_lpo1 = create_liquidity_pool( sam_id, sam_eur.get_id(), sam_usd.get_id(),\n                                                                     sam_lp1.get_id(), 100, 310 );\n      const liquidity_pool_object sam_lpo2 = create_liquidity_pool( sam_id, sam_usd.get_id(), ted_usd.get_id(),\n                                                                     sam_lp2.get_id(), 200, 320 );\n      const liquidity_pool_object ted_lpo1 = create_liquidity_pool( ted_id, sam_usd.get_id(), ted_usd.get_id(),\n                                                                     ted_lp1.get_id(), 300, 330 );\n      const liquidity_pool_object ted_lpo2 = create_liquidity_pool( ted_id, sam_usd.get_id(), ted_eur.get_id(),\n                                                                     ted_lp2.get_id(), 400, 340 );\n      const liquidity_pool_object ted_lpo3 = create_liquidity_pool( ted_id, ted_eur.get_id(), ted_usd.get_id(),\n                                                                     ted_lp3.get_id(), 500, 350 );\n      generate_block();\n\n      // Check database API\n      graphene::app::database_api db_api( db, &( app.get_options() ) );\n\n      // list all pools\n      auto pools = db_api.list_liquidity_pools();\n      BOOST_REQUIRE_EQUAL( pools.size(), 5u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo1.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo3.get_id() );\n\n      // pagination\n      pools = db_api.list_liquidity_pools( 5, sam_lpo2.get_id() );\n      BOOST_REQUIRE_EQUAL( pools.size(), 4u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo3.get_id() );\n\n      // with statistics\n      pools = db_api.list_liquidity_pools( 2, sam_lpo2.get_id(), true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 2u );\n      BOOST_CHECK( pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo1.get_id() );\n\n      // get_liquidity_pools_by_asset_a\n      pools = db_api.get_liquidity_pools_by_asset_a( \"SAMUSD\" );\n      BOOST_REQUIRE_EQUAL( pools.size(), 3u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo2.get_id() );\n\n      // pagination and with statistics\n      pools = db_api.get_liquidity_pools_by_asset_a( \"SAMUSD\", 2, ted_lpo2.get_id(), true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 1u );\n      BOOST_CHECK( pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == ted_lpo2.get_id() );\n\n      // get_liquidity_pools_by_asset_b\n      pools = db_api.get_liquidity_pools_by_asset_b( \"TEDUSD\" );\n      BOOST_REQUIRE_EQUAL( pools.size(), 3u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo3.get_id() );\n\n      // pagination and with statistics\n      pools = db_api.get_liquidity_pools_by_asset_b( \"TEDUSD\", 2, sam_lpo1.get_id(), true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 2u );\n      BOOST_CHECK( pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo1.get_id() );\n\n      // get_liquidity_pools_by_one_asset\n      pools = db_api.get_liquidity_pools_by_one_asset( \"SAMUSD\" );\n      BOOST_REQUIRE_EQUAL( pools.size(), 4u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo1.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo2.get_id() );\n\n      // pagination and with statistics\n      pools = db_api.get_liquidity_pools_by_one_asset( \"SAMUSD\", 3, liquidity_pool_id_type(), true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 3u );\n      BOOST_CHECK( pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo1.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo1.get_id() );\n\n      // get_liquidity_pools_by_both_asset\n      pools = db_api.get_liquidity_pools_by_both_assets( \"SAMUSD\", \"TEDUSD\" );\n      BOOST_REQUIRE_EQUAL( pools.size(), 2u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo1.get_id() );\n\n      // pagination and with statistics\n      pools = db_api.get_liquidity_pools_by_both_assets( \"SAMUSD\", \"TEDUSD\", 3, ted_lpo2.get_id(), true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 0u );\n\n      // get_liquidity_pools_by_share_asset\n      auto opools = db_api.get_liquidity_pools_by_share_asset( { \"SAMLP1\", \"SAMEUR\" }, true, true );\n      BOOST_REQUIRE_EQUAL( opools.size(), 2u );\n      BOOST_CHECK( opools.front().valid() );\n      BOOST_CHECK( opools.front()->statistics.valid() );\n      BOOST_CHECK( opools.front()->id == sam_lpo1.get_id() );\n      BOOST_CHECK( !opools.back().valid() );\n\n      // get_liquidity_pools_by_owner\n      pools = db_api.get_liquidity_pools_by_owner( \"sam\" );\n      BOOST_REQUIRE_EQUAL( pools.size(), 2u );\n      BOOST_CHECK( !pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == sam_lpo1.get_id() );\n      BOOST_CHECK( pools.back().id == sam_lpo2.get_id() );\n\n      // pagination and with statistics\n      pools = db_api.get_liquidity_pools_by_owner( \"ted\", 5, ted_lp2.get_id(), true );\n      BOOST_REQUIRE_EQUAL( pools.size(), 2u );\n      BOOST_CHECK( pools.front().statistics.valid() );\n      BOOST_CHECK( pools.front().id == ted_lpo2.get_id() );\n      BOOST_CHECK( pools.back().id == ted_lpo3.get_id() );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/login_api_tests.cpp",
    "content": "/*\n * Copyright (c) 2022 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(login_api_tests, database_fixture)\n\nBOOST_AUTO_TEST_CASE( get_config_test )\n{ try {\n   auto default_opt = graphene::app::application_options::get_default();\n   auto opt = app.get_options();\n\n   graphene::app::login_api login_api1( app );\n\n   BOOST_CHECK( login_api1.get_info() == \"Test API node\" );\n\n   BOOST_CHECK_THROW( login_api1.get_config(), fc::exception );\n\n   login_api1.login(\"\",\"\"); // */*\n   auto config = login_api1.get_config();\n\n   BOOST_CHECK_EQUAL( default_opt.api_limit_get_call_orders, config.api_limit_get_call_orders );\n   BOOST_CHECK_EQUAL( opt.api_limit_get_call_orders, config.api_limit_get_call_orders );\n\n   BOOST_CHECK_EQUAL( default_opt.api_limit_get_full_accounts_subscribe, uint32_t(100) );\n   BOOST_CHECK_EQUAL( opt.api_limit_get_full_accounts_subscribe, uint32_t(120) );\n   BOOST_CHECK_EQUAL( config.api_limit_get_full_accounts_subscribe, uint32_t(120) );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_CASE( login_test )\n{ try {\n   graphene::app::login_api login_api1( app );\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 0u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n\n   login_api1.login(\"\",\"\"); // */*\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 3u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n   auto db_api1 = login_api1.database();\n   auto his_api1 = login_api1.history();\n   auto nb_api1 = login_api1.network_broadcast();\n\n   login_api1.login(\"user2\",\"superpassword2\");\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 1u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.database(), fc::exception );\n   auto his_api2 = login_api1.history();\n   BOOST_CHECK( his_api1 == his_api2 );\n\n   login_api1.login(\"user2\",\"superpassword3\"); // wrong password\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 0u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.database(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.history(), fc::exception );\n\n   login_api1.login(\"bytemaster\",\"looooooooooooooooongpassword\"); // wrong password\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 0u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.database(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.history(), fc::exception );\n\n   login_api1.login(\"bytemaster\",\"supersecret\");\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 10u );\n   auto nn_api3 = login_api1.network_node();\n   auto db_api3 = login_api1.database();\n   auto his_api3 = login_api1.history();\n   auto ord_api3 = login_api1.orders();\n   auto nb_api3 = login_api1.network_broadcast();\n   auto as_api3 = login_api1.asset();\n   auto cr_api3 = login_api1.crypto();\n   auto blk_api3 = login_api1.block();\n   auto co_api3 = login_api1.custom_operations();\n   auto dbg_api3 = login_api1.debug();\n   BOOST_CHECK( his_api1 == his_api3 );\n\n   login_api1.logout();\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 0u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.database(), fc::exception );\n   BOOST_CHECK_THROW( login_api1.history(), fc::exception );\n\n   login_api1.login(\"bytemaster2\",\"randompassword\"); // */*\n   BOOST_CHECK_EQUAL( login_api1.get_available_api_sets().size(), 3u );\n   BOOST_CHECK_THROW( login_api1.network_node(), fc::exception );\n   auto db_api4 = login_api1.database();\n   auto his_api4 = login_api1.history();\n   auto nb_api4 = login_api1.network_broadcast();\n   BOOST_CHECK( his_api1 == his_api4 );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/main.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"../common/init_unit_test_suite.hpp\"\n"
  },
  {
    "path": "tests/tests/margin_call_fee_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 Contributors\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\n// For account history\n#include <vector>\n#include <graphene/app/api.hpp>\n\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nstruct bitasset_database_fixture : database_fixture {\n   bitasset_database_fixture()\n           : database_fixture() {\n   }\n\n   const limit_order_create_operation\n   create_sell_operation(account_id_type user, const asset &amount, const asset &recv) {\n      const time_point_sec order_expiration = time_point_sec::maximum();\n      const price &fee_core_exchange_rate = price::unit_price();\n      limit_order_create_operation op = create_sell_operation(user, amount, recv, order_expiration,\n                                                              fee_core_exchange_rate);\n      return op;\n   }\n\n   const limit_order_create_operation\n   create_sell_operation(account_id_type user, const asset &amount, const asset &recv,\n                         const time_point_sec order_expiration,\n                         const price &fee_core_exchange_rate) {\n      limit_order_create_operation op = create_sell_operation(user(db), amount, recv, order_expiration,\n                                                              fee_core_exchange_rate);\n      return op;\n   }\n\n   const limit_order_create_operation\n   create_sell_operation(const account_object &user, const asset &amount, const asset &recv,\n                         const time_point_sec order_expiration,\n                         const price &fee_core_exchange_rate) {\n      limit_order_create_operation sell_order;\n      sell_order.seller = user.id;\n      sell_order.amount_to_sell = amount;\n      sell_order.min_to_receive = recv;\n      sell_order.expiration = order_expiration;\n\n      return sell_order;\n   }\n\n   const asset_create_operation create_user_issued_asset_operation(const string &name, const account_object &issuer,\n                                                                   uint16_t flags, const price &core_exchange_rate,\n                                                                   uint8_t precision, uint16_t maker_fee_percent,\n                                                                   uint16_t taker_fee_percent) {\n      asset_create_operation creator;\n      creator.issuer = issuer.id;\n      creator.fee = asset();\n      creator.symbol = name;\n      creator.common_options.max_supply = 0;\n      creator.precision = precision;\n\n      creator.common_options.core_exchange_rate = core_exchange_rate;\n      creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      creator.common_options.flags = flags;\n      creator.common_options.issuer_permissions = flags;\n      creator.common_options.market_fee_percent = maker_fee_percent;\n      creator.common_options.extensions.value.taker_fee_percent = taker_fee_percent;\n\n      return creator;\n\n   }\n};\n\n\nBOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture)\n\n   /**\n    * Test the effects of different MCFRs on derived prices and ratios\n    */\n   BOOST_AUTO_TEST_CASE(mcfr_tests) {\n      try {\n         ACTORS((charlie))\n         const asset_id_type core_id;\n\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n         //////\n         // Initialize\n         //////\n         fc::optional<uint16_t> mcfr;\n         price expected_offer_price; // The price offered by the margin call\n         price expected_paid_price; // The effective price paid by the margin call\n         ratio_type expected_margin_call_pays_ratio;\n\n         const asset_object core = core_id(db);\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT2\", charlie_id, smartbit_market_fee_percent, charge_market_fee, 2);\n         generate_block();\n         const asset_object smartbit2 = get_asset(\"SMARTBIT2\");\n         BOOST_CHECK_EQUAL(2, smartbit2.precision);\n\n         // Construct a price feed\n         // Initial price of 1 satoshi SMARTBIT2 for 20 satoshi CORE\n         // = 0.0001 SMARTBIT2 for 0.00020 CORE = 1 SMARTBIT2 for 2 CORE\n         const price initial_price =\n                 smartbit2.amount(1) / core.amount(20); // 1 satoshi SMARTBIT2 for 20 satoshi CORE\n\n         price_feed feed;\n         feed.settlement_price = initial_price;\n         feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         feed.maximum_short_squeeze_ratio = 1500; // MSSR of 1.50x\n\n         //////\n         // Check prices and ratios when MSSR = 150% and MCFR is not set\n         //////\n         mcfr = {};\n\n         // Expected paid price = price / MSSR\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1500 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.max_short_squeeze_price());\n\n         // Expected offer price = price / (MSSR-MCFR)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / ([1500 - 0] / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.margin_call_order_price(mcfr));\n\n         // Expected margin call pays ratio = (MSSR-MCFR) / MSSR\n         // = (1500 - 0) / 1500\n         // = 1\n         expected_margin_call_pays_ratio = ratio_type(1, 1);\n         BOOST_CHECK(expected_margin_call_pays_ratio == feed.margin_call_pays_ratio(mcfr));\n\n\n         //////\n         // Check prices and ratios when MSSR = 150% and MCFR = 0\n         //////\n         mcfr = 0;\n\n         // Expected paid price = price / MSSR\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1500 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.max_short_squeeze_price());\n\n         // Expected offer price = price / (MSSR-MCFR)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / ([1500 - 0] / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.margin_call_order_price(mcfr));\n\n         // Expected margin call pays ratio = (MSSR-MCFR) / MSSR\n         // = (1500 - 0) / 1500\n         // = 1\n         expected_margin_call_pays_ratio = ratio_type(1, 1);\n         BOOST_CHECK(expected_margin_call_pays_ratio == feed.margin_call_pays_ratio(mcfr));\n\n\n         //////\n         // Check prices and ratios when MSSR = 150% and MCFR = 5%\n         //////\n         mcfr = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n\n         // Expected paid price = price / MSSR\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1500 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.max_short_squeeze_price());\n\n         // Expected offer price = price / (MSSR-MCFR)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / ([1500 - 50] / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1450 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (145 / 100)\n         // = (1 satoshi SMARTBIT2 / 2 satoshi Core) / (145 / 10)\n         // = (10 satoshi SMARTBIT2 / 290 satoshi Core)\n         // = (1 satoshi SMARTBIT2 / 29 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(29));\n         BOOST_CHECK(expected_paid_price == feed.margin_call_order_price(mcfr));\n\n         // Expected margin call pays ratio = (MSSR-MCFR) / MSSR\n         // = (1500 - 50) / 1500\n         // = 1450 / 1500 = 145 / 150 = 29 / 30\n         expected_margin_call_pays_ratio = ratio_type(29, 30);\n         BOOST_CHECK(expected_margin_call_pays_ratio == feed.margin_call_pays_ratio(mcfr));\n\n\n         //////\n         // Check prices and ratios when MSSR = 150% and MCFR = 30%\n         //////\n         mcfr = 300; // 30% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n\n         // Expected paid price = price / MSSR\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1500 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.max_short_squeeze_price());\n\n         // Expected offer price = price / (MSSR-MCFR)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / ([1500 - 300] / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1200 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (6 / 5)\n         // = (5 satoshi SMARTBIT2 / 120 satoshi Core)\n         // = (1 satoshi SMARTBIT2 / 24 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(24));\n         BOOST_CHECK(expected_paid_price == feed.margin_call_order_price(mcfr));\n\n         // Expected margin call pays ratio = (MSSR-MCFR) / MSSR\n         // = (1500 - 300) / 1500\n         // = 1200 / 1500 = 4 / 5\n         expected_margin_call_pays_ratio = ratio_type(4, 5);\n         BOOST_CHECK(expected_margin_call_pays_ratio == feed.margin_call_pays_ratio(mcfr));\n\n\n         //////\n         // Check prices and ratios when MSSR = 150% and MCFR = 60%\n         //////\n         mcfr = 600; // 60% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n\n         // Expected paid price = price / MSSR\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (1500 / 1000)\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core) / (3 / 2)\n         // = (1 satoshi SMARTBIT2 / 30 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(30));\n         BOOST_CHECK(expected_paid_price == feed.max_short_squeeze_price());\n\n         // Expected offer price = price / (MSSR-MCFR)\n         // but (MSSR-MCFR) has a floor 1\n         // Therefore = price / 1 = price\n         // = (1 satoshi SMARTBIT2 / 20 satoshi Core)\n         expected_paid_price = price(smartbit2.amount(1), core.amount(20));\n         BOOST_CHECK(expected_paid_price == feed.margin_call_order_price(mcfr));\n\n         // Expected margin call pays ratio = (MSSR-MCFR) / MSSR\n         // but (MSSR-MCFR) has a floor 1\n         // Therefore = 1 / MSSR\n         // = 1000 / 1500 = 2 / 3\n         expected_margin_call_pays_ratio = ratio_type(2, 3);\n         BOOST_CHECK(expected_margin_call_pays_ratio == feed.margin_call_pays_ratio(mcfr));\n\n      }\n      FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test a simple scenario of a Complete Fill of a Call Order as a Maker after HF\n    *\n    * 0. Advance to HF\n    * 1. Initialize actors and a smart asset called SMARTBIT\n    * 2. Publish feed\n    * 3. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n    *     Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n    *     because his debt position is what will be tracked.\n    * 4. The feed price is updated to indicate that the collateral drops enough to trigger a margin call\n    *    **but not enough** to trigger a global settlement.\n    *    Bob's activated margin call cannot be matched against any existing limit order's price.\n    * 5. (Order 2: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT at a price\n    *    that will overlap with Bob's \"activated\" call order / margin call.\n    *    **Bob should be charged as a maker, and Alice as a taker.**\n    *    Alice's limit order should be (partially or completely) filled, but Bob's order should be completely filled,\n    *    and the debt position should be closed.\n    */\n   BOOST_AUTO_TEST_CASE(complete_fill_of_call_order_as_maker) {\n      try {\n         //////\n         // 0. Advance to activate hardfork\n         //////\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n\n         //////\n         // 1. Initialize actors and a smart asset called SMARTBIT\n         //////\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         // CORE asset exists by default\n         const asset_object &core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n         const int64_t CORE_UNIT = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n\n         // Create the SMARTBIT asset\n         const int16_t SMARTBIT_UNIT = 10000; // 10000 satoshi SMARTBIT in 1 SMARTBIT\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const uint16_t smartbit_margin_call_fee_ratio = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         // Define the margin call fee ratio\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 4, core_id,\n                         GRAPHENE_MAX_SHARE_SUPPLY, {}, smartbit_margin_call_fee_ratio);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object smartbit = get_asset(\"SMARTBIT\");\n         const asset_id_type smartbit_id = smartbit.get_id();\n         update_feed_producers(smartbit, {feedproducer_id});\n\n         // Initialize token balance of actors\n         // Alice should start with 5,000,000 CORE\n         const asset alice_initial_core = asset(5000000 * CORE_UNIT);\n         transfer(committee_account, alice_id, alice_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id, core_id), alice_initial_core.amount.value);\n\n         // Bob should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 2x\n         const price initial_feed_price =\n                 smartbit.amount(1) / core.amount(20); // 1 satoshi SMARTBIT for 20 satoshi CORE\n         const asset bob_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset bob_initial_core = core.amount(\n                 2 * (bob_initial_smart * initial_feed_price).amount); // 80,000,000 satoshi CORE\n         transfer(committee_account, bob_id, bob_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, core), 80000000);\n\n\n         //////\n         // 2. Publish feed\n         //////\n         price_feed current_feed;\n         current_feed.settlement_price = initial_feed_price;\n         current_feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         current_feed.maximum_short_squeeze_ratio = 1500; // MSSR of 1.50x\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // 3. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n         //    Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n         //    because his debt position is what will be tracked.\n         //////\n         call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core)).get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n\n         //////\n         // 4. The feed price is updated to indicate that the collateral drops enough to trigger a margin call\n         //    **but not enough** to trigger a global settlement.\n         //    Bob's activated margin call cannot be matched against any existing limit order's price.\n         //////\n         // Adjust the price such that the initial CR of Bob's position (CR_0) drops to 1.7x = (17/10)x\n         // Want new price = 1.7 / CR_0 = (17/10) / CR_0\n         //\n         // Collateral ratios are defined as collateral / debt\n         // BitShares prices are conventionally defined as debt / collateral\n         // The new price can be expressed with the available codebase as\n         // = (17/10) * ~CR_0 = ~CR_0 * (17/10)\n         const price intermediate_feed_price = ~bob_initial_cr * ratio_type(17, 10); // Units of debt / collateral\n         // Reduces to (2000000 * 17) / (80000000 * 10) = (17) / (40 * 10) = 17 / 400\n         BOOST_CHECK(intermediate_feed_price < initial_feed_price);\n         BOOST_CHECK_EQUAL(intermediate_feed_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(intermediate_feed_price.quote.amount.value, 400); // satoshi CORE\n\n         current_feed.settlement_price = intermediate_feed_price;\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n\n         BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n\n         // Check Bob's debt to the blockchain\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value, bob_initial_core.amount.value);\n\n         // Check Bob's balances\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 0);\n\n\n\n         //////\n         // 5. (Order 2: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT at a price\n         //    that will overlap with Bob's \"activated\" call order / margin call.\n         //    **Bob should be charged as a maker, and Alice as a taker.**\n         //    Alice's limit order should be (partially or completely) filled, but Bob's order should be completely filled,\n         //    and the debt position should be closed.\n         //////\n         // Alice obtains her SMARTBIT from Bob\n         transfer(bob_id, alice_id, bob_initial_smart);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), bob_initial_smart.amount.value);\n\n         // The margin call should be priced at settlement_price / (MSSR-MCFR)\n         // where settlement_price is expressed as debt / collateral\n         // Create a \"large\" sell order at a \"high\" price of settlement_price * 1.1 = settlement_price * (11/10)\n         const price alice_order_price_implied = intermediate_feed_price * ratio_type(11, 10);\n\n\n         const asset alice_debt_to_sell = smartbit.amount(get_balance(alice_id(db), smartbit_id(db)));\n         // multiply_and_round_up() handles inverting the price so that the output is in correct collateral units\n         const asset alice_collateral_to_buy = alice_debt_to_sell.multiply_and_round_up(alice_order_price_implied);\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice_id, alice_debt_to_sell,\n                                                                            alice_collateral_to_buy);\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         // asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Margin call should exchange all of the available debt (X) for X*(MSSR-MCFR)/settlement_price\n         // The match price should be the settlement_price/(MSSR-MCFR) = settlement_price/(MSSR-MCFR)\n         const uint16_t ratio_numerator = current_feed.maximum_short_squeeze_ratio - smartbit_margin_call_fee_ratio;\n         BOOST_REQUIRE_EQUAL(ratio_numerator,\n                             1450); // GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO - smartbit_margin_call_fee_ratio\n         const price expected_match_price = intermediate_feed_price * ratio_type(GRAPHENE_COLLATERAL_RATIO_DENOM,\n                                                                           ratio_numerator);\n         // Reduces to (17 satoshi SMARTBIT / 400 satoshi CORE) * (1000 / 1450)\n         // = (17 satoshi SMARTBIT / 400 satoshi CORE) * (100 / 145)\n         // = (17 satoshi SMARTBIT / 4 satoshi CORE) * (1 / 145)\n         // = 17 satoshi SMARTBIT / 580 satoshi CORE\n         BOOST_CHECK_EQUAL(expected_match_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(expected_match_price.quote.amount.value, 580); // satoshi CORE\n\n         // Payment to limit order = X*(MSSR-MCFR)/settlement_price\n         // = 2000000 satoshi SMARTBIT * (580 satoshi CORE / 17 satoshi SMARTBIT)\n         // = 68235294.1176 satoshi CORE rounded up to 68235295 satoshi CORE = 682.35295 CORE\n         const asset expected_payment_to_alice_core = core.amount(68235295);\n\n         // Expected payment by call order: filled_debt * (MSSR / settlement_price) = filled_debt * (MSSR / settlement_price)\n         //\n         // (MSSR / settlement_price) = (1500 / 1000) / (17 satoshi SMARTBIT / 400 satoshi CORE)\n         // = (15 / 10) / (17 satoshi SMARTBIT / 400 satoshi CORE)\n         // = (15 / 1) / (17 satoshi SMARTBIT / 40 satoshi CORE)\n         // = (15 * 40 satoshi CORE) / (17 satoshi SMARTBIT)\n         // = (15 * 40 satoshi CORE) / (17 satoshi SMARTBIT)\n         // = 600 satoshi CORE / 17 satoshi SMARTBIT\n         //\n         // Expected payment by call order = 2000000 satoshi SMARTBIT * (600 satoshi CORE / 17 satoshi SMARTBIT)\n         // = 2000000 * 600 satoshi CORE / 17\n         // = 70588235.2941 satoshi CORE rounding up to 70588236 satoshi CORE = 705.88236 CORE\n         const asset expected_payment_from_bob_core = core.amount(70588236);\n\n         // Expected fee = payment by call order - payment to limit order\n         // fee = (70588236 - 68235295) satoshi CORE = 2352941 satoshi CORE = 23.52941 CORE\n         const asset expected_margin_call_fee =\n                 expected_payment_from_bob_core - expected_payment_to_alice_core; // core.amount(2352941);\n\n         // Check Alice's balances\n         BOOST_CHECK_EQUAL(get_balance(alice, smartbit), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice, core),\n                           alice_initial_core.amount.value + expected_payment_to_alice_core.amount.value);\n\n         // Check Alice's limit order is closed\n         BOOST_CHECK(!db.find(alice_order_id));\n\n         // Check Bob's debt position is closed\n         BOOST_CHECK(!db.find(bob_call_id));\n\n         // Check Bob's balances\n         // Bob should have no debt asset\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0);\n         // Bob should have collected the balance of his collateral after the margin call\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)),\n                           bob_initial_core.amount.value - expected_payment_from_bob_core.amount.value);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                           expected_margin_call_fee.amount.value);\n\n         // Check the fee of the fill operations for Alice and Bob\n         generate_block(); // To trigger db_notify() and record pending operations into histories\n         graphene::app::history_api hist_api(app);\n         vector <operation_history_object> histories;\n         const int fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n         // Check Alice's history\n         histories = hist_api.get_account_history_operations(\n                 \"alice\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Alice's fill order for her limit order should have zero fee\n         fill_order_operation alice_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(alice_fill_op.fee == asset(0));\n         // Alice's fill order's fill price should equal the expected match price\n         BOOST_CHECK(~alice_fill_op.fill_price == expected_match_price);\n\n         // Check Bob's history\n         histories = hist_api.get_account_history_operations(\n                 \"bob\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Bob's fill order for his margin call should have a fee equal to the margin call fee\n         fill_order_operation bob_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(bob_fill_op.fee == expected_margin_call_fee);\n         // Bob's fill order's fill price should equal the expected match price\n         BOOST_CHECK(~bob_fill_op.fill_price == expected_match_price);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test a scenario of a Complete Fill of a Call Order as a Maker after HF\n    * that evaluates the price ranges of matchable limit orders.\n    * Before BSIP74, taker limit orders must be priced >= settlement_price/MSSR\n    * After BSIP74, taker limit orders must priced >= settlement_price/(MSSR-MCFR)\n    *\n    * 0. Advance to HF\n    * 1. Initialize actors and a smart asset called SMARTBIT\n    * 2. Publish feed\n    * 3. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n    *     Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n    *     because his debt position is what will be tracked.\n    * 4. The feed price is updated to indicate that the collateral drops enough to trigger a margin call\n    *    **but not enough** to trigger a global settlement.\n    *    Bob's activated margin call cannot be matched against any existing limit order's price.\n    * 5. (Order 2: Limit order) Charlie places a **\"large\"** limit order to sell SMARTBIT at a price\n    *    that should NOT overlap with Bob's \"activated\" call order / margin call but would have before BSIP74.\n    *    **Bob's margin call should not be affected.\n    * 6. (Order 3: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT at a price\n    *    that will overlap with Bob's \"activated\" call order / margin call.\n    *    **Bob should be charged as a maker, and Alice as a taker.**\n    *    Alice's limit order should be (partially or completely) filled, but Bob's order should be completely filled,\n    *    and the debt position should be closed.\n    *\n    * Summary: The offer price of the taker limit order affects whether it matches the margin call order.\n    *          The offer price of the taker limit order DOES NOT affect the filling.\n    *          Filling of a maker margin call / taker limit order is based on the the call order's match price.\n    */\n   BOOST_AUTO_TEST_CASE(complete_fill_of_call_order_as_maker_2) {\n      try {\n         //////\n         // 0. Advance to activate hardfork\n         //////\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n\n         //////\n         // 1. Initialize actors and a smart asset called SMARTBIT\n         //////\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob)(charlie));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         // CORE asset exists by default\n         const asset_object &core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n         const int64_t CORE_UNIT = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n\n         // Create the SMARTBIT asset\n         const int16_t SMARTBIT_UNIT = 10000; // 10000 satoshi SMARTBIT in 1 SMARTBIT\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const uint16_t smartbit_margin_call_fee_ratio = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         // Define the margin call fee ratio\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 4, core_id,\n                         GRAPHENE_MAX_SHARE_SUPPLY, {}, smartbit_margin_call_fee_ratio);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object smartbit = get_asset(\"SMARTBIT\");\n         const asset_id_type smartbit_id = smartbit.get_id();\n         update_feed_producers(smartbit, {feedproducer_id});\n\n         // Initialize token balance of actors\n         // Alice should start with 5,000,000 CORE\n         const asset alice_initial_core = asset(5000000 * CORE_UNIT);\n         transfer(committee_account, alice_id, alice_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id, core_id), alice_initial_core.amount.value);\n\n         // Bob should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 2x\n         const price initial_feed_price =\n                 smartbit.amount(1) / core.amount(20); // 1 satoshi SMARTBIT for 20 satoshi CORE\n         const asset bob_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset bob_initial_core = core.amount(\n                 2 * (bob_initial_smart * initial_feed_price).amount); // 80,000,000 satoshi CORE\n         transfer(committee_account, bob_id, bob_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, core), 80000000);\n\n         // Charlie should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 3x\n         const asset charlie_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset charlie_initial_core = core.amount(\n                 3 * (bob_initial_smart * initial_feed_price).amount); // 120,000,000 satoshi CORE\n         transfer(committee_account, charlie_id, charlie_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, core), 120000000);\n\n\n         //////\n         // 2. Publish feed\n         //////\n         price_feed current_feed;\n         current_feed.settlement_price = initial_feed_price;\n         current_feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         current_feed.maximum_short_squeeze_ratio = 1500; // MSSR of 1.50x\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // 3. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n         //    Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n         //    because his debt position is what will be tracked.\n         //////\n         call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core)).get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n\n         //////\n         // 4. The feed price is updated to indicate that the collateral drops enough to trigger a margin call\n         //    **but not enough** to trigger a global settlement.\n         //    Bob's activated margin call cannot be matched against any existing limit order's price.\n         //////\n         // Adjust the price such that the initial CR of Bob's position (CR_0) drops to 1.7x = (17/10)x\n         // Want new price = 1.7 / CR_0 = (17/10) / CR_0\n         //\n         // Collateral ratios are defined as collateral / debt\n         // BitShares prices are conventionally defined as debt / collateral\n         // The new price can be expressed with the available codebase as\n         // = (17/10) * ~CR_0 = ~CR_0 * (17/10)\n         const price intermediate_feed_price = ~bob_initial_cr * ratio_type(17, 10); // Units of debt / collateral\n         // Reduces to (2000000 * 17) / (80000000 * 10) = (17) / (40 * 10) = 17 / 400\n         BOOST_CHECK(intermediate_feed_price < initial_feed_price);\n         BOOST_CHECK_EQUAL(intermediate_feed_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(intermediate_feed_price.quote.amount.value, 400); // satoshi CORE\n\n         current_feed.settlement_price = intermediate_feed_price;\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n\n         BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n\n         // Check Bob's debt to the blockchain\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value, bob_initial_core.amount.value);\n\n         // Check Bob's balances\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 0);\n\n\n         //////\n         // 5. (Order 2: Limit order) Charlie places a **\"large\"** limit order to sell SMARTBIT at a price\n         //    that SHOULD NOT overlap with Bob's \"activated\" call order / margin call but would have before BSIP74.\n         //    **Bob's margin call SHOULD NOT be affected.**\n         //////\n         // Charlie obtains his SMARTBIT by borrowing it from the blockchain\n         call_order_id_type charlie_call_id = (*borrow(charlie, charlie_initial_smart, charlie_initial_core)).get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, smartbit), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price charlie_initial_cr = charlie_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(charlie_initial_cr.base.amount.value, 120000000); // Collateral of 120,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(charlie_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n         // Check Charlie's liquid balance\n         BOOST_CHECK_EQUAL(get_balance(charlie_id(db), core_id(db)), 0);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id(db), smartbit_id(db)), charlie_initial_smart.amount.value);\n\n         // The margin call match price should be the settlement_price/(MSSR-MCFR) = settlement_price/(MSSR-MCFR)\n         const uint16_t ratio_numerator = current_feed.maximum_short_squeeze_ratio - smartbit_margin_call_fee_ratio;\n         BOOST_REQUIRE_EQUAL(ratio_numerator,\n                             1450); // GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO - smartbit_margin_call_fee_ratio\n         const price expected_match_price = intermediate_feed_price * ratio_type(GRAPHENE_COLLATERAL_RATIO_DENOM,\n                                                                                 ratio_numerator);\n         // Reduces to (17 satoshi SMARTBIT / 400 satoshi CORE) * (1000 / 1450)\n         // = (17 satoshi SMARTBIT / 400 satoshi CORE) * (100 / 145)\n         // = (17 satoshi SMARTBIT / 4 satoshi CORE) * (1 / 145)\n         // = 17 satoshi SMARTBIT / 580 satoshi CORE\n         BOOST_CHECK_EQUAL(expected_match_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(expected_match_price.quote.amount.value, 580); // satoshi CORE\n\n         // Charlie create a \"large\" sell order SLIGHTLY BELOW the match_price\n         // This price should ensure that the order is NOT matched against Bob's margin call\n         // The margin call should be priced at settlement_price / (MSSR-MCFR)\n         // where settlement_price is expressed as debt / collateral\n         const price charlie_order_price = price(smartbit.amount(17), core.amount(580));\n         BOOST_CHECK(charlie_order_price == expected_match_price); // Exactly at the edge\n\n         const asset charlie_debt_to_sell = smartbit.amount(get_balance(charlie_id(db), smartbit_id(db)));\n         // multiply_and_round_up() handles inverting the price so that the output is in correct collateral units\n         const asset charlie_collateral_to_buy = charlie_debt_to_sell.multiply_and_round_up(charlie_order_price);\n         limit_order_create_operation charlie_sell_op = create_sell_operation(charlie_id, charlie_debt_to_sell,\n                                                                            charlie_collateral_to_buy);\n         // The limit order's price should be slightly below the expected match price\n         // due to multiply_and_round_up() which increases the collateral\n         // thereby decreasing the ratio of debt / collateral\n         BOOST_CHECK(charlie_sell_op.get_price() < expected_match_price);\n\n         trx.clear();\n         trx.operations.push_back(charlie_sell_op);\n         // asset charlie_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, charlie_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type charlie_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check Charlies's limit order is still open\n         BOOST_CHECK(db.find(charlie_order_id));\n\n         // Check Charlies's limit order is NOT CHANGED\n         const limit_order_object charlie_limit_order = charlie_order_id(db);\n         BOOST_CHECK(charlie_limit_order.amount_for_sale() == charlie_debt_to_sell);\n         BOOST_CHECK(charlie_limit_order.amount_to_receive() == charlie_collateral_to_buy);\n\n         // Check Bob's debt position is still open\n         BOOST_CHECK(db.find(bob_call_id));\n\n         // Check Bob's debt to the blockchain is NOT CHANGED\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value, bob_initial_core.amount.value);\n\n\n         //////\n         // 6. (Order 2: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT at a price\n         //    that will overlap with Bob's \"activated\" call order / margin call.\n         //    **Bob should be charged as a maker, and Alice as a taker.**\n         //    Alice's limit order should be (partially or completely) filled,\n         //    but Bob's order should be completely filled,\n         //    and the debt position should be closed.\n         //////\n         // Alice obtains her SMARTBIT from Bob\n         transfer(bob_id, alice_id, bob_initial_smart);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), bob_initial_smart.amount.value);\n\n         // The margin call should be priced at settlement_price / (MSSR-MCFR)\n         // where settlement_price is expressed as debt / collateral\n         // Create a \"large\" sell order at JUST above the expected match price\n         const price alice_order_price_implied = intermediate_feed_price * ratio_type(11, 10);\n         const price alice_order_price = price(smartbit.amount(17 + 1), core.amount(580)); // Barely matching\n         // const price alice_order_price = price(smartbit.amount(17), core.amount(580 - 1)); // Barely matching\n         BOOST_CHECK(alice_order_price > expected_match_price);\n\n         const asset alice_debt_to_sell = smartbit.amount(get_balance(alice_id(db), smartbit_id(db)));\n         // multiply_and_round_up() handles inverting the price so that the output is in correct collateral units\n         const asset alice_collateral_to_buy = alice_debt_to_sell.multiply_and_round_up(alice_order_price_implied);\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice_id, alice_debt_to_sell,\n                                                                            alice_collateral_to_buy);\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         // asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Margin call should exchange all of the available debt (X) for X*(MSSR-MCFR)/settlement_price\n         // Payment to limit order = X*(MSSR-MCFR)/settlement_price\n         // = 2000000 satoshi SMARTBIT * (580 satoshi CORE / 17 satoshi SMARTBIT)\n         // = 68235294.1176 satoshi CORE rounded up to 68235295 satoshi CORE = 682.35295 CORE\n         const asset expected_payment_to_alice_core = core.amount(68235295);\n\n         // Expected payment by call order: filled_debt * (MSSR / settlement_price) = filled_debt * (MSSR / settlement_price)\n         //\n         // (MSSR / settlement_price) = (1500 / 1000) / (17 satoshi SMARTBIT / 400 satoshi CORE)\n         // = (15 / 10) / (17 satoshi SMARTBIT / 400 satoshi CORE)\n         // = (15 / 1) / (17 satoshi SMARTBIT / 40 satoshi CORE)\n         // = (15 * 40 satoshi CORE) / (17 satoshi SMARTBIT)\n         // = (15 * 40 satoshi CORE) / (17 satoshi SMARTBIT)\n         // = 600 satoshi CORE / 17 satoshi SMARTBIT\n         //\n         // Expected payment by call order = 2000000 satoshi SMARTBIT * (600 satoshi CORE / 17 satoshi SMARTBIT)\n         // = 2000000 * 600 satoshi CORE / 17\n         // = 70588235.2941 satoshi CORE rounding up to 70588236 satoshi CORE = 705.88236 CORE\n         const asset expected_payment_from_bob_core = core.amount(70588236);\n\n         // Expected fee = payment by call order - payment to limit order\n         // fee = (70588236 - 68235295) satoshi CORE = 2352941 satoshi CORE = 23.52941 CORE\n         const asset expected_margin_call_fee =\n                 expected_payment_from_bob_core - expected_payment_to_alice_core; // core.amount(2352941);\n\n         // Check Alice's balances\n         BOOST_CHECK_EQUAL(get_balance(alice, smartbit), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice, core),\n                           alice_initial_core.amount.value + expected_payment_to_alice_core.amount.value);\n\n         // Check Alice's limit order is close\n         BOOST_CHECK(!db.find(alice_order_id));\n\n         // Check Bob's debt position is closed\n         BOOST_CHECK(!db.find(bob_call_id));\n\n         // Check Bob's balances\n         // Bob should have no debt asset\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0);\n         // Bob should have collected the balance of his collateral after the margin call\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)),\n                           bob_initial_core.amount.value - expected_payment_from_bob_core.amount.value);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                           expected_margin_call_fee.amount.value);\n\n         // Check the fee of the fill operations for Alice and Bob\n         generate_block(); // To trigger db_notify() and record pending operations into histories\n         graphene::app::history_api hist_api(app);\n         vector <operation_history_object> histories;\n         const int fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n         // Check Alice's history\n         histories = hist_api.get_account_history_operations(\n                 \"alice\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Alice's fill order for her limit order should have zero fee\n         fill_order_operation alice_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(alice_fill_op.fee == asset(0));\n         // Alice's fill order's fill price should equal the expected match price\n         BOOST_CHECK(~alice_fill_op.fill_price == expected_match_price);\n\n         // Check Bob's history\n         histories = hist_api.get_account_history_operations(\n                 \"bob\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Bob's fill order for his margin call should have a fee equal to the margin call fee\n         fill_order_operation bob_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(bob_fill_op.fee == expected_margin_call_fee);\n         // Bob's fill order's fill price should equal the expected match price\n         BOOST_CHECK(~bob_fill_op.fill_price == expected_match_price);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test a scenario of a partial Filling of a Call Order as a Maker after HF\n    * where the partial filling is due to call order defining a target collateral ratio (TCR) (BSIP38)\n    *\n    * 0. Advance to HF\n    * 1. Initialize actors and a smart asset called SMARTBIT\n    * 2. Publish feed\n    * 3. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n    *     Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n    *     because his debt position is what will be tracked.\n    * 4. The feed price is updated to indicate that the collateral drops enough to trigger a margin call\n    *    **but not enough** to trigger a global settlement.\n    *    Bob's activated margin call cannot be matched against any existing limit order's price.\n    * 5. (Order 2: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT at a price\n    *    that will overlap with Bob's \"activated\" call order / margin call.\n    *    **Bob should be charged as a maker, and Alice as a taker.**\n    *    Alice's limit order should be (partially or completely) filled,\n    *    but Bob's order will also only be partially filled because the TCR will sell just enough collateral\n    *    so that the remaining CR of the debt position >= TCR.\n    *    Bob's debt position should remain open.\n    */\n   BOOST_AUTO_TEST_CASE(target_cr_partial_fill_of_call_order_as_maker) {\n      try {\n         //////\n         // 0. Advance to activate hardfork\n         //////\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n\n         //////\n         // 1. Initialize actors and a smart asset called SMARTBIT\n         //////\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         // CORE asset exists by default\n         const asset_object &core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n         const int64_t CORE_UNIT = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n\n         // Create the SMARTBIT asset\n         const int16_t SMARTBIT_UNIT = 10000; // 10000 satoshi SMARTBIT in 1 SMARTBIT\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const uint16_t smartbit_margin_call_fee_ratio = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         // Define the margin call fee ratio\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 4, core_id,\n                         GRAPHENE_MAX_SHARE_SUPPLY, {}, smartbit_margin_call_fee_ratio);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object smartbit = get_asset(\"SMARTBIT\");\n         const asset_id_type smartbit_id = smartbit.get_id();\n         update_feed_producers(smartbit, {feedproducer_id});\n\n         // Initialize token balance of actors\n         // Alice should start with 5,000,000 CORE\n         const asset alice_initial_core = asset(5000000 * CORE_UNIT);\n         transfer(committee_account, alice_id, alice_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id, core_id), alice_initial_core.amount.value);\n\n         // Bob should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 2x\n         const price initial_feed_price =\n                 smartbit.amount(1) / core.amount(20); // 1 satoshi SMARTBIT for 20 satoshi CORE\n         const asset bob_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset bob_initial_core = core.amount(\n                 2 * (bob_initial_smart * initial_feed_price).amount); // 80,000,000 satoshi CORE\n         transfer(committee_account, bob_id, bob_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, core), 80000000);\n\n\n         //////\n         // 2. Publish feed\n         //////\n         price_feed current_feed;\n         current_feed.settlement_price = initial_feed_price;\n         current_feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         current_feed.maximum_short_squeeze_ratio = 1500; // MSSR of 1.50x\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // 3. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n         //    Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n         //    because his debt position is what will be tracked.\n         //////\n         const uint16_t tcr = 2200; // Bob's target collateral ratio (TCR) 220% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core, tcr)).get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n\n         //////\n         // 4. The feed price is updated to indicate that the collateral drops enough to trigger a margin call\n         //    **but not enough** to trigger a global settlement.\n         //    Bob's activated margin call cannot be matched against any existing limit order's price.\n         //////\n         // Adjust the price such that the initial CR of Bob's position (CR_0) drops to 1.7x = (17/10)x\n         // Want new price = 1.7 / CR_0 = (17/10) / CR_0\n         //\n         // Collateral ratios are defined as collateral / debt\n         // BitShares prices are conventionally defined as debt / collateral\n         // The new price can be expressed with the available codebase as\n         // = (17/10) * ~CR_0 = ~CR_0 * (17/10)\n         const price intermediate_feed_price = ~bob_initial_cr * ratio_type(17, 10); // Units of debt / collateral\n         // Reduces to (2000000 * 17) / (80000000 * 10) = (17) / (40 * 10) = 17 / 400\n         BOOST_CHECK(intermediate_feed_price < initial_feed_price);\n         BOOST_CHECK_EQUAL(intermediate_feed_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(intermediate_feed_price.quote.amount.value, 400); // satoshi CORE\n\n         current_feed.settlement_price = intermediate_feed_price;\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n\n         BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n\n         // Check Bob's debt to the blockchain\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value, bob_initial_core.amount.value);\n\n         // Check Bob's balances\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 0);\n\n\n\n         //////\n         // 5. (Order 2: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT at a price\n         //    that will overlap with Bob's \"activated\" call order / margin call.\n         //    **Bob should be charged as a maker, and Alice as a taker.**\n         //    Alice's limit order should be (partially or completely) filled,\n         //    but Bob's order will also only be partially filled because the TCR will sell just enough collateral\n         //    so that the remaining CR of the debt position >= TCR.\n         //    Bob's debt position should remain open.\n         //////\n         // Alice obtains her SMARTBIT from Bob\n         transfer(bob_id, alice_id, bob_initial_smart);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), bob_initial_smart.amount.value);\n\n         // The margin call should be priced at settlement_price / (MSSR-MCFR)\n         // where settlement_price is expressed as debt / collateral\n         // Create a \"large\" sell order at a \"high\" price of settlement_price * 1.1 = settlement_price * (11/10)\n         const price alice_order_price_implied = intermediate_feed_price * ratio_type(11, 10);\n\n\n         const asset alice_debt_to_sell = smartbit.amount(get_balance(alice_id(db), smartbit_id(db)));\n         // multiply_and_round_up() handles inverting the price so that the output is in correct collateral units\n         const asset alice_collateral_to_buy = alice_debt_to_sell.multiply_and_round_up(alice_order_price_implied);\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice_id, alice_debt_to_sell,\n                                                                            alice_collateral_to_buy);\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         // asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // The match price **as maker** should be the settlement_price/(MSSR-MCFR) = settlement_price/(MSSR-MCFR)\n         const uint16_t ratio_numerator = current_feed.maximum_short_squeeze_ratio - smartbit_margin_call_fee_ratio;\n         BOOST_REQUIRE_EQUAL(ratio_numerator,\n                             1450); // GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO - smartbit_margin_call_fee_ratio\n         const price expected_match_price = intermediate_feed_price * ratio_type(GRAPHENE_COLLATERAL_RATIO_DENOM,\n                                                                                 ratio_numerator);\n         // Reduces to (17 satoshi SMARTBIT / 400 satoshi CORE) * (1000 / 1450)\n         // = (17 satoshi SMARTBIT / 400 satoshi CORE) * (100 / 145)\n         // = (17 satoshi SMARTBIT / 4 satoshi CORE) * (1 / 145)\n         // = 17 satoshi SMARTBIT / 580 satoshi CORE\n         BOOST_CHECK_EQUAL(expected_match_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(expected_match_price.quote.amount.value, 580); // satoshi CORE\n\n         // When a TCR is set for a call order, the ideal is to not sell all of the collateral\n         // but only enough collateral so that the remaining collateral and the remaining debt in the debt position\n         // has a resulting CR >= TCR.  The specifications are described in BSIP38.\n         //\n         // Per BSIP38, the expected amount to sell from the call order is\n         // max_amount_to_sell = (debt * target_CR - collateral * settlement_price)\n         //                    / (target_CR * match_price - settlement_price)\n         //\n         // HOWEVER, the match price that is used in this calculation\n         // NEEDS TO BE ADJUSTED to account for the extra MCFR > 0 that will be paid by the call order.\n         //\n         // Rather than using a match price of settlement_price/(MSSR-MCFR) **AS A MAKER**,\n         // the call_pays_price of settlement_price/(MSSR-MCFR+MCFR) = settlement_price/MSSR should be used\n         // when determining the amount of collateral and debt that will removed from the debt position.\n         // The limit order will still be compensated based on the normal match price of settlement_price/(MSSR-MCFR)\n         // but the calculation from BSIP38 should use the call_pays_price which reflects that the call order\n         // will actually pay more collateral\n         // (it can be considered as a higher effective price when denominated in collateral / debt,\n         // or equivalently a lower effective price when denominated in debt / collateral).\n\n         // Therefore, the call_pays_price, WHEN THE CALL ORDER IS MAKER,\n         //                   = feed_price / MSSR reduces to\n         // feed_price / MSSR = (17 satoshi SMARTBIT / 400 satoshi CORE) * (1000 / 1500)\n         //                   =  (17 satoshi SMARTBIT / 400 satoshi CORE) * (10 / 15)\n         //                   =  17 satoshi SMARTBIT / 600 satoshi CORE\n\n         // Returning to the formula for the TCR amount to sell from the call order\n         // max_amount_to_sell = (debt * target_CR - collateral * feed_price) / (target_CR * call_pays_price - feed_price)\n         //\n         // = (2000000 satoshi SMARTBIT * [2200 / 1000] - 80000000 satoshi CORE * [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //   / ([2200 / 1000] *  [17 satoshi SMARTBIT / 600 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (2000000 satoshi SMARTBIT * [22 / 10] - 80000000 satoshi SMARTBIT * [17 / 400])\n         //   / ([22 / 10] *  [17 satoshi SMARTBIT / 600 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (200000 satoshi SMARTBIT * [22] - 200000 satoshi SMARTBIT * [17])\n         //   / ([22 / 10] *  [17 satoshi SMARTBIT / 600 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (200000 satoshi SMARTBIT * [22 - 17])\n         //   / ([22 / 10] *  [17 satoshi SMARTBIT / 600 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (200000 satoshi CORE * [5]) / ([22 / 10] *  [17 / 600] - [17 / 400])\n         //\n         // = (1000000 satoshi CORE) / ([22 / 10] *  [17 / 600] - [17 / 400])\n         //\n         // ~= (1000000 satoshi CORE) / (0.0198333333333) ~= 50420168.0757 satoshi CORE\n         //\n         // ~= rounded up to 50420169 satoshi CORE = 504.20169 CORE\n         // const asset expected_max_amount_to_sell = core.amount(50420169);\n         // match() is calculating 50420189 CORE\n\n         // Per BSIP38, the expected amount to cover from the call order\n         //\n         // max_debt_to_cover = max_amount_to_sell * match_price\n         //\n         // which is adjusted to\n         //\n         // max_debt_to_cover = max_amount_to_sell * call_pays_price\n\n         // Therefore the\n         //\n         // = (1000000 satoshi CORE) / ([22 / 10] *  [17 / 600] - [17 / 400]) * (17 satoshi SMARTBIT / 600 satoshi CORE)\n         //\n         // ~= 50420168.0757 satoshi CORE * (17 satoshi SMARTBIT / 600 satoshi CORE)\n         //\n         // ~= 1428571.42881 satoshi SMARTBIT rounded down to 1428571 satoshi SMARTBIT = 142.8571 SMARTBIT\n         // ~= 1428571.42881 satoshi SMARTBIT rounded up to 1428572 satoshi SMARTBIT = 142.8572 SMARTBIT\n         const asset expected_max_debt_to_cover = smartbit.amount(1428572);\n\n         // WHEN THE CALL ORDER IS MAKER, the match_price is settlement_price/(MSSR-MCFR)\n         // Payment to limit order = X/match_price = X*(MSSR-MCFR)/settlement_price\n         // = 1428572 satoshi SMARTBIT * (580 satoshi CORE / 17 satoshi SMARTBIT)\n         // = 48739515.2941 satoshi CORE rounded up to 48739516 satoshi CORE = 487.39516 CORE\n         // Margin call should exchange the filled debt (X) for X*(MSSR-MCFR)/settlement_price\n         const asset expected_payment_to_alice_core = core.amount(48739516);\n\n         // Caluclate the expected payment in collateral by the call order\n         // to fulfill the (complete or partial) filling of the margin call.\n         //\n         // The expected payment is not necessarily equal to BSIP38's max_amount_to_sell.\n         // It should be calculated base on the amount paid to the limit order (X), the settlement price,\n         // and the MSSR.\n         //\n         // Expected payment by call order = X*MSSR/settlement_price\n         // Expected payment by call order = 1428572 satoshi SMARTBIT * (600 satoshi CORE / 17 satoshi SMARTBIT)\n         // = 1428572 * 600 satoshi CORE / 17\n         // = 50420188.2353 satoshi CORE rounding up to 50420189 satoshi CORE = 504.20189 CORE\n         const asset expected_payment_from_bob_core = core.amount(50420189);\n\n         // The call order MUST ALSO pay the margin call fee\n         // Expected fee = payment by call order - payment to limit order\n         const asset expected_margin_call_fee = expected_payment_from_bob_core - expected_payment_to_alice_core;\n\n         // Check Alice's balances\n         BOOST_CHECK_EQUAL(get_balance(alice, smartbit), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice, core),\n                           alice_initial_core.amount.value + expected_payment_to_alice_core.amount.value);\n\n         // Alice's limit order should be open because of its partial filling\n         BOOST_CHECK(db.find(alice_order_id));\n\n         // Check Alice's limit order\n         // The amount of smart asset available for sale should be reduced by the amount paid to Bob's margin call\n         limit_order_object alice_limit_order = alice_order_id(db);\n         asset expected_alice_remaining_smart_for_sale = alice_debt_to_sell - expected_max_debt_to_cover;\n         BOOST_CHECK_EQUAL(alice_limit_order.amount_for_sale().amount.value,\n                           expected_alice_remaining_smart_for_sale.amount.value);\n         // Alice's limit order's price should be unchanged by the margin call\n         BOOST_CHECK(alice_limit_order.sell_price == alice_sell_op.get_price());\n\n         // Bob's debt position should be open because of its partial filling\n         BOOST_CHECK(db.find(bob_call_id));\n\n         // Check Bob's debt position\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value,\n                           bob_initial_smart.amount.value - expected_max_debt_to_cover.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value,\n                           bob_initial_core.amount.value - expected_payment_to_alice_core.amount.value -\n                           expected_margin_call_fee.amount.value);\n\n         // Check Bob's balances\n         // Bob should have no debt asset\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0);\n         // Bob should NOT have collected the balance of his collateral after the margin call\n         // because the debt position is still open\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 0);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                           expected_margin_call_fee.amount.value);\n\n         // Check the fee of the fill operations for Alice and Bob\n         generate_block(); // To trigger db_notify() and record pending operations into histories\n         graphene::app::history_api hist_api(app);\n         vector <operation_history_object> histories;\n         const int fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n         // Check Alice's history\n         histories = hist_api.get_account_history_operations(\n                 \"alice\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Alice's fill order for her limit order should have zero fee\n         fill_order_operation alice_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(alice_fill_op.fee == asset(0));\n         // Alice's fill order's fill price should equal the expected match price\n         BOOST_CHECK(~alice_fill_op.fill_price == expected_match_price);\n\n         // Check Bob's history\n         histories = hist_api.get_account_history_operations(\n                 \"bob\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Bob's fill order for his margin call should have a fee equal to the margin call fee\n         fill_order_operation bob_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(bob_fill_op.fee == expected_margin_call_fee);\n         // Bob's fill order's fill price should equal the expected match price\n         BOOST_CHECK(~bob_fill_op.fill_price == expected_match_price);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test a simple scenario of a Complete Fill of a Call Order as a Taker after HF\n    *\n    * 0. Advance to HF\n    * 1. Initialize actors and a smart asset called SMARTBIT\n    * 2. Publish feed\n    * 3. (Order 1: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT\n    * 4. (Order 2: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n    *     Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n    *     because his debt position is what will be tracked.\n    * 5. The feed price indicates that the collateral drops enough to trigger a margin call\n    *    **and** enough to be matched against Alice's limit order.\n    *    (Global settlement is not at risk because Bob's small order should be matched\n    *    and completely filled by Alice's large order).\n    *    Alice's limit order should be matched against Bob's \"activated\" call order.\n    *    **Alice should be charged as a maker, and Bob as a taker.**\n    *    Alice's limit order should be partially filled,\n    *    but Bob's order should be completely filled and removed from the book.\n    */\n   BOOST_AUTO_TEST_CASE(complete_fill_of_call_order_as_taker) {\n      try {\n         //////\n         // 0. Advance to activate hardfork\n         //////\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n\n         //////\n         // 1. Initialize actors and a smart asset called SMARTBIT\n         //////\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob)(charlie));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         // CORE asset exists by default\n         const asset_object &core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n         const int64_t CORE_UNIT = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n\n         // Create the SMARTBIT asset\n         const int16_t SMARTBIT_UNIT = 10000; // 10000 satoshi SMARTBIT in 1 SMARTBIT\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const uint16_t smartbit_margin_call_fee_ratio = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         // Define the margin call fee ratio\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 4, core_id,\n                         GRAPHENE_MAX_SHARE_SUPPLY, {}, smartbit_margin_call_fee_ratio);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object smartbit = get_asset(\"SMARTBIT\");\n         const asset_id_type smartbit_id = smartbit.get_id();\n         update_feed_producers(smartbit, {feedproducer_id});\n\n         // Initialize token balance of actors\n\n         // Alice should start with enough CORE to back 5000 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 4x\n         const price initial_feed_price =\n                 smartbit.amount(1) / core.amount(20); // 1 satoshi SMARTBIT for 20 satoshi CORE\n         const asset alice_initial_smart = smartbit.amount(500 * SMARTBIT_UNIT); // 5,000,000 satoshi SMARTBIT\n         const asset alice_initial_core = core.amount(\n                 4 * (alice_initial_smart * initial_feed_price).amount); // 400,000,000 satoshi CORE\n         transfer(committee_account, alice_id, alice_initial_core);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), alice_initial_core.amount.value);\n\n         // Bob should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 2x\n         const asset bob_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset bob_initial_core = core.amount(\n                 2 * (bob_initial_smart * initial_feed_price).amount); // 80,000,000 satoshi CORE\n         transfer(committee_account, bob_id, bob_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, core), 80000000);\n\n         // In Step 5, the feed price will be adjusted such that\n         // the initial CR of Bob's position (CR_0) drops to 1.7x = (17/10)x\n         // Want new price = 1.7 / CR_0 = (17/10) / CR_0\n         //\n         // Collateral ratios are defined as collateral / debt\n         // BitShares prices are conventionally defined as debt / collateral\n         // The new price can be expressed with the available codebase as\n         // = (17/10) * ~CR_0 = ~CR_0 * (17/10)\n         const price expected_bob_initial_cr =\n                 core.amount(2 * 20) / smartbit.amount(1); // 1 satoshi SMARTBIT for 40 satoshi CORE\n         const price intermediate_feed_price =\n                 ~expected_bob_initial_cr * ratio_type(17, 10); // Units of debt / collateral\n         // Reduces to (2000000 * 17) / (80000000 * 10) = (17) / (40 * 10) = 17 satoshi SMARTBIT / 400 satoshi CORE\n         BOOST_CHECK_EQUAL(intermediate_feed_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(intermediate_feed_price.quote.amount.value, 400); // satoshi CORE\n         BOOST_CHECK(intermediate_feed_price < initial_feed_price);\n\n\n         //////\n         // 2. Publish feed\n         //////\n         price_feed current_feed;\n         current_feed.settlement_price = initial_feed_price;\n         current_feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         current_feed.maximum_short_squeeze_ratio = 1500; // MSSR of 1.50x\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // 3. (Order 1: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT.\n         //////\n         // Alice borrows SMARTBIT\n         call_order_id_type alice_call_id = (*borrow(alice, alice_initial_smart, alice_initial_core)).get_id();\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 500 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n\n         // Alice offer to sell the SMARTBIT\n         // Create a \"large\" sell order at a \"high\" price of settlement_price * 1.1 = settlement_price * (11/10)\n         const price alice_order_price_implied = intermediate_feed_price * ratio_type(11, 10);\n         // = (17 satoshi SMARTBIT / 400 satoshi CORE) * (11/10)\n         // = 187 satoshi SMARTBIT / 4000 satoshi CORE\n         BOOST_CHECK_EQUAL(alice_order_price_implied.base.amount.value, 187); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(alice_order_price_implied.quote.amount.value, 4000); // satoshi CORE\n\n         const asset alice_debt_to_sell = smartbit.amount(get_balance(alice_id(db), smartbit_id(db)));\n         // multiply_and_round_up() handles inverting the price so that the output is in correct collateral units\n         const asset alice_collateral_to_buy = alice_debt_to_sell.multiply_and_round_up(alice_order_price_implied);\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice_id, alice_debt_to_sell,\n                                                                            alice_collateral_to_buy);\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         // asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Alice should have no balance\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n\n\n         //////\n         // 4. (Order 2: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n         //    Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n         //    because his debt position is what will be tracked.\n         //////\n         const asset bob_initial_debt_smart = bob_initial_smart;\n         const asset bob_initial_debt_collateral = bob_initial_core;\n         call_order_id_type bob_call_id = (*borrow(bob, bob_initial_debt_smart, bob_initial_debt_collateral)).get_id();\n\n         // Bobs's balances should reflect that CORE was used to create SMARTBIT\n         BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK(bob_initial_cr == expected_bob_initial_cr);\n         BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n         // Alice's balances should not have changed\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n\n         // Alice should not have been margin called\n         price alice_initial_cr = alice_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(alice_initial_cr.base.amount.value, 400000000); // Collateral of 400,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(alice_initial_cr.quote.amount.value, 5000000); // Debt of 5,000,000 satoshi SMARTBIT\n\n         //////\n         // Bob transfers his SMARTBIT to Charlie to clarify the accounting\n         //////\n         transfer(bob_id, charlie_id, bob_initial_smart);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0 * CORE_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, core_id), 0 * CORE_UNIT);\n\n\n         //////\n         // 5. The feed price indicates that the collateral drops enough to trigger a margin call\n         //    **and** enough to be matched against Alice's limit order.\n         //    (Global settlement is not at risk because Bob's small order should be matched\n         //    and completely filled by Alice's large order).\n         //    Alice's limit order should be matched against Bob's \"activated\" call order.\n         //    **Alice should be charged as a maker, and Bob as a taker.**\n         //    Alice's limit order should be partially filled,\n         //     but Bob's order should be completely filled and removed from the book.\n         //////\n         current_feed.settlement_price = intermediate_feed_price;\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         // Confirm the updated feed\n         BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n         // Confirm no global settlement\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled());\n\n\n         // The margin call of Bob's position should have closed the debt of bob_initial_smart\n         // Bob's margin call should been matched against Alice's limit order\n         // Bob's debt position should have paid collateral = bob_initial_smart / limit_order_price\n         // 200 SMARTBIT / (187 satoshi SMARTBIT / 4000 satoshi CORE)\n         // = 2,000,000 satoshi SMARTBIT / (187 satoshi SMARTBIT / 4000 satoshi CORE)\n         // = 2,000,000 satoshi SMARTBIT * (4000 satoshi CORE / 187 satoshi SMARTBIT)\n         // = 2,000,000 satoshi CORE / (4000 / 187)\n         // = 42,780,748.6631 satoshi CORE rounded up to 42,780,749 satoshi CORE\n         const asset expected_margin_call_from_bob_debt_core = core.amount(42780749);\n\n         // Bob's margin call fee, which is paid in collateral, should be charged as a taker\n         // The margin call fee debt = filled_debt * MCFR/(MSSR-MCFR) / limit_order_price\n         // 200 SMARTBIT * (50 / (1500 - 50)) / (187 satoshi SMARTBIT / 4000 satoshi CORE)\n         // = 2,000,000 satoshi SMARTBIT * (50 / 1450) / (187 satoshi SMARTBIT / 4000 satoshi CORE)\n         // = 2,000,000 satoshi CORE * (1 / 29) * (4000 / 187)\n         // = 1475198.22976 satoshi CORE rounded up to 1475199 satoshi CORE\n         const asset expected_margin_call_fee_from_bob_debt_core = core.amount(1475199);\n\n         // The balance of Bob's debt position\n         const asset expected_return_from_bob_debt_core = bob_initial_core\n                                                          - expected_margin_call_from_bob_debt_core\n                                                          - expected_margin_call_fee_from_bob_debt_core;\n\n         // Check Bob's debt position is closed\n         BOOST_CHECK(!db.find(bob_call_id));\n\n         // Check Bob's balances\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), expected_return_from_bob_debt_core.amount.value);\n\n         // Charlie's balances should not have changed\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, core_id), 0 * CORE_UNIT);\n\n         // Alice's balances should have changed because her limit order was partially filled by the margin call\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), expected_margin_call_from_bob_debt_core.amount.value);\n\n         // Check Alice's debt\n         // Alice's debt position should not be NOT closed\n         BOOST_CHECK(db.find(alice_call_id));\n         // Alice's debt should NOT have changed because its CR > MCR\n         alice_initial_cr = alice_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(alice_initial_cr.base.amount.value, 400000000); // Collateral of 400,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(alice_initial_cr.quote.amount.value, 5000000); // Debt of 5,000,000 satoshi SMARTBIT\n\n         // Check Alice's limit order\n         // The amount of smart asset available for sale should be reduced by the amount paid to Bob's margin call\n         limit_order_object alice_limit_order = alice_order_id(db);\n         asset expected_alice_remaining_smart_for_sale = alice_debt_to_sell - bob_initial_debt_smart;\n         asset expected_alice_remaining_core_to_receive =\n                 alice_collateral_to_buy - expected_margin_call_from_bob_debt_core;\n         BOOST_CHECK(alice_limit_order.amount_for_sale() == expected_alice_remaining_smart_for_sale);\n         BOOST_CHECK(alice_limit_order.amount_to_receive() == expected_alice_remaining_core_to_receive);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_fees.value, 0);\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                           expected_margin_call_fee_from_bob_debt_core.amount.value);\n\n         // Check the fee of the fill operations for Alice and Bob\n         generate_block(); // To trigger db_notify() and record pending operations into histories\n         graphene::app::history_api hist_api(app);\n         vector <operation_history_object> histories;\n         const int fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n         // Check Alice's history\n         histories = hist_api.get_account_history_operations(\n                 \"alice\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Alice's fill order for her limit order should have zero fee\n         fill_order_operation alice_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(alice_fill_op.fee == asset(0));\n         // Alice's fill order's fill price should equal the expected match price\n         // Alice's alice_order_price_implied differs slightly from alice_sell_op.get_price()\n         // due to rounding in this test while creating the parameters for the limit order\n         const price expected_match_price = alice_sell_op.get_price();\n         BOOST_CHECK(alice_fill_op.fill_price == expected_match_price);\n\n         // Check Bob's history\n         histories = hist_api.get_account_history_operations(\n                 \"bob\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Bob's fill order for his margin call should have a fee equal to the margin call fee\n         fill_order_operation bob_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(bob_fill_op.fee == expected_margin_call_fee_from_bob_debt_core);\n         // Bob's fill order's fill price should equal the expected match price\n         BOOST_CHECK(bob_fill_op.fill_price == expected_match_price);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test a scenario of a partial Filling of a Call Order as a Taker after HF\n    * where the partial filling is due to call order defining a target collateral ratio (TCR) (BSIP38)\n    *\n    * 0. Advance to HF\n    * 1. Initialize actors and a smart asset called SMARTBIT\n    * 2. Publish feed\n    * 3. (Order 1: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT\n    * 4. (Order 2: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n    *     Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n    *     because his debt position is what will be tracked.\n    * 5. The feed price indicates that the collateral drops enough to trigger a margin call\n    *    **and** enough to be matched against Alice's limit order.\n    *    Alice's limit order should be matched against Bob's \"activated\" call order.\n    *    **Alice should be charged as a maker, and Bob as a taker.**\n    *    Alice's limit order should be (partially or completely) filled,\n    *    but Bob's order will also only be partially filled because the TCR will sell just enough collateral\n    *    so that the remaining CR of the debt position >= TCR.\n    *    Bob's debt position should remain open.\n    */\n   BOOST_AUTO_TEST_CASE(target_cr_partial_fill_of_call_order_as_taker) {\n      try {\n         //////\n         // 0. Advance to activate hardfork\n         //////\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n\n         //////\n         // 1. Initialize actors and a smart asset called SMARTBIT\n         //////\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob)(charlie));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         // CORE asset exists by default\n         const asset_object &core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n         const int64_t CORE_UNIT = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n\n         // Create the SMARTBIT asset\n         const int16_t SMARTBIT_UNIT = 10000; // 10000 satoshi SMARTBIT in 1 SMARTBIT\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const uint16_t smartbit_margin_call_fee_ratio = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         // Define the margin call fee ratio\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 4, core_id,\n                         GRAPHENE_MAX_SHARE_SUPPLY, {}, smartbit_margin_call_fee_ratio);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object smartbit = get_asset(\"SMARTBIT\");\n         const asset_id_type smartbit_id = smartbit.get_id();\n         update_feed_producers(smartbit, {feedproducer_id});\n\n         // Initialize token balance of actors\n\n         // Alice should start with enough CORE to back 5000 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 4x\n         const price initial_feed_price =\n                 smartbit.amount(1) / core.amount(20); // 1 satoshi SMARTBIT for 20 satoshi CORE\n         const asset alice_initial_smart = smartbit.amount(500 * SMARTBIT_UNIT); // 5,000,000 satoshi SMARTBIT\n         const asset alice_initial_core = core.amount(\n                 4 * (alice_initial_smart * initial_feed_price).amount); // 400,000,000 satoshi CORE\n         transfer(committee_account, alice_id, alice_initial_core);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), alice_initial_core.amount.value);\n\n         // Bob should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 2x\n         const asset bob_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset bob_initial_core = core.amount(\n                 2 * (bob_initial_smart * initial_feed_price).amount); // 80,000,000 satoshi CORE\n         transfer(committee_account, bob_id, bob_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, core), 80000000);\n\n         // In Step 5, the feed price will be adjusted such that\n         // the initial CR of Bob's position (CR_0) drops to 1.7x = (17/10)x\n         // Want new price = 1.7 / CR_0 = (17/10) / CR_0\n         //\n         // Collateral ratios are defined as collateral / debt\n         // BitShares prices are conventionally defined as debt / collateral\n         // The new price can be expressed with the available codebase as\n         // = (17/10) * ~CR_0 = ~CR_0 * (17/10)\n         const price expected_bob_initial_cr =\n                 core.amount(2 * 20) / smartbit.amount(1); // 1 satoshi SMARTBIT for 40 satoshi CORE\n         const price intermediate_feed_price =\n                 ~expected_bob_initial_cr * ratio_type(17, 10); // Units of debt / collateral\n         // Reduces to (2000000 * 17) / (80000000 * 10) = (17) / (40 * 10) = 17 satoshi SMARTBIT / 400 satoshi CORE\n         BOOST_CHECK_EQUAL(intermediate_feed_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(intermediate_feed_price.quote.amount.value, 400); // satoshi CORE\n         BOOST_CHECK(intermediate_feed_price < initial_feed_price);\n\n\n         //////\n         // 2. Publish feed\n         //////\n         price_feed current_feed;\n         current_feed.settlement_price = initial_feed_price;\n         current_feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         current_feed.maximum_short_squeeze_ratio = 1500; // MSSR of 1.50x\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // 3. (Order 1: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT.\n         //////\n         // Alice borrows SMARTBIT\n         borrow(alice, alice_initial_smart, alice_initial_core);\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 500 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n\n         // Alice offer to sell the SMARTBIT\n         // Create a \"large\" sell order at a \"high\" price of settlement_price * 1.1 = settlement_price * (11/10)\n         const price alice_order_price_implied = intermediate_feed_price * ratio_type(11, 10);\n         // = (17 satoshi SMARTBIT / 400 satoshi CORE) * (11/10)\n         // = 187 satoshi SMARTBIT / 4000 satoshi CORE\n         BOOST_CHECK_EQUAL(alice_order_price_implied.base.amount.value, 187); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(alice_order_price_implied.quote.amount.value, 4000); // satoshi CORE\n\n         const asset alice_debt_to_sell = smartbit.amount(get_balance(alice_id(db), smartbit_id(db)));\n         // multiply_and_round_up() handles inverting the price so that the output is in correct collateral units\n         const asset alice_collateral_to_buy = alice_debt_to_sell.multiply_and_round_up(alice_order_price_implied);\n         //\n         // NOTE: The calculated limit order price is 5000000 satoshi SMARTBIT / 106951872 satoshi CORE\n         //\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice_id, alice_debt_to_sell,\n                                                                            alice_collateral_to_buy);\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         // asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Alice should have no balance\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n\n\n         //////\n         // 4. (Order 1: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n         //    Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n         //    because his debt position is what will be tracked.\n         //////\n         const uint16_t tcr = 2200; // Bob's target collateral ratio (TCR) 220% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core, tcr)).get_id();\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n         //////\n         // Bob transfers his SMARTBIT to Charlie to clarify the accounting\n         //////\n         transfer(bob_id, charlie_id, bob_initial_smart);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0 * CORE_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, core_id), 0 * CORE_UNIT);\n\n\n         //////\n         // 5. The feed price indicates that the collateral drops enough to trigger a margin call\n         //    **and** enough to be matched against Alice's limit order.\n         //    Alice's limit order should be matched against Bob's \"activated\" call order.\n         //    **Alice should be charged as a maker, and Bob as a taker.**\n         //    Alice's limit order should be (partially or completely) filled,\n         //    but Bob's order will also only be partially filled because the TCR will sell just enough collateral\n         //    so that the remaining CR of the debt position >= TCR.\n         //    Bob's debt position should remain open.\n         //////\n         current_feed.settlement_price = intermediate_feed_price;\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         // Confirm the updated feed\n         BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n         // Confirm no global settlement\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled());\n\n         // When a TCR is set for a call order, the ideal is to not sell all of the collateral\n         // but only enough collateral so that the remaining collateral and the remaining debt in the debt position\n         // has a resulting CR >= TCR.  The specifications are described in BSIP38.\n         //\n         // Per BSIP38, the expected amount to sell from the call order is\n         // max_amount_to_sell = (debt * target_CR - collateral * feed_price) / (target_CR * match_price - feed_price)\n         //\n         // HOWEVER, the match price that is used in this calculation\n         // NEEDS TO BE ADJUSTED to account for the extra MCFR > 0 that will be paid by the call order.\n         //\n         // Rather than using a match price of limit_order_price **AS A TAKER**,\n         // the call_pays_price of limit_order_price * (MSSR-MCFR) / MSSR should be used\n         // when determining the amount of collateral and debt that will removed from the debt position.\n         //\n         // The limit order will still be compensated based on the quoted match price of limit_order_price\n         // but the calculation from BSIP38 should use the call_pays_price which reflects that the call order\n         // will actually pay more collateral\n         // (it can be considered as a higher effective price when denominated in collateral / debt,\n         // or equivalently a lower effective price when denominated in debt / collateral).\n\n         // Therefore, the call_pays_price, WHEN THE CALL ORDER IS TAKER,\n         //                 = limit_order_price*(MSSR-MCFR)/MSSR reduces to\n         //\n         // call_pays_price = (5000000 satoshi SMARTBIT / 106951872 satoshi CORE) * ([1500-50] / 1500)\n         //                 = (5000000 satoshi SMARTBIT / 106951872 satoshi CORE) * (1450 / 1500)\n         //                 = (5000000 satoshi SMARTBIT / 106951872 satoshi CORE) * (29 / 30)\n         //                 = (500000 satoshi SMARTBIT / 106951872 satoshi CORE) * (29 / 3)\n         //                 = (14500000 satoshi SMARTBIT / 320855616 satoshi CORE)\n         //                 = (453125 satoshi SMARTBIT / 10026738 satoshi CORE)\n\n         // Returning to the formula for the TCR amount to sell from the call order\n         // max_amount_to_sell = (debt * target_CR - collateral * feed_price) / (target_CR * call_pays_price - feed_price)\n         //\n         // = (2000000 satoshi SMARTBIT * [2200 / 1000] - 80000000 satoshi CORE * [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //   / ([2200 / 1000] *  [453125 satoshi SMARTBIT / 10026738 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (2000000 satoshi SMARTBIT * [22 / 10] - 80000000 satoshi SMARTBIT * [17 / 400])\n         //   / ([22 / 10] *  [453125 satoshi SMARTBIT / 10026738 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (200000 satoshi SMARTBIT * [22] - 200000 satoshi SMARTBIT * [17])\n         //   / ([22 / 10] *  [453125 satoshi SMARTBIT / 10026738 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (200000 satoshi SMARTBIT * [22 - 17])\n         //   / ([22 / 10] *  [453125 satoshi SMARTBIT / 10026738 satoshi CORE] - [17 satoshi SMARTBIT / 400 satoshi CORE])\n         //\n         // = (200000 satoshi CORE * [5]) / ([22 / 10] *  [453125 / 10026738] - [17 / 400])\n         //\n         // = (1000000 satoshi CORE) / ([22 / 10] *  [453125 / 10026738] - [17 / 400])\n         //\n         // ~= (1000000 satoshi CORE) / (0.0569216663485) ~= 17568002.9117 satoshi CORE\n         //\n         // ~= rounded up to 17568003 satoshi CORE = 175.68003 CORE\n         // const asset expected_max_amount_to_sell = core.amount(17568003);\n         // match() is calculating ???? CORE\n\n         // Per BSIP38, the expected amount to cover from the call order\n         //\n         // max_debt_to_cover = max_amount_to_sell * match_price\n         //\n         // which is adjusted to\n         //\n         // max_debt_to_cover = max_amount_to_sell * call_pays_price\n\n         // Therefore the\n         //\n         // = (1000000 satoshi CORE) / ([22 / 10] *  [17 / 600] - [17 / 400])\n         //   * (453125 satoshi SMARTBIT / 10026738 satoshi CORE)\n         //\n         // ~= 17568002.9117 satoshi CORE * (453125 satoshi SMARTBIT / 10026738 satoshi CORE)\n         //\n         // ~= 793927.329044 satoshi SMARTBIT rounded down to 793927 satoshi SMARTBIT = 79.3927 SMARTBIT\n         // ~= 793927.329044 satoshi SMARTBIT rounded up to 793928 satoshi SMARTBIT = 79.3928 SMARTBIT\n         const asset expected_max_debt_to_cover = smartbit.amount(793928);\n\n\n         // WHEN THE CALL ORDER IS TAKER, the match_price is the limit_order price\n         // Payment to limit order = X/match_price = X/limit_order_price\n         // = 793928 satoshi SMARTBIT * (106951872 satoshi CORE / 5000000 satoshi SMARTBIT)\n         // = 16982417.1666 satoshi CORE rounded up to 16982418 satoshi CORE = 169.82418 CORE\n         // Margin call should exchange the filled debt (X) for X/limit_order_price\n         const asset expected_payment_to_alice_core = core.amount(16982418);\n\n         // Caluclate the expected payment in collateral by the call order\n         // to fulfill the (complete or partial) filling of the margin call.\n         //\n         // The expected payment is not necessarily equal to BSIP38's max_amount_to_sell.\n         // It should be calculated base on the amount paid to the limit order (X), the settlement price,\n         // and the MSSR.\n         //\n         // Expected payment by call order = X/fill_price\n         // Expected payment by call order = X/[settlement_price*(MSSR-MCFR)/MSSR]\n         // Expected payment by call order\n         // = 793928 satoshi SMARTBIT / (453125 satoshi SMARTBIT / 10026738 satoshi CORE)\n         // = 17568017.7586 satoshi CORE rounding up to 17568018 satoshi CORE = 175.68018 CORE\n         const asset expected_payment_from_bob_core = core.amount(17568018);\n\n         // The call order MUST ALSO pay the margin call fee\n         // Expected fee = payment by call order - payment to limit order\n         const asset expected_margin_call_fee = expected_payment_from_bob_core - expected_payment_to_alice_core;\n\n         // Check Alice's balances\n         BOOST_CHECK_EQUAL(get_balance(alice, smartbit), 0);\n         BOOST_CHECK_EQUAL(get_balance(alice, core), 0 + expected_payment_to_alice_core.amount.value);\n\n         // Alice's limit order should be open because of its partial filling\n         BOOST_CHECK(db.find(alice_order_id));\n\n         // Check Alice's limit order\n         // The amount of smart asset available for sale should be reduced by the amount paid to Bob's margin call\n         limit_order_object alice_limit_order = alice_order_id(db);\n         asset expected_alice_remaining_smart_for_sale = alice_debt_to_sell - expected_max_debt_to_cover;\n         BOOST_CHECK_EQUAL(alice_limit_order.amount_for_sale().amount.value,\n                           expected_alice_remaining_smart_for_sale.amount.value);\n         // Alice's limit order's price should be unchanged by the margin call\n         BOOST_CHECK(alice_limit_order.sell_price == alice_sell_op.get_price());\n\n         // Bob's debt position should be open because of its partial filling\n         BOOST_CHECK(db.find(bob_call_id));\n\n         // Check Bob's debt position\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value,\n                           bob_initial_smart.amount.value - expected_max_debt_to_cover.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value,\n                           bob_initial_core.amount.value - expected_payment_to_alice_core.amount.value -\n                           expected_margin_call_fee.amount.value);\n\n         // Bob's balances should not have changed because his debt position should remain open\n         // because the debt position is still open\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 0 * CORE_UNIT);\n\n         // Charlie's balances should not have changed\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, core_id), 0 * CORE_UNIT);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == 0);\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                           expected_margin_call_fee.amount.value);\n\n         // Check the fee of the fill operations for Alice and Bob\n         generate_block(); // To trigger db_notify() and record pending operations into histories\n         graphene::app::history_api hist_api(app);\n         vector <operation_history_object> histories;\n         const int fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n         // Check Alice's history\n         histories = hist_api.get_account_history_operations(\n                 \"alice\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Alice's fill order for her limit order should have zero fee\n         fill_order_operation alice_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(alice_fill_op.fee == asset(0));\n         // Alice's fill order's fill price should equal the expected match price\n         // Alice's alice_order_price_implied differs slightly from alice_sell_op.get_price()\n         // due to rounding in this test while creating the parameters for the limit order\n         const price expected_match_price = alice_sell_op.get_price();\n         BOOST_CHECK(alice_fill_op.fill_price == expected_match_price);\n\n         // Check Bob's history\n         histories = hist_api.get_account_history_operations(\n                 \"bob\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Bob's fill order for his margin call should have a fee equal to the margin call fee\n         fill_order_operation bob_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(bob_fill_op.fee == expected_margin_call_fee);\n         // Bob's fill order's fill price should equal the expected match price\n         BOOST_CHECK(bob_fill_op.fill_price == expected_match_price);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test a scenario of a Complete Fill of a Call Order as a Taker after HF\n    * where the matching to an existing limit order becomes possible\n    * after the MCFR is reduced and without any change to the feed price.\n    * This is made possible by the reduction of the MCFR changing the margin call order price.\n    *\n    * 0. Advance to HF\n    * 1. Initialize actors and a smart asset called SMARTBIT\n    * 2. Publish feed\n    * 3. (Order 1: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT\n    * 4. (Order 2: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n    *     Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n    *     because his debt position is what will be tracked.\n    * 5. The feed price indicates that the collateral drops enough to trigger a margin call\n    *    **but** the margin call order price (denominated in debt/collateral) is less than\n    *    than Alice's limit order price, resulting in no match.\n    * 6. The asset owner reduces the MCFR enough such that Alice's offer price SHOULD overlap\n    *    with the margin call order price.\n    *    Alice's limit order should be matched against Bob's \"activated\" call order.\n    *    **Alice should be charged as a maker, and Bob as a taker.**\n    *    Alice's limit order should be partially filled,\n    *    but Bob's order should be completely filled and removed from the book.\n    */\n   BOOST_AUTO_TEST_CASE(mcfr_reduction_triggers_matching_of_margin_call_order) {\n      try {\n         //////\n         // 0. Advance to activate hardfork\n         //////\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n\n\n         //////\n         // 1. Initialize actors and a smart asset called SMARTBIT\n         //////\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob)(charlie));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         // CORE asset exists by default\n         const asset_object &core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n         const int64_t CORE_UNIT = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE\n\n         // Create the SMARTBIT asset\n         const int16_t SMARTBIT_UNIT = 10000; // 10000 satoshi SMARTBIT in 1 SMARTBIT\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const uint16_t initial_mcfr = 400; // 40% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         const uint16_t final_mcfr = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         // Define the margin call fee ratio\n         create_bitasset(\"SMARTBIT\", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 4, core_id,\n                         GRAPHENE_MAX_SHARE_SUPPLY, {}, initial_mcfr);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object smartbit = get_asset(\"SMARTBIT\");\n         const asset_id_type smartbit_id = smartbit.get_id();\n         update_feed_producers(smartbit, {feedproducer_id});\n\n         // Initialize token balance of actors\n\n         // Alice should start with enough CORE to back 5000 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 4x\n         const price initial_feed_price =\n                 smartbit.amount(1) / core.amount(20); // 1 satoshi SMARTBIT for 20 satoshi CORE\n         const asset alice_initial_smart = smartbit.amount(500 * SMARTBIT_UNIT); // 5,000,000 satoshi SMARTBIT\n         const asset alice_initial_core = core.amount(\n                 4 * (alice_initial_smart * initial_feed_price).amount); // 400,000,000 satoshi CORE\n         transfer(committee_account, alice_id, alice_initial_core);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), alice_initial_core.amount.value);\n\n         // Bob should start with enough CORE to back 200 SMARTBIT subject to\n         // (a) to an initial price feed of 1 satoshi SMARTBIT for 20 satoshi CORE\n         // = 0.0001 SMARTBIT for 0.00020 CORE = 1 SMARTBIT for 2 CORE\n         // (b) an initial collateral ratio of 2x\n         const asset bob_initial_smart = smartbit.amount(200 * SMARTBIT_UNIT); // 2,000,000 satoshi SMARTBIT\n         const asset bob_initial_core = core.amount(\n                 2 * (bob_initial_smart * initial_feed_price).amount); // 80,000,000 satoshi CORE\n         transfer(committee_account, bob_id, bob_initial_core);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, core), 80000000);\n\n         // In Step 5, the feed price will be adjusted such that\n         // the initial CR of Bob's position (CR_0) drops to 1.7x = (17/10)x\n         // Want new price = 1.7 / CR_0 = (17/10) / CR_0\n         //\n         // Collateral ratios are defined as collateral / debt\n         // BitShares prices are conventionally defined as debt / collateral\n         // The new price can be expressed with the available codebase as\n         // = (17/10) * ~CR_0 = ~CR_0 * (17/10)\n         const price expected_bob_initial_cr =\n                 core.amount(2 * 20) / smartbit.amount(1); // 1 satoshi SMARTBIT for 40 satoshi CORE\n         const price intermediate_feed_price =\n                 ~expected_bob_initial_cr * ratio_type(17, 10); // Units of debt / collateral\n         // Reduces to (2000000 * 17) / (80000000 * 10) = (17) / (40 * 10) = 17 satoshi SMARTBIT / 400 satoshi CORE\n         BOOST_CHECK_EQUAL(intermediate_feed_price.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(intermediate_feed_price.quote.amount.value, 400); // satoshi CORE\n         BOOST_CHECK(intermediate_feed_price < initial_feed_price);\n\n         // Pre-calculate the planned initial margin call order price (MCOP)\n         const uint16_t mssr = 1500;\n         const uint16_t initial_ratio_numerator = mssr - initial_mcfr;\n         BOOST_REQUIRE_EQUAL(initial_ratio_numerator, 1100);\n         const price planned_initial_mcop = intermediate_feed_price * ratio_type(GRAPHENE_COLLATERAL_RATIO_DENOM,\n                                                                                 initial_ratio_numerator);\n         // The initial MCOP should = 17 satoshi SMARTBIT / 400 satoshi CORE / (1100 / 1000)\n         //                         = 17 satoshi SMARTBIT / 400 satoshi CORE * (1000 / 1100)\n         //                         = 17 satoshi SMARTBIT / 4 satoshi CORE * (10 / 1100)\n         //                         = 17 satoshi SMARTBIT / 4 satoshi CORE * (1 / 110)\n         //                         = 17 satoshi SMARTBIT / 440 satoshi CORE\n         //                        ~= 0.0386 satoshi SMARTBIT / satoshi CORE\n         BOOST_CHECK_EQUAL(planned_initial_mcop.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(planned_initial_mcop.quote.amount.value, 440); // satoshi CORE\n\n         // Pre-calculate the planned final margin call order price (MCOP)\n         const uint16_t final_ratio_numerator = mssr - final_mcfr;\n         BOOST_REQUIRE_EQUAL(final_ratio_numerator, 1450);\n         const price planned_final_mcop = intermediate_feed_price * ratio_type(GRAPHENE_COLLATERAL_RATIO_DENOM,\n                                                                               final_ratio_numerator);\n         // The final MCOP should   = 17 satoshi SMARTBIT / 400 satoshi CORE / (1450 / 1000)\n         //                         = 17 satoshi SMARTBIT / 400 satoshi CORE * (1000 / 1450)\n         //                         = 17 satoshi SMARTBIT / 4 satoshi CORE * (10 / 1450)\n         //                         = 17 satoshi SMARTBIT / 4 satoshi CORE * (1 / 145)\n         //                         = 17 satoshi SMARTBIT / 580 satoshi CORE\n         //                        ~= 0.0293 satoshi SMARTBIT / satoshi CORE\n         BOOST_CHECK_EQUAL(planned_final_mcop.base.amount.value, 17); // satoshi SMARTBIT\n         BOOST_CHECK_EQUAL(planned_final_mcop.quote.amount.value, 580); // satoshi CORE\n\n\n         //////\n         // 2. Publish feed\n         //////\n         price_feed current_feed;\n         current_feed.settlement_price = initial_feed_price;\n         current_feed.maintenance_collateral_ratio = 1750; // MCR of 1.75x\n         current_feed.maximum_short_squeeze_ratio = mssr; // MSSR of 1.50x\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // 3. (Order 1: Limit order) Alice places a **\"large\"** limit order to sell SMARTBIT.\n         //////\n         // Alice borrows SMARTBIT\n         call_order_id_type alice_call_id = (*borrow(alice, alice_initial_smart, alice_initial_core)).get_id();\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 500 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n\n         // Alice offer to sell the SMARTBIT\n         const asset alice_debt_to_sell = smartbit.amount(500 * SMARTBIT_UNIT);\n         const asset alice_collateral_to_buy = core.amount(1500 * CORE_UNIT); // 150,000,000 satoshi CORE\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice_id, alice_debt_to_sell,\n                                                                            alice_collateral_to_buy);\n\n         // Check the new price relative to the planned initial and final MCOP\n         // The implied resulting price = 5,000,000 satoshi SMARTBIT / 150,000,000 satoshi CORE\n         //                             = 1 satoshi SMARTBIT / 30 satoshi CORE\n         //                            ~= 0.033 satoshi SMARTBIT / satoshi CORE\n         const price alice_order_price_implied = price(smartbit.amount(1), core.amount(30));\n         BOOST_REQUIRE(alice_sell_op.get_price() == alice_order_price_implied);\n         // Alice's offer price should be less than the intermediate MCOP\n         BOOST_REQUIRE(alice_sell_op.get_price() < planned_initial_mcop);\n         // Alice's offer price should be more than the final MCOP\n         BOOST_REQUIRE(alice_sell_op.get_price() > planned_final_mcop);\n\n         // Submit the limit order\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         // asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Alice should have no balance\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n\n\n\n         //////\n         // 4. (Order 2: Call order) Bob borrows a **\"small\"** amount of SMARTBIT into existence.\n         //    Bob retains the asset in his own balances, or transfers it, or sells it is not critical\n         //    because his debt position is what will be tracked.\n         //////\n         const asset bob_initial_debt_smart = bob_initial_smart;\n         const asset bob_initial_debt_collateral = bob_initial_core;\n         call_order_id_type bob_call_id = (*borrow(bob, bob_initial_debt_smart, bob_initial_debt_collateral)).get_id();\n\n         // Bobs's balances should reflect that CORE was used to create SMARTBIT\n         BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0);\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement\n         const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK(bob_initial_cr == expected_bob_initial_cr);\n         BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT\n\n         // Alice's balances should not have changed\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT);\n\n         // Alice should not have been margin called\n         price alice_initial_cr = alice_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(alice_initial_cr.base.amount.value, 400000000); // Collateral of 400,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(alice_initial_cr.quote.amount.value, 5000000); // Debt of 5,000,000 satoshi SMARTBIT\n\n         //////\n         // Bob transfers his SMARTBIT to Charlie to clarify the accounting\n         //////\n         transfer(bob_id, charlie_id, bob_initial_smart);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0 * CORE_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, core_id), 0 * CORE_UNIT);\n\n\n         //////\n         // 5. The feed price indicates that the collateral drops enough to trigger a margin call\n         //    **but** the margin call order price (denominated in debt/collateral) is less than\n         //    than Alice's limit order price, resulting in no match.\n         //////\n         current_feed.settlement_price = intermediate_feed_price;\n         publish_feed(smartbit, feedproducer_id(db), current_feed);\n         // Confirm the updated feed\n         BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n         // Confirm no global settlement\n         BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled());\n         // Verify the margin call order price is as planned\n         BOOST_CHECK(smartbit_id(db).bitasset_data(db).current_feed.margin_call_order_price(initial_mcfr)\n                     == planned_initial_mcop);\n\n         // Alice's limit order should be open\n         BOOST_CHECK(db.find(alice_order_id));\n\n         // Alice's limit order should not be affected\n         BOOST_CHECK_EQUAL(alice_order_id(db).amount_for_sale().amount.value,\n                           alice_debt_to_sell.amount.value);\n\n         // Bob's debt position should be open\n         BOOST_CHECK(db.find(bob_call_id));\n\n         // Bob's debt to the blockchain should not have changed\n         BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value);\n         BOOST_CHECK_EQUAL(bob_call_id(db).collateral.value, bob_initial_core.amount.value);\n\n         // Bob's balances should not have changed\n         BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0 * CORE_UNIT);\n\n\n         //////\n         // 6. The asset owner reduces the MCFR enough such that Alice's offer price SHOULD overlap\n         //    with the margin call order price.\n         //    Alice's limit order should be matched against Bob's \"activated\" call order.\n         //    **Alice should be charged as a maker, and Bob as a taker.**\n         //    Alice's limit order should be partially filled,\n         //     but Bob's order should be completely filled and removed from the book.\n         //////\n         asset_update_bitasset_operation uop;\n         uop.issuer = smartissuer_id;\n         uop.asset_to_update = smartbit_id;\n         uop.new_options = smartbit_id(db).bitasset_data(db).options;\n         uop.new_options.extensions.value.margin_call_fee_ratio = final_mcfr;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx);\n\n         // Check MCFR is updated\n         BOOST_CHECK(smartbit_id(db).bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n         BOOST_CHECK_EQUAL(*smartbit_id(db).bitasset_data(db).options.extensions.value.margin_call_fee_ratio,\n                           final_mcfr);\n\n         // Verify the margin call order price is as planned\n         BOOST_CHECK(smartbit_id(db).bitasset_data(db).current_feed.margin_call_order_price(final_mcfr)\n                     == planned_final_mcop);\n\n         //////\n         // Bob's margin call should have been matched with Alice's limit order\n         //////\n\n         // The margin call of Bob's position should have closed the debt of bob_initial_smart\n         // Bob's margin call should been matched against Alice's limit order\n         // Bob's debt position should have paid collateral = bob_initial_smart / limit_order_price\n         // 200 SMARTBIT / (1 satoshi SMARTBIT / 30 satoshi CORE)\n         // = 2,000,000 satoshi SMARTBIT / (1 satoshi SMARTBIT / 30 satoshi CORE)\n         // = 2,000,000 satoshi CORE / (1 / 30)\n         // = 60,000,000 satoshi CORE\n         const asset expected_margin_call_from_bob_debt_core = core.amount(60000000);\n\n         // Bob's margin call fee, which is paid in collateral, should be charged as a taker\n         // The margin call fee debt = filled_debt * MCFR/(MSSR-MCFR) / limit_order_price\n         // 200 SMARTBIT * (50 / (1500 - 50)) / (1 satoshi SMARTBIT / 30 satoshi CORE)\n         // = 2,000,000 satoshi SMARTBIT * (50 / 1450) / (1 satoshi SMARTBIT / 30 satoshi CORE)\n         // = 2,000,000 satoshi CORE * (1 / 29) * (30 / 1)\n         // = 2068965.51724 satoshi CORE rounded up to 2068966 satoshi CORE\n         const asset expected_margin_call_fee_from_bob_debt_core = core.amount(2068966);\n\n         // The balance of Bob's debt position\n         const asset expected_return_from_bob_debt_core = bob_initial_core\n                                                          - expected_margin_call_from_bob_debt_core\n                                                          - expected_margin_call_fee_from_bob_debt_core;\n\n         // Check Bob's debt position is closed\n         BOOST_CHECK(!db.find(bob_call_id));\n\n         // Check Bob's balances\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), expected_return_from_bob_debt_core.amount.value);\n\n         // Charlie's balances should not have changed\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, smartbit_id), 200 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(charlie_id, core_id), 0 * CORE_UNIT);\n\n         // Alice's balances should have changed because her limit order was partially filled by the margin call\n         BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 0 * SMARTBIT_UNIT);\n         BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), expected_margin_call_from_bob_debt_core.amount.value);\n\n         // Check Alice's debt\n         // Alice's debt position should not be NOT closed\n         BOOST_CHECK(db.find(alice_call_id));\n         // Alice's debt should NOT have changed because its CR > MCR\n         alice_initial_cr = alice_call_id(db).collateralization(); // Units of collateral / debt\n         BOOST_CHECK_EQUAL(alice_initial_cr.base.amount.value, 400000000); // Collateral of 400,000,000 satoshi CORE\n         BOOST_CHECK_EQUAL(alice_initial_cr.quote.amount.value, 5000000); // Debt of 5,000,000 satoshi SMARTBIT\n\n         // Check Alice's limit order\n         // The amount of smart asset available for sale should be reduced by the amount paid to Bob's margin call\n         limit_order_object alice_limit_order = alice_order_id(db);\n         asset expected_alice_remaining_smart_for_sale = alice_debt_to_sell - bob_initial_debt_smart;\n         asset expected_alice_remaining_core_to_receive =\n                 alice_collateral_to_buy - expected_margin_call_from_bob_debt_core;\n         BOOST_CHECK(alice_limit_order.amount_for_sale() == expected_alice_remaining_smart_for_sale);\n         BOOST_CHECK(alice_limit_order.amount_to_receive() == expected_alice_remaining_core_to_receive);\n\n         // Check the asset owner's accumulated asset fees\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_fees.value, 0);\n         BOOST_CHECK_EQUAL(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                           expected_margin_call_fee_from_bob_debt_core.amount.value);\n\n         // Check the fee of the fill operations for Alice and Bob\n         generate_block(); // To trigger db_notify() and record pending operations into histories\n         graphene::app::history_api hist_api(app);\n         vector<operation_history_object> histories;\n         const int fill_order_op_id = operation::tag<fill_order_operation>::value;\n\n         // Check Alice's history\n         histories = hist_api.get_account_history_operations(\n                 \"alice\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Alice's fill order for her limit order should have zero fee\n         fill_order_operation alice_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(alice_fill_op.fee == asset(0));\n         // Alice's fill order's fill price should equal the expected match price\n         // Alice's alice_order_price_implied differs slightly from alice_sell_op.get_price()\n         // due to rounding in this test while creating the parameters for the limit order\n         const price expected_match_price = alice_sell_op.get_price();\n         BOOST_CHECK(alice_fill_op.fill_price == expected_match_price);\n\n         // Check Bob's history\n         histories = hist_api.get_account_history_operations(\n                 \"bob\", fill_order_op_id, operation_history_id_type(), operation_history_id_type(), 100);\n         // There should be one fill order operation\n         BOOST_CHECK_EQUAL(histories.size(), 1);\n         // Bob's fill order for his margin call should have a fee equal to the margin call fee\n         fill_order_operation bob_fill_op = histories.front().op.get<fill_order_operation>();\n         BOOST_CHECK(bob_fill_op.fee == expected_margin_call_fee_from_bob_debt_core);\n         // Bob's fill order's fill price should equal the expected match price\n         BOOST_CHECK(bob_fill_op.fill_price == expected_match_price);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test the ability to create and update assets with a margin call fee ratio (MCFR) before HARDFORK_CORE_BSIP74_TIME\n    *\n    *\n    * Before HARDFORK_CORE_BSIP74_TIME\n    *\n    * 1. Asset owner fails to create the smart coin called USDBIT with a MCFR\n    * 2. Asset owner fails to create the smart coin called USDBIT with a MCFR in a proposal\n    * 3. Asset owner succeeds to create the smart coin called USDBIT without a MCFR\n    *\n    * 4. Asset owner fails to update the smart coin with a MCFR\n    * 5. Asset owner fails to update the smart coin with a MCFR in a proposal\n    *\n    *\n    * 6. Activate HARDFORK_CORE_BSIP74_TIME\n    *\n    *\n    * After HARDFORK_CORE_BSIP74_TIME\n    *\n    * 7. Asset owner succeeds to create the smart coin called CNYBIT with a MCFR\n    * 8. Asset owner succeeds to create the smart coin called RUBBIT with a MCFR in a proposal\n    *\n    * 9. Asset owner succeeds to update the smart coin called CNYBIT with a MCFR\n    * 10. Asset owner succeeds to update the smart coin called RUBBIT with a MCFR in a proposal\n    *\n    * 11. Asset owner succeeds to create the smart coin called YENBIT without a MCFR\n    * 12. Asset owner succeeds to update the smart coin called RUBBIT without a MCFR in a proposal\n    */\n   BOOST_AUTO_TEST_CASE(prevention_before_hardfork_test) {\n      try {\n         ///////\n         // Initialize the scenario\n         ///////\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Create actors\n         ACTORS((assetowner));\n\n         // CORE asset exists by default\n         asset_object core = asset_id_type()(db);\n         const asset_id_type core_id = core.get_id();\n\n         // Fund actors\n         uint64_t initial_balance_core = 10000000;\n         transfer(committee_account, assetowner_id, asset(initial_balance_core));\n\n         // Confirm before hardfork activation\n         BOOST_CHECK(db.head_block_time() < HARDFORK_CORE_BSIP74_TIME);\n\n\n         ///////\n         // 1. Asset owner fails to create the smart coin called bitUSD with a MCFR\n         ///////\n         const uint16_t market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const optional<uint16_t> icr_opt = {}; // Initial collateral ratio\n         const uint16_t mcfr_5 = 50; // 5% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         optional<uint16_t> mcfr_opt = mcfr_5;\n\n         // Attempt to create the smart asset with a MCFR\n         // The attempt should fail because it is before HARDFORK_CORE_BSIP74_TIME\n         {\n            const asset_create_operation create_op = make_bitasset(\"USDBIT\", assetowner_id, market_fee_percent,\n                                                                   charge_market_fee, 4, core_id,\n                                                                   GRAPHENE_MAX_SHARE_SUPPLY, icr_opt, mcfr_opt);\n            trx.clear();\n            trx.operations.push_back(create_op);\n            sign(trx, assetowner_private_key);\n            REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP74\");\n         }\n\n         ///////\n         // 2. Asset owner fails to create the smart coin called bitUSD with a MCFR in a proposal\n         ///////\n         {\n            const asset_create_operation create_op = make_bitasset(\"USDBIT\", assetowner_id, market_fee_percent,\n                                                                   charge_market_fee, 4, core_id,\n                                                                   GRAPHENE_MAX_SHARE_SUPPLY, icr_opt, mcfr_opt);\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(create_op);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP74\");\n         }\n\n\n         ///////\n         // 3. Asset owner succeeds to create the smart coin called bitUSD without a MCFR\n         ///////\n         const optional<uint16_t> mcfr_null_opt = {};\n         {\n            const asset_create_operation create_op = make_bitasset(\"USDBIT\", assetowner_id, market_fee_percent,\n                                                                   charge_market_fee, 4, core_id,\n                                                                   GRAPHENE_MAX_SHARE_SUPPLY, icr_opt, mcfr_null_opt);\n\n            trx.clear();\n            trx.operations.push_back(create_op);\n            sign(trx, assetowner_private_key);\n            PUSH_TX(db, trx); // No exception should be thrown\n         }\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const asset_object &bitusd = get_asset(\"USDBIT\");\n         core = core_id(db);\n\n         // The force MCFR should not be set\n         BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n\n\n         ///////\n         // 4. Asset owner fails to update the smart coin with a MCFR\n         ///////\n         const uint16_t mcfr_3 = 30; // 3% MCFR (BSIP74)\n         asset_update_bitasset_operation uop;\n         uop.issuer = assetowner_id;\n         uop.asset_to_update = bitusd.get_id();\n         uop.new_options = bitusd.bitasset_data(db).options;\n         uop.new_options.extensions.value.margin_call_fee_ratio = mcfr_3;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, assetowner_private_key);\n         REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP74\");\n\n         // The MCFR should not be set\n         BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n\n\n         ///////\n         // 5. Asset owner fails to update the smart coin with a MCFR in a proposal\n         ///////\n         {\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), \"cannot be set before Hardfork BSIP74\");\n\n            // The MCFR should not be set\n            BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n         }\n\n\n         ///////\n         // 6. Activate HARDFORK_CORE_BSIP74_TIME\n         ///////\n         BOOST_CHECK(db.head_block_time() < HARDFORK_CORE_BSIP74_TIME); // Confirm still before hardfork activation\n         BOOST_TEST_MESSAGE(\"Advancing past Hardfork BSIP74\");\n         generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n\n         ///////\n         // 7. Asset owner succeeds to create the smart coin called CNYBIT with a MCFR\n         ///////\n         {\n            mcfr_opt = mcfr_3;\n            const asset_create_operation create_op = make_bitasset(\"CNYBIT\", assetowner_id, market_fee_percent,\n                                                                   charge_market_fee, 4, core_id,\n                                                                   GRAPHENE_MAX_SHARE_SUPPLY, icr_opt, mcfr_opt);\n\n            trx.clear();\n            trx.operations.push_back(create_op);\n            sign(trx, assetowner_private_key);\n            PUSH_TX(db, trx); // No exception should be thrown\n         }\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bitcny = get_asset(\"CNYBIT\");\n\n         // The MCFR should be set\n         BOOST_CHECK(bitcny.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n         BOOST_CHECK_EQUAL(*bitcny.bitasset_data(db).options.extensions.value.margin_call_fee_ratio, mcfr_3);\n\n\n         ///////\n         // 8. Asset owner succeeds to create the smart coin called RUBBIT with a MCFR in a proposal\n         ///////\n         const uint16_t mcfr_1 = 10; // 1% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM\n         {\n            // Create the proposal\n            mcfr_opt = mcfr_1;\n            const asset_create_operation create_op = make_bitasset(\"RUBBIT\", assetowner_id, market_fee_percent,\n                                                                   charge_market_fee, 4, core_id,\n                                                                   GRAPHENE_MAX_SHARE_SUPPLY, icr_opt, mcfr_opt);\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(create_op);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            processed_transaction processed = PUSH_TX(db, trx);\n\n\n            // Approve the proposal\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = assetowner_id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(assetowner_id);\n            trx.clear();\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, assetowner_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n\n            // Advance to the activation of the proposal\n            generate_blocks(cop.expiration_time);\n            set_expiration(db, trx);\n         }\n         const auto &bitrub = get_asset(\"RUBBIT\");\n\n         // The MCFR should be set\n         BOOST_CHECK(bitrub.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n         BOOST_CHECK_EQUAL(*bitrub.bitasset_data(db).options.extensions.value.margin_call_fee_ratio, mcfr_1);\n\n\n         ///////\n         // 9. Asset owner succeeds to update the smart coin called CNYBIT with a MCFR\n         ///////\n         uop = asset_update_bitasset_operation();\n         uop.issuer = assetowner_id;\n         uop.asset_to_update = bitcny.get_id();\n         uop.new_options = bitcny.bitasset_data(db).options;\n         uop.new_options.extensions.value.margin_call_fee_ratio = mcfr_5;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, assetowner_private_key);\n         PUSH_TX(db, trx);\n\n         // The MCFR should be set\n         BOOST_CHECK(bitcny.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n         BOOST_CHECK_EQUAL(*bitcny.bitasset_data(db).options.extensions.value.margin_call_fee_ratio, mcfr_5);\n\n\n         ///////\n         // 10. Asset owner succeeds to update the smart coin called RUBBIT with a MCFR in a proposal\n         ///////\n         {\n            // Create the proposal\n            uop = asset_update_bitasset_operation();\n            uop.issuer = assetowner_id;\n            uop.asset_to_update = bitrub.get_id();\n            uop.new_options = bitrub.bitasset_data(db).options;\n            uop.new_options.extensions.value.margin_call_fee_ratio = mcfr_5;\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            processed_transaction processed = PUSH_TX(db, trx);\n\n\n            // Approve the proposal\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = assetowner_id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(assetowner_id);\n            trx.clear();\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, assetowner_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n            // Advance to the activation of the proposal\n            generate_blocks(cop.expiration_time);\n            set_expiration(db, trx);\n         }\n\n         // The MCFR should be set\n         BOOST_CHECK(bitrub.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n         BOOST_CHECK_EQUAL(*bitrub.bitasset_data(db).options.extensions.value.margin_call_fee_ratio, mcfr_5);\n\n\n         ///////\n         // 11. Asset owner succeeds to create the smart coin called YENBIT without a MCFR\n         ///////\n         {\n            const asset_create_operation create_op = make_bitasset(\"YENBIT\", assetowner_id, market_fee_percent,\n                                                                   charge_market_fee, 4, core_id,\n                                                                   GRAPHENE_MAX_SHARE_SUPPLY, icr_opt, mcfr_null_opt);\n\n            trx.clear();\n            trx.operations.push_back(create_op);\n            sign(trx, assetowner_private_key);\n            PUSH_TX(db, trx); // No exception should be thrown\n         }\n\n         generate_block();\n         set_expiration(db, trx);\n         trx.clear();\n\n         const auto &bityen = get_asset(\"YENBIT\");\n\n         // The MCFR should be set\n         BOOST_CHECK(!bityen.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n\n\n         ///////\n         // 12. Asset owner succeeds to update the smart coin called RUBBIT without a MCFR in a proposal\n         ///////\n         {\n            // Create the proposal\n            uop = asset_update_bitasset_operation();\n            uop.issuer = assetowner_id;\n            uop.asset_to_update = bitrub.get_id();\n            uop.new_options = bitrub.bitasset_data(db).options;\n            uop.new_options.extensions.value.margin_call_fee_ratio = mcfr_null_opt;\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.clear();\n            trx.operations.push_back(cop);\n            // sign(trx, assetowner_private_key);\n            processed_transaction processed = PUSH_TX(db, trx);\n\n\n            // Approve the proposal\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = assetowner_id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(assetowner_id);\n            trx.clear();\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, assetowner_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n            // Advance to the activation of the proposal\n            generate_blocks(cop.expiration_time);\n            set_expiration(db, trx);\n         }\n\n         // The MCFR should NOT be set\n         BOOST_CHECK(!bitrub.bitasset_data(db).options.extensions.value.margin_call_fee_ratio.valid());\n\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/market_fee_sharing_tests.cpp",
    "content": "#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/app/database_api.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nnamespace fc\n{\n   template<typename Ch, typename T>\n   std::basic_ostream<Ch>& operator<<(std::basic_ostream<Ch>& os, safe<T> const& sf)\n   {\n      os << sf.value;\n      return os;\n   }\n}\n\nstruct reward_database_fixture : database_fixture\n{\n   using whitelist_market_fee_sharing_t = fc::optional<flat_set<account_id_type>>;\n\n   reward_database_fixture()\n      : database_fixture()\n   {\n   }\n\n   void update_asset( const account_id_type& issuer_id,\n                      const fc::ecc::private_key& private_key,\n                      const asset_id_type& asset_id,\n                      uint16_t reward_percent,\n                      const whitelist_market_fee_sharing_t &whitelist_market_fee_sharing = {},\n                      const flat_set<account_id_type> &blacklist = flat_set<account_id_type>())\n   {\n      asset_update_operation op;\n      op.issuer = issuer_id;\n      op.asset_to_update = asset_id;\n      op.new_options = asset_id(db).options;\n      op.new_options.extensions.value.reward_percent = reward_percent;\n      op.new_options.extensions.value.whitelist_market_fee_sharing = whitelist_market_fee_sharing;\n      op.new_options.blacklist_authorities = blacklist;\n\n      signed_transaction tx;\n      tx.operations.push_back( op );\n      db.current_fee_schedule().set_fee( tx.operations.back() );\n      set_expiration( db, tx );\n      sign( tx, private_key );\n      PUSH_TX( db, tx );\n   }\n\n   void asset_update_blacklist_authority(const account_id_type& issuer_id,\n                                         const asset_id_type& asset_id,\n                                         const account_id_type& authority_account_id,\n                                         const fc::ecc::private_key& issuer_private_key)\n   {\n      asset_update_operation uop;\n      uop.issuer = issuer_id;\n      uop.asset_to_update = asset_id;\n      uop.new_options = asset_id(db).options;\n      uop.new_options.blacklist_authorities.insert(authority_account_id);\n\n      signed_transaction tx;\n      tx.operations.push_back( uop );\n      db.current_fee_schedule().set_fee( tx.operations.back() );\n      set_expiration( db, tx );\n      sign( tx, issuer_private_key );\n      PUSH_TX( db, tx );\n   }\n\n   void add_account_to_blacklist(const account_id_type& authorizing_account_id,\n                                 const account_id_type& blacklisted_account_id,\n                                 const fc::ecc::private_key& authorizing_account_private_key)\n   {\n      account_whitelist_operation wop;\n      wop.authorizing_account = authorizing_account_id;\n      wop.account_to_list = blacklisted_account_id;\n      wop.new_listing = account_whitelist_operation::black_listed;\n\n      signed_transaction tx;\n      tx.operations.push_back( wop );\n      db.current_fee_schedule().set_fee( tx.operations.back() );\n      set_expiration( db, tx );\n      sign( tx, authorizing_account_private_key );\n      PUSH_TX( db, tx);\n   }\n\n   void generate_blocks_past_hf1774()\n   {\n      generate_blocks( HARDFORK_1774_TIME );\n      generate_block();\n      set_expiration(db, trx);\n   }\n\n   void generate_blocks_past_hf1800()\n   {\n      database_fixture::generate_blocks( HARDFORK_CORE_1800_TIME );\n      database_fixture::generate_block();\n      set_expiration(db, trx);\n   }\n\n   asset core_asset(int64_t x )\n   {\n       return asset( x*core_precision );\n   };\n\n   const share_type core_precision = asset::scaled_precision( asset_id_type()(db).precision );\n\n   void create_vesting_balance_object(const account_id_type& account_id, vesting_balance_type balance_type )\n   {\n      db.create<vesting_balance_object>([&account_id, balance_type] (vesting_balance_object &vbo) {\n         vbo.owner = account_id;\n         vbo.balance_type = balance_type;\n      });\n   };\n};\n\nBOOST_FIXTURE_TEST_SUITE( fee_sharing_tests, reward_database_fixture )\n\nBOOST_AUTO_TEST_CASE(cannot_create_asset_with_reward_percent_of_100_before_hf1774)\n{\n   try\n   {\n      ACTOR(issuer);\n\n      uint16_t reward_percent = GRAPHENE_100_PERCENT + 1; // 100.01%\n      flat_set<account_id_type> whitelist = {issuer_id};\n      price price(asset(1, asset_id_type(1)), asset(1));\n      uint16_t market_fee_percent = 100;\n\n      additional_asset_options_t options;\n      options.value.reward_percent = reward_percent;\n      options.value.whitelist_market_fee_sharing = whitelist;\n\n      GRAPHENE_CHECK_THROW(create_user_issued_asset(\"USD\",\n                                                    issuer,\n                                                    charge_market_fee,\n                                                    price,\n                                                    2,\n                                                    market_fee_percent,\n                                                    options),\n                           fc::assert_exception);\n\n      reward_percent = GRAPHENE_100_PERCENT; // 100%\n      options.value.reward_percent = reward_percent;\n      GRAPHENE_CHECK_THROW(create_user_issued_asset(\"USD\",\n                                                    issuer,\n                                                    charge_market_fee,\n                                                    price,\n                                                    2,\n                                                    market_fee_percent,\n                                                    options),\n                           fc::assert_exception);\n\n      reward_percent = GRAPHENE_100_PERCENT - 1; // 99.99%\n      options.value.reward_percent = reward_percent;\n      asset_object usd_asset = create_user_issued_asset(\"USD\",\n                                                        issuer,\n                                                        charge_market_fee,\n                                                        price,\n                                                        2,\n                                                        market_fee_percent,\n                                                        options);\n\n      additional_asset_options usd_options = usd_asset.options.extensions.value;\n      BOOST_CHECK_EQUAL(reward_percent, *usd_options.reward_percent);\n      BOOST_CHECK(whitelist == *usd_options.whitelist_market_fee_sharing);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(cannot_set_reward_percent_to_100_before_hf1774)\n{\n   try\n   {\n      ACTOR(issuer);\n\n      asset_object usd_asset = create_user_issued_asset(\"USD\", issuer, charge_market_fee);\n\n      uint16_t reward_percent = GRAPHENE_100_PERCENT + 1; // 100.01%\n      flat_set<account_id_type> whitelist = {issuer_id};\n      GRAPHENE_CHECK_THROW(\n                  update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist),\n                  fc::assert_exception );\n\n      reward_percent = GRAPHENE_100_PERCENT; // 100%\n      GRAPHENE_CHECK_THROW(\n                  update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist),\n                  fc::assert_exception );\n\n      reward_percent = GRAPHENE_100_PERCENT - 1; // 99.99%\n      update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist);\n\n      asset_object updated_asset = usd_asset.get_id()(db);\n      additional_asset_options options = updated_asset.options.extensions.value;\n      BOOST_CHECK_EQUAL(reward_percent, *options.reward_percent);\n      BOOST_CHECK(whitelist == *options.whitelist_market_fee_sharing);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(create_asset_with_reward_percent_of_100_after_hf1774)\n{\n   try\n   {\n      generate_blocks_past_hf1774();\n\n      ACTOR(issuer);\n\n      uint16_t reward_percent = GRAPHENE_100_PERCENT; // 100.00%\n      flat_set<account_id_type> whitelist = {issuer_id};\n      price price(asset(1, asset_id_type(1)), asset(1));\n      uint16_t market_fee_percent = 100;\n\n      additional_asset_options_t options;\n      options.value.reward_percent = reward_percent;\n      options.value.whitelist_market_fee_sharing = whitelist;\n\n      asset_object usd_asset = create_user_issued_asset(\"USD\",\n                                                        issuer,\n                                                        charge_market_fee,\n                                                        price,\n                                                        2,\n                                                        market_fee_percent,\n                                                        options);\n\n      additional_asset_options usd_options = usd_asset.options.extensions.value;\n      BOOST_CHECK_EQUAL(reward_percent, *usd_options.reward_percent);\n      BOOST_CHECK(whitelist == *usd_options.whitelist_market_fee_sharing);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(set_reward_percent_to_100_after_hf1774)\n{\n   try\n   {\n      ACTOR(issuer);\n\n      asset_object usd_asset = create_user_issued_asset(\"USD\", issuer, charge_market_fee); // make a copy\n\n      generate_blocks_past_hf1774();\n\n      uint16_t reward_percent = GRAPHENE_100_PERCENT; // 100.00%\n      flat_set<account_id_type> whitelist = {issuer_id};\n      update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist);\n\n      additional_asset_options options = usd_asset.get_id()(db).options.extensions.value;\n      BOOST_CHECK_EQUAL(reward_percent, *options.reward_percent);\n      BOOST_CHECK(whitelist == *options.whitelist_market_fee_sharing);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(asset_rewards_test)\n{\n   try\n   {\n      // handle small percentages\n      generate_blocks(HARDFORK_453_TIME + 10);\n      set_expiration(db, trx);\n\n      ACTORS((registrar)(alicereferrer)(bobreferrer)(izzy)(jill));\n\n      auto register_account = [&](const string& name, const account_object& referrer) -> const account_object&\n      {\n         uint16_t referrer_percent = GRAPHENE_1_PERCENT;\n         fc::ecc::private_key _private_key = generate_private_key(name);\n         public_key_type _public_key = _private_key.get_public_key();\n         return create_account(name, registrar, referrer, referrer_percent, _public_key);\n      };\n\n      // Izzy issues asset to Alice\n      // Jill issues asset to Bob\n      // Alice and Bob trade in the market and pay fees\n      // Bob's and Alice's referrers can get reward\n      upgrade_to_lifetime_member(registrar);\n      upgrade_to_lifetime_member(alicereferrer);\n      upgrade_to_lifetime_member(bobreferrer);\n\n      auto alice = register_account(\"alice\", alicereferrer);\n      auto bob = register_account(\"bob\", bobreferrer);\n\n      transfer( committee_account, alice.get_id(), core_asset(1000000) );\n      transfer( committee_account, bob.get_id(), core_asset(1000000) );\n      transfer( committee_account, izzy_id, core_asset(1000000) );\n      transfer( committee_account, jill_id, core_asset(1000000) );\n\n      constexpr auto izzycoin_reward_percent = 10*GRAPHENE_1_PERCENT;\n      constexpr auto jillcoin_reward_percent = 20*GRAPHENE_1_PERCENT;\n\n      constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT;\n      constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT;\n\n      asset_id_type izzycoin_id = create_bitasset( \"IZZYCOIN\", izzy_id, izzycoin_market_percent ).get_id();\n      asset_id_type jillcoin_id = create_bitasset( \"JILLCOIN\", jill_id, jillcoin_market_percent ).get_id();\n\n      update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent);\n      update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent);\n\n      const share_type izzy_prec = asset::scaled_precision( asset_id_type(izzycoin_id)(db).precision );\n      const share_type jill_prec = asset::scaled_precision( asset_id_type(jillcoin_id)(db).precision );\n\n      auto _izzy = [&]( int64_t x ) -> asset\n      {   return asset( x*izzy_prec, izzycoin_id );   };\n      auto _jill = [&]( int64_t x ) -> asset\n      {   return asset( x*jill_prec, jillcoin_id );   };\n\n      update_feed_producers( izzycoin_id(db), { izzy_id } );\n      update_feed_producers( jillcoin_id(db), { jill_id } );\n\n      // Izzycoin is worth 100 BTS\n      price_feed feed;\n      feed.settlement_price = price( _izzy(1), core_asset(100) );\n      feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      publish_feed( izzycoin_id(db), izzy, feed );\n\n      // Jillcoin is worth 30 BTS\n      feed.settlement_price = price( _jill(1), core_asset(30) );\n      feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n      publish_feed( jillcoin_id(db), jill, feed );\n\n      enable_fees();\n\n      // Alice and Bob create some coins\n      borrow( alice.get_id(), _izzy( 1500), core_asset( 600000) );\n      borrow( bob.get_id(), _jill(2000), core_asset(180000) );\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice.get_id(), _izzy(1000), _jill(1500) ); // Alice is willing to sell her 1000 Izzy's\n                                                                     // for 1.5 Jill\n      create_sell_order( bob.get_id(), _jill(1500), _izzy(1000) );   // Bob is buying up to 1500 Izzy's\n                                                                     // for up to 0.6 Jill\n\n      // 1000 Izzys and 1500 Jills are matched, so the fees should be\n      //   100 Izzy (10%) and 300 Jill (20%).\n      // Bob's and Alice's referrers should get rewards\n      share_type bob_refereer_reward = get_market_fee_reward( bob.referrer, izzycoin_id );\n      share_type alice_refereer_reward = get_market_fee_reward( alice.referrer, jillcoin_id );\n\n      // Bob's and Alice's registrars should get rewards\n      share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id );\n      share_type alice_registrar_reward = get_market_fee_reward( alice.registrar, jillcoin_id );\n\n      auto calculate_percent = [](const share_type& value, uint16_t percent)\n      {\n         auto a(value.value);\n         a *= percent;\n         a /= GRAPHENE_100_PERCENT;\n         return a;\n      };\n\n      BOOST_CHECK_GT( bob_refereer_reward, 0 );\n      BOOST_CHECK_GT( alice_refereer_reward, 0 );\n      BOOST_CHECK_GT( bob_registrar_reward, 0 );\n      BOOST_CHECK_GT( alice_registrar_reward, 0 );\n\n      const auto izzycoin_market_fee = calculate_percent(_izzy(1000).amount, izzycoin_market_percent);\n      const auto izzycoin_reward = calculate_percent(izzycoin_market_fee, izzycoin_reward_percent);\n      BOOST_CHECK_EQUAL( izzycoin_reward, bob_refereer_reward + bob_registrar_reward );\n      BOOST_CHECK_EQUAL( calculate_percent(izzycoin_reward, bob.referrer_rewards_percentage), bob_refereer_reward );\n\n      const auto jillcoin_market_fee = calculate_percent(_jill(1500).amount, jillcoin_market_percent);\n      const auto jillcoin_reward = calculate_percent(jillcoin_market_fee, jillcoin_reward_percent);\n      BOOST_CHECK_EQUAL( jillcoin_reward, alice_refereer_reward + alice_registrar_reward );\n      BOOST_CHECK_EQUAL( calculate_percent(jillcoin_reward, alice.referrer_rewards_percentage),\n                         alice_refereer_reward );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(asset_claim_reward_test)\n{\n   try\n   {\n      ACTORS((jill)(izzy));\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n\n      upgrade_to_lifetime_member(izzy);\n\n      price price(asset(1, asset_id_type(1)), asset(1));\n      uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT;\n      const asset_object jillcoin = create_user_issued_asset( \"JCOIN\", jill,  charge_market_fee, price,\n                                                              2, market_fee_percent );\n\n      const account_object alice = create_account(\"alice\", izzy, izzy, 50/*0.5%*/);\n      const account_object bob   = create_account(\"bob\",   izzy, izzy, 50/*0.5%*/);\n\n      // prepare users' balance\n      issue_uia( alice, jillcoin.amount( 20000000 ) );\n\n      transfer( committee_account, alice.get_id(), core_asset(1000) );\n      transfer( committee_account, bob.get_id(),   core_asset(1000) );\n      transfer( committee_account, izzy.get_id(),  core_asset(1000) );\n\n      // update_asset: set referrer percent\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent);\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice, jillcoin.amount(200000), core_asset(1) );\n      create_sell_order( bob, core_asset(1), jillcoin.amount(100000) );\n\n      const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin );\n      const int64_t izzy_balance = get_balance( izzy, jillcoin );\n\n      BOOST_CHECK_GT(izzy_reward, 0);\n\n      auto claim_reward = [&]( account_object referrer, asset amount_to_claim, fc::ecc::private_key private_key )\n      {\n        vesting_balance_withdraw_operation op;\n        op.vesting_balance = vesting_balance_id_type(0);\n        op.owner = referrer.get_id();\n        op.amount = amount_to_claim;\n\n        signed_transaction tx;\n        tx.operations.push_back( op );\n        db.current_fee_schedule().set_fee( tx.operations.back() );\n        set_expiration( db, tx );\n        sign( tx, private_key );\n        PUSH_TX( db, tx );\n      };\n\n      const int64_t amount_to_claim = 3;\n      claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key );\n\n      BOOST_CHECK_EQUAL(get_balance( izzy, jillcoin ), izzy_balance + amount_to_claim);\n      BOOST_CHECK_EQUAL(get_market_fee_reward( izzy, jillcoin ), izzy_reward - amount_to_claim);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(create_actors)\n{\n   try\n   {\n      ACTORS((jill)(izzyregistrar)(izzyreferrer)(tempregistrar));\n\n      upgrade_to_lifetime_member(izzyregistrar);\n      upgrade_to_lifetime_member(izzyreferrer);\n      upgrade_to_lifetime_member(tempregistrar);\n\n      price price(asset(1, asset_id_type(1)), asset(1));\n      uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT;\n      const asset_object jillcoin = create_user_issued_asset( \"JCOIN\", jill, charge_market_fee,\n                                                              price, 2, market_fee_percent );\n\n      const account_object alice = create_account(\"alice\", izzyregistrar, izzyreferrer, 50/*0.5%*/);\n      const account_object bob   = create_account(\"bob\",   izzyregistrar, izzyreferrer, 50/*0.5%*/);\n      const account_object old   = create_account(\"old\",   GRAPHENE_TEMP_ACCOUNT(db),\n                                                           GRAPHENE_COMMITTEE_ACCOUNT(db), 50u);\n      const account_object tmp   = create_account(\"tmp\",   tempregistrar,\n                                                           GRAPHENE_TEMP_ACCOUNT(db), 50u);\n\n      // prepare users' balance\n      issue_uia( alice, jillcoin.amount( 20000000 ) );\n\n      transfer( committee_account, alice.get_id(), core_asset(1000) );\n      transfer( committee_account, bob.get_id(),   core_asset(1000) );\n      transfer( committee_account, old.get_id(),   core_asset(1000) );\n      transfer( committee_account, tmp.get_id(),   core_asset(1000) );\n      transfer( committee_account, izzyregistrar.get_id(),  core_asset(1000) );\n      transfer( committee_account, izzyreferrer.get_id(),  core_asset(1000) );\n      transfer( committee_account, tempregistrar.get_id(),  core_asset(1000) );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(fee_shares_between_temp_acc_and_committee_acc_before_hf_1800)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      flat_set<account_id_type> whitelist;\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n\n      GET_ACTOR(alice);\n      GET_ACTOR(old);\n\n      create_sell_order( alice, jillcoin.amount(100000), core_asset(1) );\n      create_sell_order( old, core_asset(1), jillcoin.amount(100000) );\n\n      BOOST_CHECK_GT( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_GT( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n\n   }\n   FC_LOG_AND_RETHROW()\n\n}\n\nBOOST_AUTO_TEST_CASE(fee_do_not_share_between_temp_acc_and_committee_acc_after_hf_1800)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      generate_blocks_past_hf1800();\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      flat_set<account_id_type> whitelist;\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n\n      GET_ACTOR(alice);\n      GET_ACTOR(old);\n\n      create_sell_order( alice, jillcoin.amount(100000), core_asset(1) );\n      create_sell_order( old, core_asset(1), jillcoin.amount(100000) );\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_GT( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n\n   }\n   FC_LOG_AND_RETHROW()\n\n}\n\nBOOST_AUTO_TEST_CASE(fee_shares_to_temp_referrer_before_hf_1800)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      GET_ACTOR(jill);\n      GET_ACTOR(tempregistrar);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      flat_set<account_id_type> whitelist;\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( tempregistrar, jillcoin), 0);\n\n      GET_ACTOR(alice);\n      GET_ACTOR(tmp);\n\n      create_sell_order( alice, jillcoin.amount(100000), core_asset(1) );\n      create_sell_order( tmp, core_asset(1), jillcoin.amount(100000) );\n\n      BOOST_CHECK_GT( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_GT( get_market_fee_reward( tempregistrar, jillcoin), 0);\n\n   }\n   FC_LOG_AND_RETHROW()\n\n}\n\nBOOST_AUTO_TEST_CASE(fee_do_not_share_to_temp_referrer_after_hf_1800)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      generate_blocks_past_hf1800();\n      GET_ACTOR(jill);\n      GET_ACTOR(tempregistrar);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      flat_set<account_id_type> whitelist;\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( tempregistrar, jillcoin), 0);\n\n      GET_ACTOR(alice);\n      GET_ACTOR(tmp);\n\n      create_sell_order( alice, jillcoin.amount(100000), core_asset(1) );\n      create_sell_order( tmp, core_asset(1), jillcoin.amount(100000) );\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( GRAPHENE_TEMP_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_GT( get_market_fee_reward( GRAPHENE_COMMITTEE_ACCOUNT(db), jillcoin), 0);\n      BOOST_CHECK_GT( get_market_fee_reward( tempregistrar, jillcoin), 0);\n\n   }\n   FC_LOG_AND_RETHROW()\n\n}\n\nBOOST_AUTO_TEST_CASE(white_list_is_empty_test)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      flat_set<account_id_type> whitelist;\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      GET_ACTOR(izzyregistrar);\n      GET_ACTOR(izzyreferrer);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 );\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n      // Alice and Bob place orders which match\n      create_sell_order( alice, jillcoin.amount(200000), core_asset(1) );\n      create_sell_order( bob, core_asset(1), jillcoin.amount(100000) );\n\n      const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin );\n      const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin );\n      BOOST_CHECK_GT(izzyregistrar_reward , 0);\n      BOOST_CHECK_GT(izzyreferrer_reward , 0);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(white_list_contains_registrar_test)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      GET_ACTOR(izzyregistrar);\n      GET_ACTOR(izzyreferrer);\n      flat_set<account_id_type> whitelist = {jill_id, izzyregistrar_id};\n\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 );\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n      // Alice and Bob place orders which match\n      create_sell_order( alice, jillcoin.amount(200000), core_asset(1) );\n      create_sell_order( bob, core_asset(1), jillcoin.amount(100000) );\n\n      const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin );\n      const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin );\n      BOOST_CHECK_GT(izzyregistrar_reward , 0);\n      BOOST_CHECK_GT(izzyreferrer_reward , 0);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(white_list_contains_referrer_test)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      GET_ACTOR(izzyregistrar);\n      GET_ACTOR(izzyreferrer);\n      flat_set<account_id_type> whitelist = {jill_id, izzyreferrer_id};\n\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 );\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n      // Alice and Bob place orders which match\n      create_sell_order( alice, jillcoin.amount(200000), core_asset(1) );\n      create_sell_order( bob, core_asset(1), jillcoin.amount(100000) );\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test)\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      GET_ACTOR(alice);\n      flat_set<account_id_type> whitelist = {jill_id, alice_id};\n\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist);\n\n      GET_ACTOR(izzyregistrar);\n      GET_ACTOR(izzyreferrer);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 );\n\n      GET_ACTOR(bob);\n      // Alice and Bob place orders which match\n      create_sell_order( alice, jillcoin.amount(200000), core_asset(1) );\n      create_sell_order( bob, core_asset(1), jillcoin.amount(100000) );\n\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(create_asset_via_proposal_test)\n{\n   try\n   {\n      ACTOR(issuer);\n      price core_exchange_rate(asset(1, asset_id_type(1)), asset(1));\n\n      asset_create_operation create_op;\n      create_op.issuer = issuer.get_id();\n      create_op.fee = asset();\n      create_op.symbol = \"ASSET\";\n      create_op.common_options.max_supply = 0;\n      create_op.precision = 2;\n      create_op.common_options.core_exchange_rate = core_exchange_rate;\n      create_op.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      create_op.common_options.flags = charge_market_fee;\n\n      additional_asset_options_t options;\n      options.value.reward_percent = 100;\n      options.value.whitelist_market_fee_sharing = flat_set<account_id_type>{issuer_id};\n      create_op.common_options.extensions = std::move(options);;\n\n      const auto& curfees = *db.get_global_properties().parameters.current_fees;\n      const auto& proposal_create_fees = curfees.get<proposal_create_operation>();\n      proposal_create_operation prop;\n      prop.fee_paying_account = issuer_id;\n      prop.proposed_ops.emplace_back( create_op );\n      prop.expiration_time =  db.head_block_time() + fc::days(1);\n      prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n\n      {\n         prop.expiration_time =  db.head_block_time() + fc::days(1);\n         signed_transaction tx;\n         tx.operations.push_back( prop );\n         db.current_fee_schedule().set_fee( tx.operations.back() );\n         set_expiration( db, tx );\n         sign( tx, issuer_private_key );\n         PUSH_TX( db, tx );\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(update_asset_via_proposal_test)\n{\n   try\n   {\n      ACTOR(issuer);\n      asset_object usd_asset = create_user_issued_asset(\"USD\", issuer, charge_market_fee);\n\n      additional_asset_options_t options;\n      options.value.reward_percent = 100;\n      options.value.whitelist_market_fee_sharing = flat_set<account_id_type>{issuer_id};\n\n      asset_update_operation update_op;\n      update_op.issuer = issuer_id;\n      update_op.asset_to_update = usd_asset.get_id();\n      asset_options new_options;\n      update_op.new_options = usd_asset.options;\n      update_op.new_options.extensions = std::move(options);\n\n      const auto& curfees = *db.get_global_properties().parameters.current_fees;\n      const auto& proposal_create_fees = curfees.get<proposal_create_operation>();\n      proposal_create_operation prop;\n      prop.fee_paying_account = issuer_id;\n      prop.proposed_ops.emplace_back( update_op );\n      prop.expiration_time =  db.head_block_time() + fc::days(1);\n      prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n\n      {\n         prop.expiration_time =  db.head_block_time() + fc::days(1);\n         signed_transaction tx;\n         tx.operations.push_back( prop );\n         db.current_fee_schedule().set_fee( tx.operations.back() );\n         set_expiration( db, tx );\n         sign( tx, issuer_private_key );\n         PUSH_TX( db, tx );\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(issue_asset){\n   try\n   {\n       ACTORS((alice)(bob)(izzy)(jill));\n      // Izzy issues asset to Alice  (Izzycoin market percent - 10%)\n      // Jill issues asset to Bob    (Jillcoin market percent - 20%)\n\n      fund( alice, core_asset(1000000) );\n      fund( bob, core_asset(1000000) );\n      fund( izzy, core_asset(1000000) );\n      fund( jill, core_asset(1000000) );\n\n      price price(asset(1, asset_id_type(1)), asset(1));\n      constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT;\n      asset_object izzycoin = create_user_issued_asset( \"IZZYCOIN\", izzy,  charge_market_fee, price,\n                                                        2, izzycoin_market_percent );\n\n      constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT;\n      asset_object jillcoin = create_user_issued_asset( \"JILLCOIN\", jill,  charge_market_fee, price,\n                                                        2, jillcoin_market_percent );\n\n      // Alice and Bob create some coins\n      issue_uia( alice, izzycoin.amount( 100000 ) );\n      issue_uia( bob, jillcoin.amount( 100000 ) );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(accumulated_fees_before_hf_test)\n{\n   try\n   {\n      INVOKE(issue_asset);\n\n      const asset_object &jillcoin = get_asset(\"JILLCOIN\");\n      const asset_object &izzycoin = get_asset(\"IZZYCOIN\");\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) );   // Alice is willing to sell\n                                                                                   // her Izzy's for 3 Jill\n      create_sell_order(   bob_id, jillcoin.amount(700), izzycoin.amount(200) );   // Bob is buying up to 200 Izzy's\n                                                                                   // for up to 3.5 Jill\n\n      // 100 Izzys and 300 Jills are matched, so the fees should be\n      // 10 Izzy (10%) and 60 Jill (20%).\n      BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount );\n      BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(accumulated_fees_after_hf_test)\n{\n   try\n   {\n      INVOKE(issue_asset);\n\n      const asset_object &jillcoin = get_asset(\"JILLCOIN\");\n      const asset_object &izzycoin = get_asset(\"IZZYCOIN\");\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) );   // Alice is willing to sell\n                                                                                   // her Izzy's for 3 Jill\n      create_sell_order(   bob_id, jillcoin.amount(700), izzycoin.amount(200) );   // Bob is buying up to 200 Izzy's\n                                                                                   // for up to 3.5 Jill\n\n      // 100 Izzys and 300 Jills are matched, so the fees should be\n      // 10 Izzy (10%) and 60 Jill (20%).\n      BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount );\n      BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(accumulated_fees_with_additional_options_after_hf_test)\n{\n   try\n   {\n      INVOKE(issue_asset);\n\n      GET_ACTOR(jill);\n      GET_ACTOR(izzy);\n\n      const asset_object &jillcoin = get_asset(\"JILLCOIN\");\n      const asset_object &izzycoin = get_asset(\"IZZYCOIN\");\n\n      uint16_t reward_percent = 0;\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), reward_percent);\n      update_asset(izzy_id, izzy_private_key, izzycoin.get_id(), reward_percent);\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) );   // Alice is willing to sell\n                                                                                   // her Izzy's for 3 Jill\n      create_sell_order(   bob_id, jillcoin.amount(700), izzycoin.amount(200) );   // Bob is buying up to 200 Izzy's\n                                                                                   // for up to 3.5 Jill\n\n      // 100 Izzys and 300 Jills are matched, so the fees should be\n      // 10 Izzy (10%) and 60 Jill (20%).\n      BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount );\n      BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_test )\n{ try {\n\n   ACTOR(alice);\n   fund(alice);\n\n   const asset_object& core = asset_id_type()(db);\n\n   vesting_balance_create_operation op;\n   op.fee = core.amount( 0 );\n   op.creator = alice_id;\n   op.owner = alice_id;\n   op.amount = core.amount( 100 );\n   op.policy = instant_vesting_policy_initializer{};\n\n   trx.operations.push_back(op);\n   set_expiration( db, trx );\n\n   processed_transaction ptx = PUSH_TX( db, trx, ~0 );\n   const vesting_balance_id_type vbid { ptx.operation_results.back().get<object_id_type>() };\n\n   auto withdraw = [&](const asset& amount) {\n      vesting_balance_withdraw_operation withdraw_op;\n      withdraw_op.vesting_balance = vbid;\n      withdraw_op.owner = alice_id;\n      withdraw_op.amount = amount;\n\n      signed_transaction withdraw_tx;\n      withdraw_tx.operations.push_back( withdraw_op );\n      set_expiration( db, withdraw_tx );\n      sign(withdraw_tx, alice_private_key);\n      PUSH_TX( db, withdraw_tx );\n   };\n   // try to withdraw more then it is on the balance\n   GRAPHENE_REQUIRE_THROW(withdraw(op.amount.amount + 1), fc::exception);\n   //to withdraw all that is on the balance\n   withdraw(op.amount);\n   // try to withdraw more then it is on the balance\n   GRAPHENE_REQUIRE_THROW(withdraw( core.amount(1) ), fc::exception);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_via_proposal_test )\n{ try {\n\n   ACTOR(actor);\n   fund(actor);\n\n   const asset_object& core = asset_id_type()(db);\n\n   vesting_balance_create_operation create_op;\n   create_op.fee = core.amount( 0 );\n   create_op.creator = actor_id;\n   create_op.owner = actor_id;\n   create_op.amount = core.amount( 100 );\n   create_op.policy = instant_vesting_policy_initializer{};\n\n   const auto& curfees = *db.get_global_properties().parameters.current_fees;\n   const auto& proposal_create_fees = curfees.get<proposal_create_operation>();\n   proposal_create_operation prop;\n   prop.fee_paying_account = actor_id;\n   prop.proposed_ops.emplace_back( create_op );\n   prop.expiration_time =  db.head_block_time() + fc::days(1);\n   prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n\n   {\n      prop.expiration_time =  db.head_block_time() + fc::days(1);\n      signed_transaction tx;\n      tx.operations.push_back( prop );\n      db.current_fee_schedule().set_fee( tx.operations.back() );\n      set_expiration( db, tx );\n      sign( tx, actor_private_key );\n      PUSH_TX( db, tx );\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(white_list_asset_rewards_test)\n{\n   try\n   {\n      ACTORS((aliceregistrar)(bobregistrar)(alicereferrer)(bobreferrer)(izzy)(jill));\n\n      // Izzy issues white_list asset to Alice\n      // Jill issues white_list asset to Bob\n      // Bobreferrer added to blacklist for izzycoin asset\n      // Aliceregistrar added to blacklist for jillcoin asset\n      // Alice and Bob trade in the market and pay fees\n      // Check registrar/referrer rewards\n      upgrade_to_lifetime_member(aliceregistrar);\n      upgrade_to_lifetime_member(alicereferrer);\n      upgrade_to_lifetime_member(bobregistrar);\n      upgrade_to_lifetime_member(bobreferrer);\n      upgrade_to_lifetime_member(izzy);\n      upgrade_to_lifetime_member(jill);\n\n      const account_object alice = create_account(\"alice\", aliceregistrar, alicereferrer, 20*GRAPHENE_1_PERCENT);\n      const account_object bob   = create_account(\"bob\", bobregistrar, bobreferrer, 20*GRAPHENE_1_PERCENT);\n\n      fund( alice, core_asset(1000000) );\n      fund( bob, core_asset(1000000) );\n      fund( izzy, core_asset(1000000) );\n      fund( jill, core_asset(1000000) );\n\n      price price(asset(1, asset_id_type(1)), asset(1));\n      constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT;\n      constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT;\n      const asset_id_type izzycoin_id = create_user_issued_asset( \"IZZYCOIN\", izzy, charge_market_fee|white_list,\n                                                                  price, 0, izzycoin_market_percent ).get_id();\n      const asset_id_type jillcoin_id = create_user_issued_asset( \"JILLCOIN\", jill, charge_market_fee|white_list,\n                                                                  price, 0, jillcoin_market_percent ).get_id();\n\n      // Alice and Bob create some coins\n      issue_uia( alice, izzycoin_id(db).amount( 200000 ) );\n      issue_uia( bob, jillcoin_id(db).amount( 200000 ) );\n\n      constexpr auto izzycoin_reward_percent = 50*GRAPHENE_1_PERCENT;\n      constexpr auto jillcoin_reward_percent = 50*GRAPHENE_1_PERCENT;\n\n      update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent);\n      update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent);\n\n      BOOST_TEST_MESSAGE( \"Attempting to blacklist bobreferrer for izzycoin asset\" );\n      asset_update_blacklist_authority(izzy_id, izzycoin_id, izzy_id, izzy_private_key);\n      add_account_to_blacklist(izzy_id, bobreferrer_id, izzy_private_key);\n      BOOST_CHECK( !(is_authorized_asset( db, bobreferrer_id(db), izzycoin_id(db) )) );\n\n      BOOST_TEST_MESSAGE( \"Attempting to blacklist aliceregistrar for jillcoin asset\" );\n      asset_update_blacklist_authority(jill_id, jillcoin_id, jill_id, jill_private_key);\n      add_account_to_blacklist(jill_id, aliceregistrar_id, jill_private_key);\n      BOOST_CHECK( !(is_authorized_asset( db, aliceregistrar_id(db), jillcoin_id(db) )) );\n\n      // Alice and Bob place orders which match\n      // Alice is willing to sell her 1000 Izzy's for 1.5 Jill\n      create_sell_order( alice.get_id(), izzycoin_id(db).amount(1000), jillcoin_id(db).amount(1500) );\n      // Bob is buying up to 1500 Izzy's for up to 0.6 Jill\n      create_sell_order(   bob.get_id(), jillcoin_id(db).amount(1500), izzycoin_id(db).amount(1000) );\n\n      // 1000 Izzys and 1500 Jills are matched, so the fees should be\n      //   100 Izzy (10%) and 300 Jill (20%).\n\n      // Only Bob's registrar should get rewards\n      share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id );\n      BOOST_CHECK_GT( bob_registrar_reward, 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( bob.referrer, izzycoin_id ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( alice.registrar, jillcoin_id ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( alice.referrer, jillcoin_id ), 0 );\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( create_vesting_balance_object_test )\n{\n   /**\n    * Test checks that an account could have duplicates VBO (with the same asset_type)\n    * for any type of vesting_balance_type\n    * except vesting_balance_type::market_fee_sharing\n   */\n   try {\n\n      ACTOR(actor);\n\n      create_vesting_balance_object(actor_id, vesting_balance_type::unspecified);\n      create_vesting_balance_object(actor_id, vesting_balance_type::unspecified);\n\n      create_vesting_balance_object(actor_id, vesting_balance_type::cashback);\n      create_vesting_balance_object(actor_id, vesting_balance_type::cashback);\n\n      create_vesting_balance_object(actor_id, vesting_balance_type::witness);\n      create_vesting_balance_object(actor_id, vesting_balance_type::witness);\n\n      create_vesting_balance_object(actor_id, vesting_balance_type::worker);\n      create_vesting_balance_object(actor_id, vesting_balance_type::worker);\n\n      create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing);\n      GRAPHENE_CHECK_THROW(create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing),\n                           fc::exception);\n\n} FC_LOG_AND_RETHROW() }\n\n\nBOOST_AUTO_TEST_CASE( possibility_to_set_100_reward_percent_after_hf1774 )\n{\n   try\n   {\n      INVOKE(create_actors);\n\n      generate_blocks_past_hf1774();\n      GET_ACTOR(jill);\n\n      constexpr auto jillcoin_reward_percent = 100*GRAPHENE_1_PERCENT;\n      const asset_object &jillcoin = get_asset(\"JCOIN\");\n\n      update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent);\n\n      GET_ACTOR(izzyregistrar);\n      GET_ACTOR(izzyreferrer);\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 );\n      BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 );\n\n      GET_ACTOR(alice);\n      GET_ACTOR(bob);\n\n      const share_type sell_amount = 100000;\n\n      // Alice and Bob place orders which match\n      create_sell_order( alice, jillcoin.amount(sell_amount), core_asset(1) );\n      create_sell_order( bob, core_asset(1), jillcoin.amount(sell_amount) );\n\n      const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin );\n      const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin );\n\n      BOOST_CHECK_GT(izzyregistrar_reward , 0);\n      BOOST_CHECK_GT(izzyreferrer_reward , 0);\n\n      auto calculate_percent = [](const share_type& value, uint16_t percent)\n      {\n         auto a(value.value);\n         a *= percent;\n         a /= GRAPHENE_100_PERCENT;\n         return a;\n      };\n      //jillcoin has 20% market fee percent, see create_actors\n      //all market fees are distributed between registrar and referrer\n      auto acc_rewards = izzyregistrar_reward + izzyreferrer_reward;\n      BOOST_CHECK_EQUAL( calculate_percent(sell_amount, 20*GRAPHENE_1_PERCENT), acc_rewards);\n\n      //check referrer reward\n      BOOST_CHECK_EQUAL( calculate_percent(acc_rewards, alice.referrer_rewards_percentage), izzyreferrer_reward);\n   }\n   FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/market_rounding_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and other contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(market_rounding_tests, database_fixture)\n\n/**\n *  Create an order such that when the trade executes at the\n *  requested price the resulting payout to one party is 0\n *  ( Reproduces https://github.com/bitshares/bitshares-core/issues/184 )\n */\nBOOST_AUTO_TEST_CASE( trade_amount_equals_zero )\n{\n   try {\n      generate_blocks( HARDFORK_555_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      const asset_object& test = create_user_issued_asset( \"UIATEST\" );\n      const asset_id_type test_id = test.get_id();\n      const asset_object& core = get_asset( GRAPHENE_SYMBOL );\n      const asset_id_type core_id = core.get_id();\n      const account_object& core_seller = create_account( \"seller1\" );\n      const account_object& core_buyer = create_account(\"buyer1\");\n\n      transfer( committee_account(db), core_seller, asset( 100000000 ) );\n\n      issue_uia( core_buyer, asset( 10000000, test_id ) );\n\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000);\n\n      create_sell_order(core_seller, core.amount(1), test.amount(2));\n      create_sell_order(core_seller, core.amount(1), test.amount(2));\n      create_sell_order(core_buyer, test.amount(3), core.amount(1));\n\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1);\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999997);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, test), 3);\n\n      generate_block();\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      auto result = get_market_order_history(core_id, test_id);\n      BOOST_CHECK_EQUAL(result.size(), 4u);\n      BOOST_CHECK(result[0].op.pays == core_id(db).amount(0));\n      BOOST_CHECK(result[0].op.receives == test_id(db).amount(1));\n      BOOST_CHECK(result[1].op.pays == test_id(db).amount(1));\n      BOOST_CHECK(result[1].op.receives == core_id(db).amount(0));\n      BOOST_CHECK(result[2].op.pays == core_id(db).amount(1));\n      BOOST_CHECK(result[2].op.receives == test_id(db).amount(2));\n      BOOST_CHECK(result[3].op.pays == test_id(db).amount(2));\n      BOOST_CHECK(result[3].op.receives == core_id(db).amount(1));\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/**\n *  The something-for-nothing bug should be fixed https://github.com/bitshares/bitshares-core/issues/184\n */\nBOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 )\n{\n   try {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_184_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      set_expiration( db, trx );\n\n      const asset_object& test = create_user_issued_asset( \"UIATEST\" );\n      const asset_id_type test_id = test.get_id();\n      const asset_object& core = get_asset( GRAPHENE_SYMBOL );\n      const asset_id_type core_id = core.get_id();\n      const account_object& core_seller = create_account( \"seller1\" );\n      const account_object& core_buyer = create_account(\"buyer1\");\n\n      transfer( committee_account(db), core_seller, asset( 100000000 ) );\n\n      issue_uia( core_buyer, asset( 10000000, test_id ) );\n\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000);\n\n      create_sell_order(core_seller, core.amount(1), test.amount(2));\n      create_sell_order(core_seller, core.amount(1), test.amount(2));\n      create_sell_order(core_buyer, test.amount(3), core.amount(1));\n\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1);\n      BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999998);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998);\n      BOOST_CHECK_EQUAL(get_balance(core_seller, test), 2);\n\n      generate_block();\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      auto result = get_market_order_history(core_id, test_id);\n      BOOST_CHECK_EQUAL(result.size(), 2u);\n      BOOST_CHECK(result[0].op.pays == core_id(db).amount(1));\n      BOOST_CHECK(result[0].op.receives == test_id(db).amount(2));\n      BOOST_CHECK(result[1].op.pays == test_id(db).amount(2));\n      BOOST_CHECK(result[1].op.receives == core_id(db).amount(1));\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/***\n * This test case reproduces one of the scenarios described in bitshares-core issue #342:\n *   when matching a limit order with another limit order, a small taker order will pay more than minimum required.\n */\nBOOST_AUTO_TEST_CASE( limit_limit_rounding_test1 )\n{\n   try {\n      generate_blocks( HARDFORK_555_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS( (seller)(buyer) );\n\n      const asset_object& test = create_user_issued_asset( \"UIATEST\" );\n      const asset_id_type test_id = test.get_id();\n      const asset_object& core = get_asset( GRAPHENE_SYMBOL );\n      const asset_id_type core_id = core.get_id();\n\n      transfer( committee_account(db), seller, asset( 100000000 ) );\n\n      issue_uia( buyer, asset( 10000000, test_id ) );\n\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 0);\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000);\n\n      // seller sells 3 core for 31 test, price 10.33 test per core\n      limit_order_id_type sell_id = create_sell_order( seller, core.amount(3), test.amount(31) )->get_id();\n\n      // buyer buys 2 core with 25 test, price 12.5 test per core\n      // the order is filled immediately\n      BOOST_CHECK( !create_sell_order( buyer, test.amount(25), core.amount(2) ) );\n\n      BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 1 ); // 2 core sold, 1 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 25); // seller got 25 test\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 2); // buyer got 2 core\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999975); // buyer paid 25 test,\n                                                            // effective price is 25/2 which is much higher than 31/3\n\n      generate_block();\n\n      // buyer buys 2 core with 25 test, price 12.5 test per core\n      limit_order_id_type buy_id = create_sell_order( buyer_id, asset(25,test_id), asset(2,core_id) )->get_id();\n\n      generate_block();\n\n      BOOST_CHECK( !db.find( sell_id ) ); // sell order is filled\n      BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 15 ); // 10 test sold, 15 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999997);\n      BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 35); // seller got 10 more test\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 3); // buyer got 1 more core\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999950);\n\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/***\n * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork:\n *   when matching a limit order with another limit order,\n *   a small taker order will only pay minimum required amount, and the rest will be returned.\n */\nBOOST_AUTO_TEST_CASE( limit_limit_rounding_test1_after_hf_342 )\n{\n   try {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_342_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      set_expiration( db, trx );\n\n      ACTORS( (seller)(buyer) );\n\n      const asset_object& test = create_user_issued_asset( \"UIATEST\" );\n      const asset_id_type test_id = test.get_id();\n      const asset_object& core = get_asset( GRAPHENE_SYMBOL );\n      const asset_id_type core_id = core.get_id();\n\n      transfer( committee_account(db), seller, asset( 100000000 ) );\n\n      issue_uia( buyer, asset( 10000000, test_id ) );\n\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 0);\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000);\n\n      // seller sells 3 core for 31 test, price 10.33 test per core\n      limit_order_id_type sell_id = create_sell_order( seller, core.amount(3), test.amount(31) )->get_id();\n\n      // buyer buys 2 core with 25 test, price 12.5 test per core\n      // the order is filled immediately\n      BOOST_CHECK( !create_sell_order( buyer, test.amount(25), core.amount(2) ) );\n\n      BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 1 ); // 2 core sold, 1 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 2); // buyer got 2 core\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999979); // buyer actually paid 21 test according to price 10.33\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 21); // seller got 21 test\n\n      generate_block();\n      set_expiration( db, trx );\n\n      // buyer buys 2 core with 25 test, price 12.5 test per core\n      limit_order_id_type buy_id = create_sell_order( buyer_id, asset(25,test_id), asset(2,core_id) )->get_id();\n\n      generate_block();\n\n      BOOST_CHECK( !db.find( sell_id ) ); // sell order is filled\n      BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 15 ); // 10 test sold according to price 10.33, and 15 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 3); // buyer got 1 more core\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999954);\n      BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999997);\n      BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 31); // seller got 10 more test, in total 31 as expected\n\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/***\n * This test case reproduces one of the scenarios described in bitshares-core issue #342:\n *   when matching a limit order with another limit order, a small maker order will pay more than minimum required.\n */\nBOOST_AUTO_TEST_CASE( limit_limit_rounding_test2 )\n{\n   try {\n      generate_blocks( HARDFORK_555_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS( (seller)(buyer) );\n\n      const asset_object& test = create_user_issued_asset( \"UIATEST\" );\n      const asset_id_type test_id = test.get_id();\n      const asset_object& core = get_asset( GRAPHENE_SYMBOL );\n      const asset_id_type core_id = core.get_id();\n\n      transfer( committee_account(db), seller, asset( 100000000 ) );\n\n      issue_uia( buyer, asset( 10000000, test_id ) );\n\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 0);\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000);\n\n      // buyer buys 17 core with 3 test, price 3/17 = 0.176 test per core\n      limit_order_id_type tmp_buy_id = create_sell_order( buyer, test.amount(3), core.amount(17) )->get_id();\n      // seller sells 33 core for 5 test, price 5/33 = 0.1515 test per core\n      limit_order_id_type sell_id = create_sell_order( seller, core.amount(33), test.amount(5) )->get_id();\n\n      BOOST_CHECK( !db.find( tmp_buy_id ) ); // buy order is filled\n      BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 16 ); // 17 core sold, 16 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 99999967);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 3); // seller got 3 test\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 17); // buyer got 17 core\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999997); // buyer paid 3 test\n\n      generate_block();\n      set_expiration( db, trx );\n\n      // buyer buys 15 core with 3 test, price 3/15 = 0.2 test per core\n      // even 15 < 16, since it's taker, we'll check with maker's price, then turns out the buy order is bigger\n      limit_order_id_type buy_id = create_sell_order( buyer_id, asset(3,test_id), asset(15,core_id) )->get_id();\n\n      generate_block();\n\n      BOOST_CHECK( !db.find( sell_id ) ); // sell order is filled\n      BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 1 ); // 2 test sold, 1 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967); // seller paid the 16 core which was remaining in the order\n      BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test\n                                                             // effective price 16/2 which is much higher than 33/5\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 33); // buyer got 16 more core\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999994);\n\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/***\n * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork:\n *   when matching a limit order with another limit order,\n *   a small maker order will only pay minimum required amount, and the rest will be returned.\n */\nBOOST_AUTO_TEST_CASE( limit_limit_rounding_test2_after_hf_342 )\n{\n   try {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_342_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      set_expiration( db, trx );\n\n      ACTORS( (seller)(buyer) );\n\n      const asset_object& test = create_user_issued_asset( \"UIATEST\" );\n      const asset_id_type test_id = test.get_id();\n      const asset_object& core = get_asset( GRAPHENE_SYMBOL );\n      const asset_id_type core_id = core.get_id();\n\n      transfer( committee_account(db), seller, asset( 100000000 ) );\n\n      issue_uia( buyer, asset( 10000000, test_id ) );\n\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 0);\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000);\n\n      // buyer buys 17 core with 3 test, price 3/17 = 0.176 test per core\n      limit_order_id_type tmp_buy_id = create_sell_order( buyer, test.amount(3), core.amount(17) )->get_id();\n      // seller sells 33 core for 5 test, price 5/33 = 0.1515 test per core\n      limit_order_id_type sell_id = create_sell_order( seller, core.amount(33), test.amount(5) )->get_id();\n\n      BOOST_CHECK( !db.find( tmp_buy_id ) ); // buy order is filled\n      BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 16 ); // 17 core sold, 16 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(seller, core), 99999967);\n      BOOST_CHECK_EQUAL(get_balance(seller, test), 3); // seller got 3 test\n      BOOST_CHECK_EQUAL(get_balance(buyer, core), 17); // buyer got 17 core\n      BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999997); // buyer paid 3 test\n\n      generate_block();\n      set_expiration( db, trx );\n\n      // buyer buys 15 core with 3 test, price 3/15 = 0.2 test per core\n      // even 15 < 16, since it's taker, we'll check with maker's price, then turns out the buy order is bigger\n      limit_order_id_type buy_id = create_sell_order( buyer_id, asset(3,test_id), asset(15,core_id) )->get_id();\n\n      generate_block();\n\n      BOOST_CHECK( !db.find( sell_id ) ); // sell order is filled\n      BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 1 ); // 2 test sold, 1 remaining\n\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 31); // buyer got 14 more core according to price 0.1515\n      BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999994);\n      BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967+16-14); // seller got refunded 2 core\n      BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test, effective price 14/2 which is close to 33/5\n\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/***\n * Reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order.\n * Also detects the cull_small issue in check_call_orders.\n */\nBOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 )\n{ try { // matching a limit order with call order\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500));\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(10));\n   transfer(borrower2, seller, bitusd.amount(100000));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 10, call.debt.value );\n   BOOST_CHECK_EQUAL( 1, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 200010, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at price 11 USD / 1 CORE, but call only owes 10 USD,\n   //   so the seller will pay 10 USD but get nothing.\n   // The remaining 1 USD is too little to get any CORE, so the limit order will be cancelled\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(11), core.amount(1)) );\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) ); // the seller paid 10 USD\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Another test case\n * reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order.\n * Also detects the cull_small issue in check_call_orders.\n *\n * In this test case, the limit order is taker.\n */\nBOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 )\n{ try {\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(10));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 10, call.debt.value );\n   BOOST_CHECK_EQUAL( 1, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100010, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at price 33 USD / 3 CORE, but call only owes 10 USD,\n   //   so the seller will pay 10 USD but get nothing.\n   // The remaining USD will be left in the order on the market\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing\n   BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Yet another test case\n * reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order.\n * Also detects the cull_small issue in check_call_orders.\n *\n * In this test case, the limit order is maker.\n */\nBOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 )\n{ try {\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(10));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 10, call.debt.value );\n   BOOST_CHECK_EQUAL( 1, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100010, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   // create a limit order which will be matched later\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   generate_block();\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10);\n   publish_feed( bitusd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD,\n   //   so the seller will pay 10 USD but get nothing.\n   // The remaining USD will be in the order on the market\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100010-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 0, get_balance(seller_id, core_id) ); // the seller got nothing\n   BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) );\n   BOOST_CHECK_EQUAL( init_balance, get_balance(borrower_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #132: something for nothing when matching a limit order with a call order.\n */\nBOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_184_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500));\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(10));\n   transfer(borrower2, seller, bitusd.amount(100000));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 10, call.debt.value );\n   BOOST_CHECK_EQUAL( 1, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 200010, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time),\n   //   but call only owes 10 USD,\n   // Since the call would pay off all debt, let it pay 1 CORE from collateral\n   // The remaining 1 USD is too little to get any CORE, so the limit order will be cancelled\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(11), core.amount(1)) );\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) ); // the seller paid 10 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Another test case\n * for fixed bitshares-core issue #132: something for nothing when matching a limit order with a call order.\n *\n * In this test case, the limit order is taker.\n */\nBOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_184_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(10));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 10, call.debt.value );\n   BOOST_CHECK_EQUAL( 1, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100010, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time),\n   //   but call only owes 10 USD,\n   // Since the call would pay off all debt, let it pay 1 CORE from collateral\n   // The remaining USD will be left in the order on the market\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Yet another test case\n * for fixed bitshares-core issue #132: something for nothing when matching a limit order with a call order.\n * Also detects the cull_small issue in check_call_orders.\n *\n * In this test case, the limit order is maker.\n */\nBOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_184_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(10));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 10, call.debt.value );\n   BOOST_CHECK_EQUAL( 1, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100010, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   // create a limit order which will be matched later\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   generate_block();\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10);\n   publish_feed( bitusd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD,\n   // Since the call would pay off all debt, let it pay 1 CORE from collateral\n   // The remaining USD will be in the order on the market\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100010-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller_id, core_id) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * This test case reproduces one of the scenarios described in bitshares-core issue #342:\n *   when matching a big taker limit order with a small maker call order,\n *   rounding was in favor of the small call order.\n */\nBOOST_AUTO_TEST_CASE( limit_call_rounding_test1 )\n{ try {\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(20), asset(2));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(20));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 20, call.debt.value );\n   BOOST_CHECK_EQUAL( 2, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100020, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at limit order's price 33 USD / 3 CORE, but call only owes 20 USD,\n   //   so the seller will pay the whole 20 USD and get 1 CORE, since 20 USD doesn't worth 2 CORE according to price 33/3,\n   //   effective price is 20/1 which is worse than the limit order's desired 33/3.\n   // The remaining USD will be left in the order on the market\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork:\n *   when matching a big taker limit order with a small maker call order,\n *   rounding in favor of the big limit order.\n */\nBOOST_AUTO_TEST_CASE( limit_call_rounding_test1_after_hf_342 )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_342_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(20), asset(2));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(20));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 20, call.debt.value );\n   BOOST_CHECK_EQUAL( 2, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100020, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time),\n   //   but call only owes 20 USD,\n   //   so the seller will pay 20 USD and get 2 CORE, since 20 USD worths a little more than 1 CORE according to price 120/11,\n   //   effective price is 20/2 which is not worse than the limit order's desired 33/3.\n   // The remaining USD will be left in the order on the market\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 2, get_balance(seller, core) ); // the seller got 2 CORE\n   BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Due to #338, when matching a smaller taker limit order with a big maker call order,\n *   the small order will be filled at its own price.\n * So unable or no need to reproduce one of the scenarios described in bitshares-core issue #342:\n *   when matching a small taker limit order with a big maker call order,\n *   the small limit order would be paying too much.\n * But we'll just write the test case for #338 here.\n */\nBOOST_AUTO_TEST_CASE( limit_call_rounding_test2 )\n{ try {\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(20), asset(2));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(20));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 20, call.debt.value );\n   BOOST_CHECK_EQUAL( 2, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100020, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at limit order's price 15 USD / 1 CORE,\n   //   so the seller will pay 15 USD and get 1 CORE,\n   //   effective price is 15/1.\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(15), core.amount(1)) ); // the sell order is filled\n   BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled\n   BOOST_CHECK_EQUAL( 20-15, call.debt.value ); // call paid 15 USD\n   BOOST_CHECK_EQUAL( 2-1, call.collateral.value ); // call got 1 CORE\n   BOOST_CHECK_EQUAL( 100020-15, get_balance(seller, bitusd) ); // the seller paid 15 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n\n/***\n * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork:\n *   when matching a small taker limit order with a big maker call order,\n *   the small limit order would be paying minimum required.\n */\nBOOST_AUTO_TEST_CASE( limit_call_rounding_test2_after_hf_342 )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_342_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(20), asset(2));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(20));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 20, call.debt.value );\n   BOOST_CHECK_EQUAL( 2, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100020, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time),\n   //   so the seller will get 1 CORE, and pay 11 USD since 1 CORE worths a little more than 10 USD according to price 120/11,\n   //     and the extra 4 USD will be returned but not overpaid,\n   //     effective price is 11/1 which is close to 120/11.\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(15), core.amount(1)) ); // the sell order is filled\n   BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled\n   BOOST_CHECK_EQUAL( 20-11, call.debt.value ); // call paid 11 USD\n   BOOST_CHECK_EQUAL( 2-1, call.collateral.value ); // call got 1 CORE\n   BOOST_CHECK_EQUAL( 100020-11, get_balance(seller, bitusd) ); // the seller paid 11 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * This test case reproduces one of the scenarios described in bitshares-core issue #342:\n *   when matching a small taker call order with a big maker limit order,\n *   rounding was in favor of the small call order.\n */\nBOOST_AUTO_TEST_CASE( call_limit_rounding_test1 )\n{ try {\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(20), asset(2));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(20));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 20, call.debt.value );\n   BOOST_CHECK_EQUAL( 2, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100020, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   // create a limit order which will be matched later\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   generate_block();\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10);\n   publish_feed( bitusd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // The limit would match with call at limit order's price 33 USD / 3 CORE, but call only owes 20 USD,\n   //   so the seller will pay the whole 20 USD and get 1 CORE, since 20 USD doesn't worth 2 CORE according to price 33/3,\n   //   effective price is 20/1 which is worse than the limit order's desired 33/3.\n   // The remaining USD will be left in the order on the market\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100020-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller_id, core_id) ); // the seller got 1 CORE\n   BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) );\n   BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork:\n *   when matching a small taker call order with a big maker limit order,\n *   rounding in favor of the big limit order.\n */\nBOOST_AUTO_TEST_CASE( call_limit_rounding_test1_after_hf_342 )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_342_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(20), asset(2));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(20));\n   transfer(borrower3, seller, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 20, call.debt.value );\n   BOOST_CHECK_EQUAL( 2, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 100020, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) );\n\n   // create a limit order which will be matched later\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->get_id();\n   BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   generate_block();\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10);\n   publish_feed( bitusd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // The limit would match with call at limit order's price 33 USD / 3 CORE, but call only owes 20 USD,\n   //   so the seller will pay 20 USD and get 2 CORE, since 20 USD worths a little more than 1 CORE according to price 33/3,\n   //   effective price is 20/2 which is not worse than the limit order's desired 33/3.\n   // The remaining USD will be left in the order on the market\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK_EQUAL( 100020-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD\n   BOOST_CHECK_EQUAL( 2, get_balance(seller_id, core_id) ); // the seller got 2 CORE\n   BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) );\n   BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * This test case reproduces one of the scenarios described in bitshares-core issue #342:\n *   when matching a big taker call order with a small maker limit order,\n *   the small limit order would be paying too much.\n */\nBOOST_AUTO_TEST_CASE( call_limit_rounding_test2 )\n{ try {\n   generate_blocks( HARDFORK_555_TIME );\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(50), asset(5));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(50));\n   transfer(borrower3, seller2, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 50, call.debt.value );\n   BOOST_CHECK_EQUAL( 5, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 50, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower, core) );\n\n   // create a buy order which will be matched\n   limit_order_id_type buy_id = create_sell_order(buyer, core.amount(1), bitusd.amount(10))->get_id();\n   BOOST_CHECK_EQUAL( 1, buy_id(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n\n   // create a limit order to fill the buy order, and remaining amounts will be matched later\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(31), core.amount(2))->get_id();\n   BOOST_CHECK( !db.find( buy_id ) ); // the buy order is filled\n   BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) ); // buyer got 10 usd\n   BOOST_CHECK_EQUAL( 21, sell_id(db).for_sale.value ); // remaining amount of sell order is 21\n   BOOST_CHECK_EQUAL( 50-31, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // seller got 1 core\n\n   // create another limit order which will be matched later\n   limit_order_id_type sell_id2 = create_sell_order(seller2, bitusd.amount(14), core.amount(1))->get_id();\n   BOOST_CHECK_EQUAL( 14, sell_id2(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) );\n\n   generate_block();\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10);\n   publish_feed( bitusd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // call will match with the limit orders at limit orders' prices,\n   //   firstly, call will match with sell_id, which has 21 USD remaining, with price 31 USD / 2 CORE,\n   //     so the seller will pay 21 USD, get 1 CORE since 21 USD doesn't worth 2 CORE according to price 31/2,\n   //     effective price is 21/1 which is much bigger than 31/2;\n   //   then, call will match with sell_id2, which has 14 USD remaining, with price 14 USD / 1 CORE,\n   //     so the seller will pay 14 USD, get 1 CORE since 14 USD worths just 1 CORE according to price 14/1\n   BOOST_CHECK( !db.find( sell_id ) ); // the sell order is filled\n   BOOST_CHECK( !db.find( sell_id2 ) ); // the other sell order is filled\n   BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled\n   BOOST_CHECK_EQUAL( 50-14-21, call_id(db).debt.value ); // call paid 14 USD and 21 USD\n   BOOST_CHECK_EQUAL( 5-1-1, call_id(db).collateral.value ); // call got 1 CORE and 1 CORE\n   BOOST_CHECK_EQUAL( 50-31, get_balance(seller_id, bitusd_id) ); // seller paid 31 USD in total\n   BOOST_CHECK_EQUAL( 1+1, get_balance(seller_id, core_id) ); // seller got 1 more CORE\n   BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2_id, bitusd_id) ); // seller2 paid 14 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller2_id, core_id) ); // seller2 got 1 CORE\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) );\n   BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork:\n *   when matching a big taker call order with a small maker limit order,\n *   the small limit order would be paying minimum required.\n */\nBOOST_AUTO_TEST_CASE( call_limit_rounding_test2_after_hf_342 )\n{ try {\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_342_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 );\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700\n   const call_order_object& call = *borrow( borrower, bitusd.amount(50), asset(5));\n   call_order_id_type call_id = call.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500));\n   transfer(borrower, seller, bitusd.amount(50));\n   transfer(borrower3, seller2, bitusd.amount(100000));\n\n   BOOST_CHECK_EQUAL( 50, call.debt.value );\n   BOOST_CHECK_EQUAL( 5, call.collateral.value );\n   BOOST_CHECK_EQUAL( 100000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n\n   BOOST_CHECK_EQUAL( 50, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower, core) );\n\n   // create a buy order which will be matched\n   limit_order_id_type buy_id = create_sell_order(buyer, core.amount(1), bitusd.amount(10))->get_id();\n   BOOST_CHECK_EQUAL( 1, buy_id(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n\n   // create a limit order to fill the buy order, and remaining amounts will be matched later\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(31), core.amount(2))->get_id();\n   BOOST_CHECK( !db.find( buy_id ) ); // the buy order is filled\n   BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) ); // buyer got 10 usd\n   BOOST_CHECK_EQUAL( 21, sell_id(db).for_sale.value ); // remaining amount of sell order is 21\n   BOOST_CHECK_EQUAL( 50-31, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // seller got 1 core\n\n   // create another limit order which will be matched later\n   limit_order_id_type sell_id2 = create_sell_order(seller2, bitusd.amount(14), core.amount(1))->get_id();\n   BOOST_CHECK_EQUAL( 14, sell_id2(db).for_sale.value );\n   BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) );\n\n   generate_block();\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10);\n   publish_feed( bitusd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE\n\n   // call will match with the limit orders at limit orders' prices,\n   //   firstly, call will match with sell_id, which has 21 USD remaining, with price 31 USD / 2 CORE,\n   //     so the seller will get 1 CORE since 21 USD doesn't work 2 CORE according to price 31/2,\n   //       and the seller will pay 16 USD since 1 CORE worths a little more than 15 USD according to price 31/2,\n   //       and the extra 5 USD will be returned to seller since it doesn't worth 1 CORE,\n   //       effective price is 16/1 which is close to 31/2;\n   //   secondly, call will match with sell_id2, which has 14 USD remaining, with price 14 USD / 1 CORE,\n   //     so the seller will get 1 CORE and pay 14 USD since 14 USD just worths 1 CORE according to price 14/1\n   BOOST_CHECK( !db.find( sell_id ) ); // the sell order is filled\n   BOOST_CHECK( !db.find( sell_id2 ) ); // the other sell order is filled\n   BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled\n   BOOST_CHECK_EQUAL( 50-14-16, call_id(db).debt.value ); // call paid 14 USD and 16 USD\n   BOOST_CHECK_EQUAL( 5-1-1, call_id(db).collateral.value ); // call got 1 CORE and 1 CORE\n   BOOST_CHECK_EQUAL( 50-31+(21-16), get_balance(seller_id, bitusd_id) ); // seller paid 31 USD then get refunded 5 USD\n   BOOST_CHECK_EQUAL( 1+1, get_balance(seller_id, core_id) ); // seller got 1 more CORE\n   BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2_id, bitusd_id) ); // seller2 paid 14 USD\n   BOOST_CHECK_EQUAL( 1, get_balance(seller2_id, core_id) ); // seller2 got 1 CORE\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) );\n   BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/market_tests.cpp",
    "content": "/*\n * Copyright (c) 2017 Peter Conrad, and other contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(market_tests, database_fixture)\n\n/***\n * Reproduce bitshares-core issue #338 #343 #453 #606 #625 #649\n */\nBOOST_AUTO_TEST_CASE(issue_338_etc)\n{ try {\n   generate_blocks(HARDFORK_615_TIME); // get around Graphene issue #615 feed expiration bug\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This order slightly below the call price will not be matched #606\n   limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(7), core.amount(59))->get_id();\n   // This order above the MSSP will not be matched\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(60))->get_id();\n\n   cancel_limit_order( sell_med(db) );\n   cancel_limit_order( sell_high(db) );\n   cancel_limit_order( sell_low(db) );\n\n   // current implementation: an incoming limit order will be filled at the\n   // requested price #338\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) );\n   BOOST_CHECK_EQUAL( 993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 60, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 993, call.debt.value );\n   BOOST_CHECK_EQUAL( 14940, call.collateral.value );\n\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->get_id();\n   // margin call takes precedence\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) );\n   BOOST_CHECK_EQUAL( 986, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 120, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 986, call.debt.value );\n   BOOST_CHECK_EQUAL( 14880, call.collateral.value );\n\n   limit_order_id_type buy_med = create_sell_order(buyer, asset(105), bitusd.amount(10))->get_id();\n   // margin call takes precedence\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(70)) );\n   BOOST_CHECK_EQUAL( 979, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 190, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 979, call.debt.value );\n   BOOST_CHECK_EQUAL( 14810, call.collateral.value );\n\n   limit_order_id_type buy_high = create_sell_order(buyer, asset(115), bitusd.amount(10))->get_id();\n   // margin call still has precedence (!) #625\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(77)) );\n   BOOST_CHECK_EQUAL( 972, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 267, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 972, call.debt.value );\n   BOOST_CHECK_EQUAL( 14733, call.collateral.value );\n\n   cancel_limit_order( buy_high(db) );\n   cancel_limit_order( buy_med(db) );\n   cancel_limit_order( buy_low(db) );\n\n   // call with more usd\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(7700)) );\n   BOOST_CHECK_EQUAL( 272, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 7967, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 272, call.debt.value );\n   BOOST_CHECK_EQUAL( 7033, call.collateral.value );\n\n   // at this moment, collateralization of call is 7033 / 272 = 25.8\n   // collateralization of call2 is 15500 / 1000 = 15.5\n   // collateralization of call3 is 16000 / 1000 = 16\n\n   // call more, still matches with the first call order #343\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(10), core.amount(110)) );\n   BOOST_CHECK_EQUAL( 262, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 262, call.debt.value );\n   BOOST_CHECK_EQUAL( 6923, call.collateral.value );\n\n   // at this moment, collateralization of call is 6923 / 262 = 26.4\n   // collateralization of call2 is 15500 / 1000 = 15.5\n   // collateralization of call3 is 16000 / 1000 = 16\n\n   // force settle\n   force_settle( seller, bitusd.amount(10) );\n   BOOST_CHECK_EQUAL( 252, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 262, call.debt.value );\n   BOOST_CHECK_EQUAL( 6923, call.collateral.value );\n\n   // generate blocks to let the settle order execute (price feed will expire after it)\n   generate_blocks( HARDFORK_615_TIME + fc::hours(25) );\n   // call2 get settled #343\n   BOOST_CHECK_EQUAL( 252, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 8177, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 262, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 6923, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 990, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15400, call2_id(db).collateral.value );\n\n   set_expiration( db, trx );\n   update_feed_producers( usd_id(db), {feedproducer_id} );\n\n   // at this moment, collateralization of call is 8177 / 252 = 32.4\n   // collateralization of call2 is 15400 / 990 = 15.5\n   // collateralization of call3 is 16000 / 1000 = 16\n\n   // adjust price feed to get call2 into black swan territory, but not the first call order\n   current_feed.settlement_price = asset(1, usd_id) / asset(20, core_id);\n   publish_feed( usd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 1/20, mssp = 1/22\n\n   // black swan event doesn't occur #649\n   BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n\n   // generate a block\n   generate_block();\n\n   set_expiration( db, trx );\n   update_feed_producers( usd_id(db), {feedproducer_id} );\n\n   // adjust price feed back\n   current_feed.settlement_price = asset(1, usd_id) / asset(10, core_id);\n   publish_feed( usd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   transfer(borrower2_id, seller_id, asset(1000, usd_id));\n   transfer(borrower3_id, seller_id, asset(1000, usd_id));\n\n   // Re-create sell_low, slightly below the call price, will not be matched, will expire soon\n   sell_low = create_sell_order(seller_id(db), asset(7, usd_id), asset(59), db.head_block_time()+fc::seconds(300) )->get_id();\n   // This would match but is blocked by sell_low, it has an amount same as call's debt which will be full filled later\n   sell_med = create_sell_order(seller_id(db), asset(262, usd_id), asset(2620))->get_id(); // 1/10\n   // Another big order above sell_med, blocked\n   limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->get_id(); // 1/10.1\n   // Another small order above sell_med2, blocked\n   limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->get_id(); // 1/10.2\n\n   // generate a block, sell_low will expire\n   BOOST_TEST_MESSAGE( \"Expire sell_low\" );\n   generate_blocks( HARDFORK_615_TIME + fc::hours(26) );\n   BOOST_CHECK( db.find( sell_low ) == nullptr );\n\n   // #453 multiple order matching issue occurs\n   BOOST_CHECK( db.find( sell_med ) == nullptr ); // sell_med get filled\n   BOOST_CHECK( db.find( sell_med2 ) != nullptr ); // sell_med2 is still there\n   BOOST_CHECK( db.find( sell_med3 ) == nullptr ); // sell_med3 get filled\n   BOOST_CHECK( db.find( call_id ) == nullptr ); // the first call order get filled\n   BOOST_CHECK( db.find( call2_id ) == nullptr ); // the second call order get filled\n   BOOST_CHECK( db.find( call3_id ) != nullptr ); // the third call order is still there\n\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #338 #343 #606 #625 #649\n */\nBOOST_AUTO_TEST_CASE(hardfork_core_338_test)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else if(hf1270)\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_343_TIME - mi);\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This sell order above MSSP will not be matched with a call\n   BOOST_CHECK( create_sell_order(seller, bitusd.amount(7), core.amount(78)) != nullptr );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->get_id();\n   // This buy order at MSSP will be matched only if no margin call (margin call takes precedence)\n   limit_order_id_type buy_med = create_sell_order(buyer, asset(110), bitusd.amount(10))->get_id();\n   // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence)\n   limit_order_id_type buy_high = create_sell_order(buyer, asset(111), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 90 - 110 - 111, get_balance(buyer, core) );\n\n   // This order slightly below the call price will be matched: #606 fixed\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(5900) ) );\n\n   // firstly it will match with buy_high, at buy_high's price: #625 fixed\n   BOOST_CHECK( !db.find( buy_high ) );\n   BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 110 );\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 90 );\n\n   // buy_high pays 111 CORE, receives 10 USD goes to buyer's balance\n   BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 90 - 110 - 111, get_balance(buyer, core) );\n   // sell order pays 10 USD, receives 111 CORE, remaining 690 USD for sale, still at price 7/59\n\n   // then it will match with call, at mssp: 1/11 = 690/7590 : #338 fixed\n   BOOST_CHECK_EQUAL( 2293, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 7701, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 310, call.debt.value );\n   BOOST_CHECK_EQUAL( 7410, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n\n   // call's call_price will be updated after the match, to 741/31/1.75 CORE/USD = 2964/217\n   // it's above settlement price (10/1) so won't be margin called again\n   if(!hf1270 && !hf2481) // can use call price only if we are before hf1270\n      BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price );\n\n   // This would match with call before, but would match with call2 after #343 fixed\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6000) ) );\n   BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 110 );\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 90 );\n\n   // fill price would be mssp: 1/11 = 700/7700 : #338 fixed\n   BOOST_CHECK_EQUAL( 1593, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 15401, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 310, call.debt.value );\n   BOOST_CHECK_EQUAL( 7410, call.collateral.value );\n   BOOST_CHECK_EQUAL( 300, call2.debt.value );\n   BOOST_CHECK_EQUAL( 7800, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n   // call2's call_price will be updated after the match, to 78/3/1.75 CORE/USD = 312/21\n   if(!hf1270 && !hf2481) // can use call price only if we are before hf1270\n      BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price );\n   // it's above settlement price (10/1) so won't be margin called\n\n   // at this moment, collateralization of call is 7410 / 310 = 23.9\n   // collateralization of call2 is 7800 / 300 = 26\n   // collateralization of call3 is 16000 / 1000 = 16\n\n   // force settle\n   force_settle( seller, bitusd.amount(10) );\n\n   BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) );\n   if( hf2481 ) // force settle matches with margin calls, at mssp 1/11\n      BOOST_CHECK_EQUAL( 15511, get_balance(seller, core) ); // 15401 + 10 * 11\n   else\n      BOOST_CHECK_EQUAL( 15401, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 310, call.debt.value );\n   BOOST_CHECK_EQUAL( 7410, call.collateral.value );\n   BOOST_CHECK_EQUAL( 300, call2.debt.value );\n   BOOST_CHECK_EQUAL( 7800, call2.collateral.value );\n   if( hf2481 ) // force settle matches with margin calls, at mssp 1/11\n   {\n      BOOST_CHECK_EQUAL( 990, call3.debt.value ); // 1000 - 10\n      BOOST_CHECK_EQUAL( 15890, call3.collateral.value ); // 16000 - 10 * 11\n   }\n   else\n   {\n      BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n      BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n   }\n\n   // generate blocks to let the settle order execute (only before hf2481) (price feed will expire after it)\n   generate_block();\n   generate_blocks( db.head_block_time() + fc::hours(24) );\n\n   // if before hf2481, call3 get settled, at settlement price 1/10: #343 fixed\n   // else matched at above step already\n   BOOST_CHECK_EQUAL( 1583, get_balance(seller_id, usd_id) );\n   if( hf2481 )\n      BOOST_CHECK_EQUAL( 15511, get_balance(seller_id, core_id) ); // no change\n   else\n      BOOST_CHECK_EQUAL( 15501, get_balance(seller_id, core_id) ); // 15401 + 10 * 10\n   BOOST_CHECK_EQUAL( 310, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 7410, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 300, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 7800, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 990, call3_id(db).debt.value );\n   if( hf2481 )\n      BOOST_CHECK_EQUAL( 15890, call3_id(db).collateral.value );\n   else\n      BOOST_CHECK_EQUAL( 15900, call3_id(db).collateral.value ); // 16000 - 10 * 10\n\n   set_expiration( db, trx );\n   update_feed_producers( usd_id(db), {feedproducer_id} );\n\n   // at this moment, collateralization of call is 7410 / 310 = 23.9\n   // collateralization of call2 is 7800 / 300 = 26\n   // collateralization of call3 is 15900 / 990 = 16.06\n\n   // adjust price feed to get call3 into black swan territory, but not the other call orders\n   // Note: after hard fork, black swan should occur when callateralization < mssp, but not at < feed\n   current_feed.settlement_price = asset(1, usd_id) / asset(16, core_id);\n   publish_feed( usd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 1/16, mssp = 10/176\n\n   // black swan event will occur: #649 fixed\n   BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n   // short positions will be closed\n   BOOST_CHECK( !db.find( call_id ) );\n   BOOST_CHECK( !db.find( call2_id ) );\n   BOOST_CHECK( !db.find( call3_id ) );\n\n   // generate a block\n   generate_block();\n\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #453: multiple limit order filling issue\n */\nBOOST_AUTO_TEST_CASE(hardfork_core_453_test)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else if(hf1270)\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_343_TIME - mi);\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // no margin call so far\n\n   // This order would match call when it's margin called, it has an amount same as call's debt which will be full filled later\n   limit_order_id_type sell_med = create_sell_order(seller_id(db), asset(1000, usd_id), asset(10000))->get_id(); // 1/10\n   // Another big order above sell_med, amount bigger than call2's debt\n   limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->get_id(); // 1/10.1\n   // Another small order above sell_med2\n   limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->get_id(); // 1/10.2\n\n   // adjust price feed to get the call orders  into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // Fixed #453 multiple order matching issue occurs\n   BOOST_CHECK( !db.find( sell_med ) ); // sell_med get filled\n   BOOST_CHECK( !db.find( sell_med2 ) ); // sell_med2 get filled\n   BOOST_CHECK( !db.find( sell_med3 ) ); // sell_med3 get filled\n   BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled\n   BOOST_CHECK( !db.find( call2_id ) ); // the second call order get filled\n   BOOST_CHECK( db.find( call3_id ) ); // the third call order is still there\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Tests (big) limit order matching logic after #625 got fixed\n */\nBOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else if(hf1270)\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_625_TIME - mi);\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(buyer2)(buyer3)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, buyer2_id, asset(init_balance));\n   transfer(committee_account, buyer3_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000));\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This sell order above MSSP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->get_id();\n   // This buy order at MSSP will be matched only if no margin call (margin call takes precedence)\n   limit_order_id_type buy_med = create_sell_order(buyer2, asset(11000), bitusd.amount(1000))->get_id();\n   // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence)\n   limit_order_id_type buy_high = create_sell_order(buyer3, asset(111), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer3, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(buyer2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );\n\n   // Create a big sell order slightly below the call price, will be matched with several orders\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700*4), core.amount(5900*4) ) );\n\n   // firstly it will match with buy_high, at buy_high's price\n   BOOST_CHECK( !db.find( buy_high ) );\n   // buy_high pays 111 CORE, receives 10 USD goes to buyer3's balance\n   BOOST_CHECK_EQUAL( 10, get_balance(buyer3, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );\n\n   // then it will match with call, at mssp: 1/11 = 1000/11000\n   BOOST_CHECK( !db.find( call_id ) );\n   // call pays 11000 CORE, receives 1000 USD to cover borrower's position, remaining CORE goes to borrower's balance\n   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // then it will match with call2, at mssp: 1/11 = 1000/11000\n   BOOST_CHECK( !db.find( call2_id ) );\n   // call2 pays 11000 CORE, receives 1000 USD to cover borrower2's position, remaining CORE goes to borrower2's balance\n   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n\n   // then it will match with buy_med, at buy_med's price. Since buy_med is too big, it's partially filled.\n   // buy_med receives the remaining USD of sell order, minus market fees, goes to buyer2's balance\n   BOOST_CHECK_EQUAL( 783, get_balance(buyer2, bitusd) ); // 700*4-10-1000-1000=790, minus 1% market fee 790*100/10000=7\n   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(buyer2, core) );\n   // buy_med pays at 1/11 = 790/8690\n   BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 11000-8690 );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4\n   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) ); // 111 + 11000 + 11000 + 8690\n\n   // Cancel buy_med\n   cancel_limit_order( buy_med(db) );\n   BOOST_CHECK( !db.find( buy_med ) );\n   BOOST_CHECK_EQUAL( 783, get_balance(buyer2, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 8690, get_balance(buyer2, core) );\n\n   // Create another sell order slightly below the call price, won't fill\n   limit_order_id_type sell_med = create_sell_order( seller, bitusd.amount(7), core.amount(59) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_med )->for_sale.value, 7 );\n   // check seller balance\n   BOOST_CHECK_EQUAL( 193-7, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #453 #606: multiple order matching without black swan, multiple bitassets\n */\nBOOST_AUTO_TEST_CASE(hard_fork_453_cross_test)\n{ try { // create orders before hard fork, which will be matched on hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& biteur = create_bitasset(\"EURBIT\", feedproducer_id);\n   const auto& bitcny = create_bitasset(\"CNYBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type eur_id = biteur.get_id();\n   asset_id_type cny_id = bitcny.get_id();\n   asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n   update_feed_producers( biteur, {feedproducer.get_id()} );\n   update_feed_producers( bitcny, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   current_feed.settlement_price = biteur.amount( 1 ) / core.amount(5);\n   publish_feed( biteur, feedproducer, current_feed );\n   current_feed.settlement_price = bitcny.amount( 1 ) / core.amount(5);\n   publish_feed( bitcny, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call_usd = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_usd_id = call_usd.get_id();\n   const call_order_object& call_eur = *borrow( borrower, biteur.amount(1000), asset(15000));\n   call_order_id_type call_eur_id = call_eur.get_id();\n   const call_order_object& call_cny = *borrow( borrower, bitcny.amount(1000), asset(15000));\n   call_order_id_type call_cny_id = call_cny.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call_usd2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call_usd2_id = call_usd2.get_id();\n   const call_order_object& call_eur2 = *borrow( borrower2, biteur.amount(1000), asset(15500));\n   call_order_id_type call_eur2_id = call_eur2.get_id();\n   const call_order_object& call_cny2 = *borrow( borrower2, bitcny.amount(1000), asset(15500));\n   call_order_id_type call_cny2_id = call_cny2.get_id();\n   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7\n   const call_order_object& call_usd3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));\n   call_order_id_type call_usd3_id = call_usd3.get_id();\n   const call_order_object& call_eur3 = *borrow( borrower3, biteur.amount(1000), asset(16000));\n   call_order_id_type call_eur3_id = call_eur3.get_id();\n   const call_order_object& call_cny3 = *borrow( borrower3, bitcny.amount(1000), asset(16000));\n   call_order_id_type call_cny3_id = call_cny3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n   transfer(borrower, seller, biteur.amount(1000));\n   transfer(borrower2, seller, biteur.amount(1000));\n   transfer(borrower3, seller, biteur.amount(1000));\n   transfer(borrower, seller, bitcny.amount(1000));\n   transfer(borrower2, seller, bitcny.amount(1000));\n   transfer(borrower3, seller, bitcny.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call_usd.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_usd.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call_usd2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call_usd2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call_usd3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call_usd3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 1000, call_eur.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_eur.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call_eur2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call_eur2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call_eur3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call_eur3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, biteur) );\n   BOOST_CHECK_EQUAL( 1000, call_cny.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_cny.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call_cny2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call_cny2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call_cny3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call_cny3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitcny) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   current_feed.settlement_price = biteur.amount( 1 ) / core.amount(10);\n   publish_feed( biteur, feedproducer, current_feed );\n   current_feed.settlement_price = bitcny.amount( 1 ) / core.amount(10);\n   publish_feed( bitcny, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This order below the call price will not be matched before hard fork: 1/8 #606\n   limit_order_id_type sell_usd_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->get_id();\n   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606\n   limit_order_id_type sell_usd_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->get_id();\n   // This order above the MSSP will not be matched before hard fork\n   limit_order_id_type sell_usd_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_usd_med = create_sell_order(seller, bitusd.amount(700), core.amount(6400))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_usd_med2 = create_sell_order(seller, bitusd.amount(7), core.amount(65))->get_id();\n\n   // This order below the call price will not be matched before hard fork: 1/8 #606\n   limit_order_id_type sell_eur_low = create_sell_order(seller, biteur.amount(1000), core.amount(7000))->get_id();\n   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606\n   limit_order_id_type sell_eur_low2 = create_sell_order(seller, biteur.amount(1007), core.amount(8056))->get_id();\n   // This order above the MSSP will not be matched before hard fork\n   limit_order_id_type sell_eur_high = create_sell_order(seller, biteur.amount(7), core.amount(78))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_eur_med = create_sell_order(seller, biteur.amount(700), core.amount(6400))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_eur_med2 = create_sell_order(seller, biteur.amount(7), core.amount(65))->get_id();\n\n   // This order below the call price will not be matched before hard fork: 1/8 #606\n   limit_order_id_type sell_cny_low = create_sell_order(seller, bitcny.amount(1000), core.amount(7000))->get_id();\n   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606\n   limit_order_id_type sell_cny_low2 = create_sell_order(seller, bitcny.amount(1007), core.amount(8056))->get_id();\n   // This order above the MSSP will not be matched before hard fork\n   limit_order_id_type sell_cny_high = create_sell_order(seller, bitcny.amount(7), core.amount(78))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_cny_med = create_sell_order(seller, bitcny.amount(700), core.amount(6400))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_cny_med2 = create_sell_order(seller, bitcny.amount(7), core.amount(65))->get_id();\n\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, eur_id) );\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, cny_id) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // generate a block to include operations above\n   generate_block();\n   // go over the hard fork, make sure feed doesn't expire\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // sell_low and call should get matched first\n   BOOST_CHECK( !db.find( sell_usd_low ) );\n   BOOST_CHECK( !db.find( call_usd_id ) );\n   // sell_low2 and call2 should get matched\n   BOOST_CHECK( !db.find( call_usd2_id ) );\n   // sell_low2 and call3 should get matched: fixed #453\n   BOOST_CHECK( !db.find( sell_usd_low2 ) );\n   // sell_med and call3 should get matched\n   BOOST_CHECK( !db.find( sell_usd_med ) );\n   // call3 now is not at margin call state, so sell_med2 won't get matched\n   BOOST_CHECK_EQUAL( db.find( sell_usd_med2 )->for_sale.value, 7 );\n   // sell_high should still be there, didn't match anything\n   BOOST_CHECK_EQUAL( db.find( sell_usd_high )->for_sale.value, 7 );\n\n   // sell_low and call should get matched first\n   BOOST_CHECK( !db.find( sell_eur_low ) );\n   BOOST_CHECK( !db.find( call_eur_id ) );\n   // sell_low2 and call2 should get matched\n   BOOST_CHECK( !db.find( call_eur2_id ) );\n   // sell_low2 and call3 should get matched: fixed #453\n   BOOST_CHECK( !db.find( sell_eur_low2 ) );\n   // sell_med and call3 should get matched\n   BOOST_CHECK( !db.find( sell_eur_med ) );\n   // call3 now is not at margin call state, so sell_med2 won't get matched\n   BOOST_CHECK_EQUAL( db.find( sell_eur_med2 )->for_sale.value, 7 );\n   // sell_high should still be there, didn't match anything\n   BOOST_CHECK_EQUAL( db.find( sell_eur_high )->for_sale.value, 7 );\n\n   // sell_low and call should get matched first\n   BOOST_CHECK( !db.find( sell_cny_low ) );\n   BOOST_CHECK( !db.find( call_cny_id ) );\n   // sell_low2 and call2 should get matched\n   BOOST_CHECK( !db.find( call_cny2_id ) );\n   // sell_low2 and call3 should get matched: fixed #453\n   BOOST_CHECK( !db.find( sell_cny_low2 ) );\n   // sell_med and call3 should get matched\n   BOOST_CHECK( !db.find( sell_cny_med ) );\n   // call3 now is not at margin call state, so sell_med2 won't get matched\n   BOOST_CHECK_EQUAL( db.find( sell_cny_med2 )->for_sale.value, 7 );\n   // sell_high should still be there, didn't match anything\n   BOOST_CHECK_EQUAL( db.find( sell_cny_high )->for_sale.value, 7 );\n\n   // all match price would be limit order price\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, eur_id) );\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, cny_id) );\n   BOOST_CHECK_EQUAL( (7000+8056+6400)*3, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 1000-7-700, call_usd3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 16000-56-6400, call_usd3_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000-7-700, call_eur3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 16000-56-6400, call_eur3_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000-7-700, call_cny3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 16000-56-6400, call_cny3_id(db).collateral.value );\n   // call3's call_price should be updated: 9544/293/1.75 = 9544*4 / 293*7 = 38176/2051 CORE/USD\n   BOOST_CHECK( price(asset(38176),asset(2051,usd_id)) == call_usd3_id(db).call_price );\n   BOOST_CHECK( price(asset(38176),asset(2051,eur_id)) == call_eur3_id(db).call_price );\n   BOOST_CHECK( price(asset(38176),asset(2051,cny_id)) == call_cny3_id(db).call_price );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #338 #453 #606: multiple order matching with black swan\n */\nBOOST_AUTO_TEST_CASE(hard_fork_338_cross_test)\n{ try { // create orders before hard fork, which will be matched on hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_338_TIME - mi); // assume all hard forks occur at same time\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));\n   call_order_id_type call3_id = call3.get_id();\n   // create yet another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7\n   const call_order_object& call4 = *borrow( borrower4, bitusd.amount(1000), asset(20000));\n   call_order_id_type call4_id = call4.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This order below the call price will not be matched before hard fork: 1/8 #606\n   limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->get_id();\n   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606\n   limit_order_id_type sell_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->get_id();\n   // This would match but is blocked by sell_low?! #606\n   limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(64))->get_id();\n\n   // adjust price feed to get call_order into black swan territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(16);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/16, mssp = 10/176\n\n   // due to sell_low, black swan won't occur\n   BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // generate a block to include operations above\n   generate_block();\n   // go over the hard fork, make sure feed doesn't expire\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // sell_low and call should get matched first\n   BOOST_CHECK( !db.find( sell_low ) );\n   BOOST_CHECK( !db.find( call_id ) );\n   // sell_low2 and call2 should get matched\n   BOOST_CHECK( !db.find( call2_id ) );\n   // sell_low2 and call3 should get matched: fixed #453\n   BOOST_CHECK( !db.find( sell_low2 ) );\n   // sell_med and call3 should get matched\n   BOOST_CHECK( !db.find( sell_med ) );\n\n   // at this moment,\n   // collateralization of call3 is (16000-56-64) / (1000-7-7) = 15880/986 = 16.1, it's > 16 but < 17.6\n   // although there is no sell order, it should trigger a black swan event right away,\n   // because after hard fork new limit order won't trigger black swan event\n   BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n   BOOST_CHECK( !db.find( call3_id ) );\n   BOOST_CHECK( !db.find( call4_id ) );\n\n   // since 16.1 > 16, global settlement should at feed price 16/1\n   // so settlement fund should be 986*16 + 1000*16\n   BOOST_CHECK_EQUAL( 1986*16, usd_id(db).bitasset_data(db).settlement_fund.value );\n   // global settlement price should be 16/1, since no rounding here\n   BOOST_CHECK( price(asset(1,usd_id),asset(16) ) == usd_id(db).bitasset_data(db).settlement_price );\n\n   BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 7000+8056+64, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3_id, usd_id) );\n   BOOST_CHECK_EQUAL( init_balance-16000+15880-986*16, get_balance(borrower3_id, core_id) );\n   BOOST_CHECK_EQUAL( 1000, get_balance(borrower4_id, usd_id) );\n   BOOST_CHECK_EQUAL( init_balance-1000*16, get_balance(borrower4_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #649: Black swan detection fetch call order by call_price but not collateral ratio\n */\nBOOST_AUTO_TEST_CASE(hard_fork_649_cross_test)\n{ try { // create orders before hard fork, which will be matched on hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This would match with call at price 707/6464\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(707), core.amount(6464)) );\n   BOOST_CHECK_EQUAL( 3000-707, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 6464, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 293, call.debt.value );\n   BOOST_CHECK_EQUAL( 8536, call.collateral.value );\n\n   // at this moment,\n   // collateralization of call is 8536 / 293 = 29.1\n   // collateralization of call2 is 15500 / 1000 = 15.5\n   // collateralization of call3 is 16000 / 1000 = 16\n\n   generate_block();\n   set_expiration( db, trx );\n   update_feed_producers( usd_id(db), {feedproducer_id} );\n\n   // adjust price feed to get call_order into black swan territory\n   current_feed.settlement_price = price(asset(1,usd_id) / asset(20));\n   publish_feed( usd_id(db), feedproducer_id(db), current_feed );\n   // settlement price = 1/20, mssp = 1/22\n\n   // due to #649, black swan won't occur\n   BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n\n   // generate a block to include operations above\n   generate_block();\n   BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n   // go over the hard fork, make sure feed doesn't expire\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   // a black swan event should occur\n   BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n   BOOST_CHECK( !db.find( call_id ) );\n   BOOST_CHECK( !db.find( call2_id ) );\n   BOOST_CHECK( !db.find( call3_id ) );\n\n   // since least collateral ratio 15.5 < 20, global settlement should execute at price = least collateral ratio 15.5/1\n   // so settlement fund should be 15500 + 15500 + round_up(15.5 * 293)\n   BOOST_CHECK_EQUAL( 15500*2 + (293 * 155 + 9) / 10, usd_id(db).bitasset_data(db).settlement_fund.value );\n   // global settlement price should be settlement_fund/(2000+293), but not 15.5/1 due to rounding\n   BOOST_CHECK( price(asset(2293,usd_id),asset(15500*2+(293*155+9)/10) ) == usd_id(db).bitasset_data(db).settlement_price );\n\n   BOOST_CHECK_EQUAL( 3000-707, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 6464, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, usd_id) );\n   BOOST_CHECK_EQUAL( init_balance-6464-(293*155+9)/10, get_balance(borrower_id, core_id) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2_id, usd_id) );\n   BOOST_CHECK_EQUAL( init_balance-15500, get_balance(borrower2_id, core_id) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3_id, usd_id) );\n   BOOST_CHECK_EQUAL( init_balance-15500, get_balance(borrower3_id, core_id) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Fixed bitshares-core issue #343: change sorting of call orders when matching against limit order\n */\nBOOST_AUTO_TEST_CASE(hard_fork_343_cross_test)\n{ try { // create orders before hard fork, which will be matched on hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n   asset_id_type core_id = core.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   transfer(committee_account, borrower4_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 350% collateral, call price is 17.5/1.75 CORE/USD = 77/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(17500));\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This would match with call at price 700/6400\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6400)) );\n   BOOST_CHECK_EQUAL( 3000-700, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 6400, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 300, call.debt.value );\n   BOOST_CHECK_EQUAL( 8600, call.collateral.value );\n\n   // at this moment,\n   // collateralization of call is 8600 / 300 = 28.67\n   // collateralization of call2 is 15500 / 1000 = 15.5\n   // collateralization of call3 is 17500 / 1000 = 17.5\n\n   // generate a block to include operations above\n   generate_block();\n   // go over the hard fork, make sure feed doesn't expire\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   // This will match with call2 at price 7/77 (#343 fixed)\n   BOOST_CHECK( !create_sell_order(seller_id(db), asset(7*50,usd_id), asset(65*50)) );\n   BOOST_CHECK_EQUAL( 3000-700-7*50, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 6400+77*50, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 300, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000-7*50, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15500-77*50, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 17500, call3_id(db).collateral.value );\n\n   // at this moment,\n   // collateralization of call is 8600 / 300 = 28.67\n   // collateralization of call2 is 11650 / 650 = 17.9\n   // collateralization of call3 is 17500 / 1000 = 17.5\n\n   // This will match with call3 at price 7/77 (#343 fixed)\n   BOOST_CHECK( !create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) );\n   BOOST_CHECK_EQUAL( 3000-700-7*50-7, get_balance(seller_id, usd_id) );\n   BOOST_CHECK_EQUAL( 6400+77*50+77, get_balance(seller_id, core_id) );\n   BOOST_CHECK_EQUAL( 300, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000-7*50, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15500-77*50, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000-7, call3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 17500-77, call3_id(db).collateral.value );\n\n   // at this moment,\n   // collateralization of call is 8600 / 300 = 28.67\n   // collateralization of call2 is 11650 / 650 = 17.9\n   // collateralization of call3 is 17423 / 993 = 17.55\n\n   // no more margin call now\n   BOOST_CHECK( create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Tests a scenario that GS may occur when there is no sufficient collateral to pay margin call fee,\n * but GS won't occur if no need to pay margin call fee.\n */\nBOOST_AUTO_TEST_CASE(mcfr_blackswan_test)\n{ try {\n   // Proceeds to the bsip-74 hard fork time\n   generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 80;\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(20000));\n   call_order_id_type call2_id = call2.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 20000, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // No margin call at this moment\n\n   // This order is sufficient to close the first debt position and no GS if margin call fee ratio is 0\n   limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(1000), core.amount(14900))->get_id();\n\n   BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value );\n\n   BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 20000, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into black swan territory\n   BOOST_TEST_MESSAGE( \"Trying to trigger GS\" );\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(18);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/18, mssp = 10/198\n\n   // GS occurs even when there is a good sell order\n   BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n   BOOST_CHECK( !db.find( call_id ) );\n   BOOST_CHECK( !db.find( call2_id ) );\n   // GS price is 1/18, but the first call order has only 15000 thus capped\n   BOOST_CHECK_EQUAL( 15000 + 18000, usd_id(db).bitasset_data(db).settlement_fund.value );\n\n   // the sell order does not change\n   BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value );\n\n   // generate a block to include operations above\n   BOOST_TEST_MESSAGE( \"Generating a new block\" );\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Tests a scenario after the core-2481 hard fork that GS may occur when there is no sufficient collateral\n * to pay margin call fee, but GS won't occur if no need to pay margin call fee. The amount gathered to the\n * global settlement fund will be different than the case before the hard fork.\n */\nBOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481)\n{ try {\n   // Proceeds to the core-2481 hard fork time\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 80;\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(20000));\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(40000));\n   call_order_id_type call3_id = call3.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 20000, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 40000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // No margin call at this moment\n\n   // This order is sufficient to close the first debt position and no GS if margin call fee ratio is 0\n   limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(1000), core.amount(14900))->get_id();\n\n   BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value );\n\n   BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 20000, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 40000, call3_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to get call_order into black swan territory\n   BOOST_TEST_MESSAGE( \"Trying to trigger GS\" );\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(18);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/18, mssp = 10/198\n\n   // GS occurs even when there is a good sell order\n   BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n   BOOST_CHECK( !db.find( call_id ) );\n   BOOST_CHECK( !db.find( call2_id ) );\n   BOOST_CHECK( !db.find( call3_id ) );\n\n   // after the core-2481 hard fork, GS price is not 1/18.\n   // * the first call order would pay all collateral.\n   //   due to margin call fee, not all collateral enters global settlement fund, but\n   //   fund_receives = round_up(15000 / 1.1) = 13637\n   //   fees = 15000 - 13637 = 1363\n   // * the second call order was in margin call territory too, so it would pay a premium and margin call fee.\n   //   fund_receives = 13637\n   //   fees = 15000 - 13637 = 1363\n   //   the rest ( 20000 - 15000 = 5000 ) returns to borrower2\n   // * the third call order was not in margin call territory, so no premium or margin call fee.\n   //   fund_receives = round_up(15000 / 1.1) = 13637\n   // GS price is 1/18, but the first call order has only 15000 thus capped\n   BOOST_CHECK_EQUAL( 13637 * 3, usd_id(db).bitasset_data(db).settlement_fund.value );\n   BOOST_CHECK_EQUAL( 1363 * 2, usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value );\n\n   // the sell order does not change\n   BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value );\n\n   // generate a block to include operations above\n   BOOST_TEST_MESSAGE( \"Generating a new block\" );\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Tests GS price\n */\nBOOST_AUTO_TEST_CASE(gs_price_test)\n{ try {\n   // Proceeds to a desired hard fork time\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   if( hf2481 )\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(40000));\n   call_order_id_type call2_id = call2.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 40000, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // No margin call at this moment\n\n   // This order is right at MSSP of the first debt position\n   limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(2000), core.amount(30000))->get_id();\n\n   BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value );\n\n   BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // adjust price feed to a value so that mssp is equal to call's collateralization\n   current_feed.settlement_price = bitusd.amount( 11 ) / core.amount(150);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 11/150, mssp = (11/150)*(10/11) = 1/15\n\n   if( !hf2481 )\n   {\n      // GS occurs\n      BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK( !db.find( call2_id ) );\n      // sell order did not change\n      BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value );\n   }\n   else\n   {\n      // GS does not occur, call got filled\n      BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find( call_id ) );\n\n      // sell order got half-filled\n      BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value );\n\n      // call2 did not change\n      BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value );\n   }\n\n   // generate a block to include operations above\n   BOOST_TEST_MESSAGE( \"Generating a new block\" );\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(gs_price_test_after_hf2481)\n{\n   hf2481 = true;\n   INVOKE(gs_price_test);\n}\n\n/***\n * Tests a scenario about rounding errors related to margin call fee\n */\nBOOST_AUTO_TEST_CASE(mcfr_rounding_test)\n{ try {\n\n   if(hf2481)\n   {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   }\n   else\n   {\n      // Proceeds to the bsip-74 hard fork time\n      generate_blocks(HARDFORK_CORE_BSIP74_TIME);\n   }\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer)(feeder2)(feeder3));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n   asset_id_type usd_id = bitusd.get_id();\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n\n   {\n      // set margin call fee ratio\n      asset_update_bitasset_operation uop;\n      uop.issuer = usd_id(db).issuer;\n      uop.asset_to_update = usd_id;\n      uop.new_options = usd_id(db).bitasset_data(db).options;\n      uop.new_options.extensions.value.margin_call_fee_ratio = 70;\n      uop.new_options.feed_lifetime_sec = 86400;\n      uop.new_options.minimum_feeds = 1;\n\n      trx.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX(db, trx, ~0);\n   }\n\n   update_feed_producers( bitusd, {feedproducer_id, feeder2_id, feeder3_id} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));\n   call_order_id_type call_id = call.get_id();\n   // create another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(40000));\n   call_order_id_type call2_id = call2.get_id();\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 40000, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower2, core) );\n\n   // No margin call at this moment\n\n   // This order would be matched later\n   limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(1100), core.amount(15451))->get_id();\n   // call_pays_price = (15451 / 1100) * 1100 / (1100-70) = 15451 / 1030\n   // debt * call_pays_price = 1000 * 15451 / 1030 = 15000.9\n\n   BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value );\n\n   BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n   BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value );\n   BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower2, core) );\n\n   // Tring to adjust price feed to get call_order into margin call territory\n   BOOST_TEST_MESSAGE( \"Trying to trigger a margin call\" );\n   auto feed2 = current_feed;\n   feed2.settlement_price = bitusd.amount( 1 ) / core.amount(18);\n\n   if(hf2481)\n   {\n      publish_feed( bitusd, feedproducer, feed2 );\n\n      // blackswan\n      BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_CHECK( !db.find( call2_id ) );\n      int64_t call_pays_to_fund = (15000 * 10 + 10) / 11;\n      BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value,\n                         call_pays_to_fund * 2 );\n      BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value,\n                         15000 - call_pays_to_fund );\n\n      // sell order doesn't change\n      BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value );\n      // seller balance doesn't change\n      BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) );\n      BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n      BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n      BOOST_CHECK_EQUAL( init_balance - call_pays_to_fund, get_balance(borrower2, core) );\n   }\n   else\n   {\n      BOOST_REQUIRE_THROW( publish_feed( bitusd, feedproducer, feed2 ), fc::exception );\n\n      publish_feed( bitusd, feeder2, current_feed );\n      publish_feed( bitusd, feeder3, current_feed );\n\n      // No change\n      BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value );\n\n      BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value );\n\n      generate_blocks( db.head_block_time() + fc::seconds(43200) );\n      set_expiration( db, trx );\n\n      publish_feed( usd_id(db), feedproducer_id(db), feed2 );\n\n      // No change\n      BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value );\n\n      BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value );\n\n      generate_blocks( db.head_block_time() + fc::seconds(43200) );\n\n      // The first call order should have been filled\n      BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !db.find( call_id ) );\n      BOOST_REQUIRE( db.find( call2_id ) );\n\n      BOOST_CHECK_EQUAL( 100, sell_mid(db).for_sale.value );\n\n      BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 900, get_balance(seller_id(db), usd_id(db)) );\n      BOOST_CHECK_EQUAL( 14047, get_balance(seller_id(db), core) );\n   }\n\n   // generate a block to include operations above\n   BOOST_TEST_MESSAGE( \"Generating a new block\" );\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * BSIP38 \"target_collateral_ratio\" test: matching a taker limit order with multiple maker call orders\n */\nBOOST_AUTO_TEST_CASE(target_cr_test_limit_call)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else if(hf1270)\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_834_TIME - mi);\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(buyer2)(buyer3)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, buyer2_id, asset(init_balance));\n   transfer(committee_account, buyer3_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% is higher than 175%\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000));\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // This sell order above MSSP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->get_id();\n   // This buy order at MSSP will be matched only if no margin call (margin call takes precedence)\n   limit_order_id_type buy_med = create_sell_order(buyer2, asset(33000), bitusd.amount(3000))->get_id();\n   // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence)\n   limit_order_id_type buy_high = create_sell_order(buyer3, asset(111), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer3, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n   BOOST_CHECK_EQUAL( init_balance - 33000, get_balance(buyer2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );\n\n   // call and call2's CR is quite high, and debt amount is quite a lot, assume neither of them will be completely filled\n   price match_price( bitusd.amount(1) / core.amount(11) );\n   share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750);\n   share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750);\n   BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value );\n   // even though call2 has a higher CR, since call's TCR is less than call2's TCR, so we expect call will cover less when called\n   BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value );\n\n   // Create a big sell order slightly below the call price, will be matched with several orders\n   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700*4), core.amount(5900*4) ) );\n\n   // firstly it will match with buy_high, at buy_high's price\n   BOOST_CHECK( !db.find( buy_high ) );\n   // buy_high pays 111 CORE, receives 10 USD goes to buyer3's balance\n   BOOST_CHECK_EQUAL( 10, get_balance(buyer3, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );\n\n   // then it will match with call, at mssp: 1/11 = 1000/11000\n   const call_order_object* tmp_call = db.find( call_id );\n   BOOST_CHECK( tmp_call != nullptr );\n\n   // call will receive call_to_cover, pay 11*call_to_cover\n   share_type call_to_pay = call_to_cover * 11;\n   BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call.debt.value * 10 * 1750 < call.collateral.value * 1000 );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // the limit order then will match with call2, at mssp: 1/11 = 1000/11000\n   const call_order_object* tmp_call2 = db.find( call2_id );\n   BOOST_CHECK( tmp_call2 != nullptr );\n\n   // call2 will receive call2_to_cover, pay 11*call2_to_cover\n   share_type call2_to_pay = call2_to_cover * 11;\n   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2.collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call2.debt.value * 10 * 2000 < call2.collateral.value * 1000 );\n   idump( (call2) );\n   // borrower2's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n\n   // then it will match with buy_med, at buy_med's price. Since buy_med is too big, it's partially filled.\n   // buy_med receives the remaining USD of sell order, minus market fees, goes to buyer2's balance\n   share_type buy_med_get = 700*4 - 10 - call_to_cover - call2_to_cover;\n   share_type buy_med_pay = buy_med_get * 11; // buy_med pays at 1/11\n   buy_med_get -= (buy_med_get/100); // minus 1% market fee\n   BOOST_CHECK_EQUAL( buy_med_get.value, get_balance(buyer2, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 33000, get_balance(buyer2, core) );\n   BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 33000-buy_med_pay.value );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4\n   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) ); // 111 + (700*4-10)*11\n\n   // Cancel buy_med\n   cancel_limit_order( buy_med(db) );\n   BOOST_CHECK( !db.find( buy_med ) );\n   BOOST_CHECK_EQUAL( buy_med_get.value, get_balance(buyer2, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - buy_med_pay.value, get_balance(buyer2, core) );\n\n   // Create another sell order slightly below the call price, won't fill\n   limit_order_id_type sell_med = create_sell_order( seller, bitusd.amount(7), core.amount(59) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_med )->for_sale.value, 7 );\n   // check seller balance\n   BOOST_CHECK_EQUAL( 193-7, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * BSIP38 \"target_collateral_ratio\" test: matching a maker limit order with multiple taker call orders\n */\nBOOST_AUTO_TEST_CASE(target_cr_test_call_limit)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else if(hf1270)\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_834_TIME - mi);\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   set_expiration( db, trx );\n\n   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, buyer_id, asset(init_balance));\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   transfer(committee_account, borrower3_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio = 1100;\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);\n   publish_feed( bitusd, feedproducer, current_feed );\n   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175%\n   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700);\n   call_order_id_type call_id = call.get_id();\n   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% is higher than 175%\n   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000);\n   call_order_id_type call2_id = call2.get_id();\n   // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr\n   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000));\n   transfer(borrower, seller, bitusd.amount(1000));\n   transfer(borrower2, seller, bitusd.amount(1000));\n   transfer(borrower3, seller, bitusd.amount(1000));\n\n   BOOST_CHECK_EQUAL( 1000, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000, call.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500, call2.collateral.value );\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) );\n\n   // This sell order above MSSP will not be matched with a call\n   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 );\n\n   BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) );\n   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );\n\n   // This buy order is too low will not be matched with a sell order\n   limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->get_id();\n\n   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );\n   BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) );\n\n   // Create a sell order which will be matched with several call orders later, price 1/9\n   limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(500), core.amount(4500) )->get_id();\n   BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 500 );\n\n   // prepare price feed to get call and call2 (but not call3) into margin call territory\n   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);\n\n   // call and call2's CR is quite high, and debt amount is quite a lot, assume neither of them will be completely filled\n   price match_price = sell_id(db).sell_price;\n   share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750);\n   share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750);\n   BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value );\n   BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value );\n   // even though call2 has a higher CR, since call's TCR is less than call2's TCR, so we expect call will cover less when called\n   BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value );\n\n   // adjust price feed to get call and call2 (but not call3) into margin call territory\n   publish_feed( bitusd, feedproducer, current_feed );\n   // settlement price = 1/10, mssp = 1/11\n\n   // firstly the limit order will match with call, at limit order's price: 1/9\n   const call_order_object* tmp_call = db.find( call_id );\n   BOOST_CHECK( tmp_call != nullptr );\n\n   // call will receive call_to_cover, pay 9*call_to_cover\n   share_type call_to_pay = call_to_cover * 9;\n   BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call.debt.value );\n   BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value );\n   // new collateral ratio should be higher than mcr as well as tcr\n   BOOST_CHECK( call.debt.value * 10 * 1750 < call.collateral.value * 1000 );\n   idump( (call) );\n   // borrower's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );\n\n   // the limit order then will match with call2, at limit order's price: 1/9\n   const call_order_object* tmp_call2 = db.find( call2_id );\n   BOOST_CHECK( tmp_call2 != nullptr );\n\n   // if the limit is big enough, call2 will receive call2_to_cover, pay 11*call2_to_cover\n   // however it's not the case, so call2 will receive less\n   call2_to_cover = 500 - call_to_cover;\n   share_type call2_to_pay = call2_to_cover * 9;\n   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2.debt.value );\n   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2.collateral.value );\n   idump( (call2) );\n   // borrower2's balance doesn't change\n   BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) );\n   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );\n\n   // call3 is not in margin call territory so won't be matched\n   BOOST_CHECK_EQUAL( 1000, call3.debt.value );\n   BOOST_CHECK_EQUAL( 25000, call3.collateral.value );\n\n   // sell_id is completely filled\n   BOOST_CHECK( !db.find( sell_id ) );\n\n   // check seller balance\n   BOOST_CHECK_EQUAL( 2493, get_balance(seller, bitusd) ); // 3000 - 7 - 500\n   BOOST_CHECK_EQUAL( 4500, get_balance(seller, core) ); // 500*9\n\n   // buy_low's price is too low that won't be matched\n   BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 );\n\n   // generate a block\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_increase_before1270)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_453_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio  = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800));\n   auto b1_id = b1.get_id();\n   const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) );\n   auto b2_id = b2.get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 );\n\n   // move order to margin call territory with mcr only\n   current_feed.maintenance_collateral_ratio = 2000;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 );\n\n   BOOST_CHECK( db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n   // attempt to trade the margin call\n   create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000  );\n\n   print_market(bitusd.symbol, core.symbol);\n\n   // both calls are still there, no margin call, mcr bug\n   BOOST_CHECK( db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_increase_after1270)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio  = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800));\n   auto b1_id = b1.get_id();\n   const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) );\n   auto b2_id = b2.get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 );\n\n   // move order to margin call territory with mcr only\n   current_feed.maintenance_collateral_ratio = 2000;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 );\n\n   BOOST_CHECK( db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n   // attempt to trade the margin call\n   create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998900 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 999100  );\n\n   print_market(bitusd.symbol, core.symbol);\n\n   // b1 is margin called\n   BOOST_CHECK( ! db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_decrease_before1270)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_453_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio  = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800));\n   auto b1_id = b1.get_id();\n   const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) );\n   auto b2_id = b2.get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 );\n\n   // move order to margin call territory with the feed\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(150);\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   // getting out of margin call territory with mcr change\n   current_feed.maintenance_collateral_ratio = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 );\n\n   BOOST_CHECK( db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n   // attempt to trade the margin call\n   create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998350 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 999650  );\n\n   print_market(bitusd.symbol, core.symbol);\n\n   // margin call at b1, mcr bug\n   BOOST_CHECK( !db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_decrease_after1270)\n{ try {\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   if(hf2481)\n      generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n   else\n      generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();\n\n   set_expiration( db, trx );\n\n   ACTORS((seller)(borrower)(borrower2)(feedproducer));\n\n   const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n   const auto& core   = asset_id_type()(db);\n\n   int64_t init_balance(1000000);\n\n   transfer(committee_account, borrower_id, asset(init_balance));\n   transfer(committee_account, borrower2_id, asset(init_balance));\n   update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n   price_feed current_feed;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n   current_feed.maintenance_collateral_ratio = 1750;\n   current_feed.maximum_short_squeeze_ratio  = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800));\n   auto b1_id = b1.get_id();\n   const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) );\n   auto b2_id = b2.get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 );\n\n   // move order to margin call territory with the feed\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(150);\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   // getting out of margin call territory with mcr decrease\n   current_feed.maintenance_collateral_ratio = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 );\n\n   BOOST_CHECK( db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n   // attempt to trade the margin call\n   create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) );\n\n   BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 );\n   BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 );\n   BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000  );\n\n   print_market(bitusd.symbol, core.symbol);\n\n   // both calls are there, no margin call, good\n   BOOST_CHECK( db.find( b1_id ) );\n   BOOST_CHECK( db.find( b2_id ) );\n\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_cross1270)\n{ try {\n\n   INVOKE(mcr_bug_increase_before1270);\n\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n\n   const asset_object& core = get_asset(GRAPHENE_SYMBOL);\n   const asset_object& bitusd = get_asset(\"USDBIT\");\n   const asset_id_type bitusd_id = bitusd.get_id();\n   const account_object& feedproducer = get_account(\"feedproducer\");\n\n   // feed is expired\n   auto mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio;\n   BOOST_CHECK_EQUAL(mcr, GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n   // make new feed\n   price_feed current_feed;\n   current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n   current_feed.maintenance_collateral_ratio = 2000;\n   current_feed.maximum_short_squeeze_ratio  = 1100;\n   publish_feed( bitusd, feedproducer, current_feed );\n\n   mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio;\n   BOOST_CHECK_EQUAL(mcr, 2000);\n\n   // pass hardfork\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();\n\n   // feed is still valid\n   mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio;\n   BOOST_CHECK_EQUAL(mcr, 2000);\n\n   // margin call is traded\n   print_market(asset_id_type(1)(db).symbol, asset_id_type()(db).symbol);\n\n   // call b1 not there anymore\n   BOOST_CHECK( !db.find( call_order_id_type() ) );\n   BOOST_CHECK( db.find( call_order_id_type(1) ) );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hardfork_core_338_test_after_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(hardfork_core_338_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hardfork_core_453_test_after_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(hardfork_core_453_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test_after_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(hardfork_core_625_big_limit_order_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(target_cr_test_limit_call_after_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(target_cr_test_limit_call);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(target_cr_test_call_limit_after_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(target_cr_test_call_limit);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hardfork_core_338_test_after_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(hardfork_core_338_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hardfork_core_453_test_after_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(hardfork_core_453_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test_after_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(hardfork_core_625_big_limit_order_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(target_cr_test_limit_call_after_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(target_cr_test_limit_call);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(target_cr_test_call_limit_after_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(target_cr_test_call_limit);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_decrease_after2481)\n{ try {\n   hf2481 = true;\n   INVOKE(mcr_bug_decrease_after1270);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcr_bug_increase_after2481)\n{ try {\n   hf2481 = true;\n   INVOKE(mcr_bug_increase_after1270);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(mcfr_rounding_test_after2481)\n{ try {\n   hf2481 = true;\n   INVOKE(mcfr_rounding_test);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/network_broadcast_api_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/api.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE(network_broadcast_api_tests, database_fixture)\n\nBOOST_AUTO_TEST_CASE( broadcast_transaction_with_callback_test ) {\n   try {\n\n      uint32_t called = 0;\n      auto callback = [&]( const variant& v )\n      {\n         ++called;\n         idump((v));\n         auto callback_obj = v.as<graphene::app::network_broadcast_api::transaction_confirmation>(200);\n         BOOST_CHECK_EQUAL( callback_obj.trx.operations.size(), callback_obj.trx.operation_results.size() );\n      };\n\n      fc::ecc::private_key cid_key = fc::ecc::private_key::regenerate( fc::digest(\"key\") );\n      const account_id_type cid_id = create_account( \"cid\", cid_key.get_public_key() ).get_id();\n      fund( cid_id(db) );\n\n      auto nb_api = std::make_shared< graphene::app::network_broadcast_api >( app );\n\n      set_expiration( db, trx );\n      transfer_operation trans;\n      trans.from = cid_id;\n      trans.to   = account_id_type();\n      trans.amount = asset(1);\n      trx.operations.push_back( trans );\n      sign( trx, cid_key );\n\n      nb_api->broadcast_transaction_with_callback( callback, trx );\n\n      trx.clear();\n\n      generate_block();\n\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      BOOST_CHECK_EQUAL( called, 1u );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( broadcast_transaction_disabled_p2p_test ) {\n   try {\n\n      uint32_t called = 0;\n      auto callback = [&]( const variant& v )\n      {\n         ++called;\n      };\n\n      fc::ecc::private_key cid_key = fc::ecc::private_key::regenerate( fc::digest(\"key\") );\n      const account_id_type cid_id = create_account( \"cid\", cid_key.get_public_key() ).get_id();\n      fund( cid_id(db) );\n\n      auto nb_api = std::make_shared< graphene::app::network_broadcast_api >( app );\n\n      set_expiration( db, trx );\n      transfer_operation trans;\n      trans.from = cid_id;\n      trans.to   = account_id_type();\n      trans.amount = asset(1);\n      trx.operations.push_back( trans );\n      sign( trx, cid_key );\n\n      BOOST_CHECK_THROW( nb_api->broadcast_transaction( trx ), fc::exception );\n      BOOST_CHECK_THROW( nb_api->broadcast_transaction_with_callback( callback, trx ), fc::exception );\n      BOOST_CHECK_EQUAL( called, 0u );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( broadcast_transaction_too_large ) {\n   try {\n\n      fc::ecc::private_key cid_key = fc::ecc::private_key::regenerate( fc::digest(\"key\") );\n      const account_id_type cid_id = create_account( \"cid\", cid_key.get_public_key() ).get_id();\n      fund( cid_id(db) );\n\n      auto nb_api = std::make_shared< graphene::app::network_broadcast_api >( app );\n\n      generate_blocks( HARDFORK_CORE_1573_TIME + 10 );\n\n      set_expiration( db, trx );\n      transfer_operation trans;\n      trans.from = cid_id;\n      trans.to   = account_id_type();\n      trans.amount = asset(1);\n      for(int i = 0; i < 250; ++i )\n         trx.operations.push_back( trans );\n      sign( trx, cid_key );\n\n      BOOST_CHECK_THROW( nb_api->broadcast_transaction( trx ), fc::exception );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/operation_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n#include <boost/assign/list_of.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n#include <graphene/chain/vesting_balance_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n\n#include <graphene/market_history/market_history_plugin.hpp>\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\n#define UIA_TEST_SYMBOL \"UIATEST\"\n\nBOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( feed_limit_logic_test )\n{ try {\n\n      asset usd(1000,asset_id_type(1));\n      asset core(1000,asset_id_type(0));\n      price_feed feed;\n      feed.settlement_price = usd / core;\n\n      // require 3x min collateral\n      auto swanp = usd / core;\n      auto callp = ~price::call_price( usd, core, 1750 );\n      // 1:1 collateral\n//      wdump((callp.to_real())(callp));\n//      wdump((swanp.to_real())(swanp));\n      FC_ASSERT( callp.to_real() > swanp.to_real() );\n\n      /*\n      wdump((feed.settlement_price.to_real()));\n      wdump((feed.maintenance_price().to_real()));\n      wdump((feed.max_short_squeeze_price().to_real()));\n\n      BOOST_CHECK( usd * feed.settlement_price < usd * feed.maintenance_price() );\n      BOOST_CHECK( usd * feed.maintenance_price() < usd * feed.max_short_squeeze_price() );\n      */\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( limit_order_update_hardfork_time_test )\n{ try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_2362_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((nathan));\n\n      const auto& munee = create_user_issued_asset(\"MUNEE\");\n\n      transfer(committee_account, nathan_id, asset(1500));\n\n      auto expiration = db.head_block_time() + 1000;\n      auto sell_price = price(asset(500), munee.amount(1000));\n      limit_order_id_type order_id = create_sell_order(nathan, asset(500), munee.amount(1000), expiration)->get_id();\n\n      BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 500);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n      // Cannot update order yet\n      sell_price.base = asset(499);\n      GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, sell_price), fc::assert_exception );\n\n      // Cannot propose\n      limit_order_update_operation louop = make_limit_order_update_op( nathan_id, order_id, sell_price );\n      BOOST_CHECK_THROW( propose( louop ), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(limit_order_update_test)\n{ try {\n\n      generate_blocks(HARDFORK_CORE_1604_TIME + 10);\n      set_expiration( db, trx );\n\n      ACTORS((nathan)(dan));\n\n      const auto& bitusd = create_bitasset(\"USDBIT\", nathan_id);\n      const auto& munee = create_user_issued_asset(\"MUNEE\");\n      const auto& core   = asset_id_type()(db);\n\n      update_feed_producers(bitusd, {nathan_id});\n      price_feed current_feed;\n      current_feed.settlement_price = bitusd.amount(200) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750;\n      publish_feed(bitusd, nathan, current_feed);\n\n      transfer(committee_account, nathan_id, asset(1500));\n      issue_uia(nathan, munee.amount(100));\n      borrow(nathan_id, bitusd.amount(100), asset(500));\n\n      auto expiration = db.head_block_time() + 1000;\n      auto sell_price = price(asset(500), bitusd.amount(1000));\n      limit_order_id_type order_id = create_sell_order(nathan, asset(500), bitusd.amount(1000), expiration)->get_id();\n      BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 500);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n      // Cannot update order without changing anything\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id), fc::exception);\n      // Cannot update order to use inverted price assets\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(bitusd.amount(2), asset(1))), fc::exception);\n      // Cannot update order to use negative price\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(-1), bitusd.amount(2))), fc::exception);\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(1), bitusd.amount(-2))), fc::exception);\n      // Cannot update order to use different assets\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(bitusd.amount(2), munee.amount(1))),\n                             fc::exception);\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(munee.amount(2), bitusd.amount(1))),\n                             fc::exception);\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(2), munee.amount(1))), fc::exception);\n      // Cannot update order to expire in the past\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, {}, db.head_block_time() - 10), fc::exception);\n      // Cannot update order with a zero delta\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset()), fc::exception);\n      // Cannot update order to add more funds than seller has\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(501)), fc::exception);\n      // Cannot update order to remove more funds than order has\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-501)), fc::exception);\n      // Cannot update order to remove all funds in order\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-500)), fc::exception);\n      // Cannot update order to add or remove different kind of funds\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, bitusd.amount(50)), fc::exception);\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, bitusd.amount(-50)), fc::exception);\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, munee.amount(50)), fc::exception);\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, munee.amount(-50)), fc::exception);\n\n      // Cannot update someone else's order\n      limit_order_update_operation louop = make_limit_order_update_op( dan_id, order_id, {}, asset(-1) );\n      trx.operations.clear();\n      trx.operations.push_back( louop );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // Can propose\n      propose( louop );\n\n      // Cannot update an order which does not exist\n      louop = make_limit_order_update_op( nathan_id, order_id + 1, {}, asset(-1) );\n      trx.operations.clear();\n      trx.operations.push_back( louop );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Try changing price\n      sell_price.base = asset(501);\n      // Cannot update base amount in the new price to be more than the amount for sale\n      GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, sell_price), fc::exception );\n      sell_price.base = asset(500);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      sell_price.base = asset(499);\n      update_limit_order(order_id, sell_price);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      sell_price.base = asset(500);\n      sell_price.quote = bitusd.amount(999);\n      update_limit_order(order_id, sell_price);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      sell_price.quote = bitusd.amount(1000);\n      update_limit_order(order_id, sell_price);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n\n      // Try changing expiration\n      expiration += 50;\n      update_limit_order(order_id, {}, {}, expiration);\n      BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n      // Cannot change expiration to a time in the past\n      GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, {}, db.head_block_time() - 1 ), fc::exception);\n\n      // Try adding funds\n      update_limit_order(order_id, {}, asset(50));\n      BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 550);\n      BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 450);\n\n      // Try removing funds\n      update_limit_order(order_id, {}, asset(-100));\n      BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 450);\n      BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 550);\n\n      // Try changing everything at once\n      expiration += 50;\n      sell_price.base = asset(499);\n      sell_price.quote = bitusd.amount(1001);\n      update_limit_order(order_id, sell_price, 50, expiration);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n      BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 500);\n      BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 500);\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( limit_order_update_asset_authorization_test )\n{ try {\n\n      generate_blocks(HARDFORK_CORE_1604_TIME + 10);\n      set_expiration( db, trx );\n\n      ACTORS((nathan)(dan)(whitey)(blacky));\n\n      const auto& munee = create_user_issued_asset(\"MUNEE\", dan, white_list );\n      const auto& noomo = create_user_issued_asset(\"NOOMO\", dan, white_list );\n\n      issue_uia(nathan, munee.amount(100));\n      issue_uia(nathan, noomo.amount(100));\n\n      auto expiration = db.head_block_time() + 1000;\n      auto sell_price = price( munee.amount(50), noomo.amount(60) );\n      limit_order_id_type order_id = create_sell_order(nathan, munee.amount(50), noomo.amount(60), expiration)\n                                     ->get_id();\n      BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 50);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n      // Can update order\n      update_limit_order(order_id, {}, munee.amount(-1));\n      BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 49);\n      BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n      BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = munee.id;\n         uop.issuer = dan_id;\n         uop.new_options = munee.options;\n         // The whitelist is managed by Whitey\n         uop.new_options.whitelist_authorities.insert(whitey_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Whitey so that he can manage the whitelist\n         upgrade_to_lifetime_member( whitey_id );\n\n         // Add Dan to the whitelist, but do not add others\n         account_whitelist_operation wop;\n         wop.authorizing_account = whitey_id;\n         wop.account_to_list = dan_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Cannot update order\n         GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception );\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 49);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n         // Add Nathan to the whitelist\n         wop.account_to_list = nathan_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Can update order\n         update_limit_order(order_id, {}, munee.amount(-1));\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 48);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n      }\n\n      // Make a blacklist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up blacklisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = noomo.id;\n         uop.issuer = dan_id;\n         uop.new_options = noomo.options;\n         // The blacklist is managed by Blacky\n         uop.new_options.blacklist_authorities.insert(blacky_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Blacky so that he can manage the blacklist\n         upgrade_to_lifetime_member( blacky_id );\n\n         // Add Nathan to the blacklist, but do not add others\n         account_whitelist_operation wop;\n         wop.authorizing_account = blacky_id;\n         wop.account_to_list = nathan_id;\n         wop.new_listing = account_whitelist_operation::black_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Cannot update order\n         GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception );\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 48);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n         // Clear blacklist\n         wop.new_listing = account_whitelist_operation::no_listing;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Can update order\n         update_limit_order(order_id, {}, munee.amount(-1));\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 47);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n      }\n\n      // Make a market whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up market whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = munee.id;\n         uop.issuer = dan_id;\n         uop.new_options = munee.options;\n         uop.new_options.whitelist_markets.insert( asset_id_type() );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Cannot update order\n         GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception );\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 47);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n         // Add Noomo to the whitelist\n         uop.new_options.whitelist_markets.insert( noomo.get_id() );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Can update order\n         update_limit_order(order_id, {}, munee.amount(-1));\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 46);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n      }\n\n      // Make a market blacklist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up market blacklisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = munee.id;\n         uop.issuer = dan_id;\n         uop.new_options = munee.options;\n         uop.new_options.whitelist_markets.clear();\n         uop.new_options.blacklist_markets.insert( noomo.get_id() );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Cannot update order\n         GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception );\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 46);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n\n         // Remove Noomo from the blacklist\n         uop.new_options.blacklist_markets.erase( noomo.get_id() );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Can update order\n         update_limit_order(order_id, {}, munee.amount(-1));\n         BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 45);\n         BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price));\n         BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch());\n      }\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(limit_order_update_dust_test)\n{ try {\n\n      generate_blocks(HARDFORK_CORE_1604_TIME + 10);\n      set_expiration( db, trx );\n\n      ACTORS((nathan)(dan));\n\n      const auto& munee = create_user_issued_asset(\"MUNEE\");\n\n      transfer(committee_account, nathan_id, asset(10000));\n      issue_uia(dan, munee.amount(1000));\n\n      auto expiration = db.head_block_time() + 1000;\n      limit_order_id_type order_id = create_sell_order(nathan, asset(1000), munee.amount(100), expiration)->get_id();\n\n      REQUIRE_EXCEPTION_WITH_TEXT( update_limit_order(order_id, {}, asset(-995)), \"order becomes too small\" );\n\n      // Partially fill the first order so that we can test price changes\n      const limit_order_object* order2 = create_sell_order( dan, munee.amount(99), asset(990) );\n      BOOST_CHECK( !order2 );\n\n      auto sell_price = asset(1000) / munee.amount(100);\n      BOOST_CHECK_EQUAL( fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price) );\n      BOOST_CHECK_EQUAL( order_id(db).for_sale.value, 10 );\n\n      REQUIRE_EXCEPTION_WITH_TEXT( update_limit_order(order_id, price(asset(1000), munee.amount(99))),\n                                   \"order becomes too small\" );\n      REQUIRE_EXCEPTION_WITH_TEXT( update_limit_order(order_id, price(asset(990), munee.amount(150)), asset(-5)),\n                                   \"order becomes too small\" );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(limit_order_update_match_test)\n{ try {\n\n      generate_blocks(HARDFORK_CORE_1604_TIME + 10);\n      set_expiration( db, trx );\n\n      ACTORS((nathan));\n\n      const auto& munee = create_user_issued_asset(\"MUNEE\");\n\n      transfer(committee_account, nathan_id, asset(10000));\n      issue_uia(nathan, munee.amount(1000));\n\n      auto expiration = db.head_block_time() + 1000;\n      limit_order_id_type order_id_1 = create_sell_order(nathan, asset(999), munee.amount(100), expiration)->get_id();\n      limit_order_id_type order_id_2 = create_sell_order(nathan, munee.amount(100),asset(1001), expiration)->get_id();\n\n      update_limit_order(order_id_1, price(asset(1000), munee.amount(99)), asset(1));\n      BOOST_REQUIRE( !db.find(order_id_1) );\n      BOOST_REQUIRE_EQUAL(db.find(order_id_2)->amount_for_sale().amount.value, 1);\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(limit_order_update_match_test_2)\n{ try {\n\n      generate_blocks(HARDFORK_CORE_1604_TIME + 10);\n      set_expiration( db, trx );\n\n      ACTORS((nathan));\n\n      const auto& munee = create_user_issued_asset(\"MUNEE\");\n\n      transfer(committee_account, nathan_id, asset(10000));\n      issue_uia(nathan, munee.amount(1000));\n\n      auto expiration = db.head_block_time() + 1000;\n      limit_order_id_type order_id_1 = create_sell_order(nathan, asset(999), munee.amount(100), expiration)->get_id();\n      limit_order_id_type order_id_2 = create_sell_order(nathan, munee.amount(100),asset(1001), expiration)->get_id();\n\n      update_limit_order(order_id_2, price(munee.amount(100), asset(999)));\n      BOOST_REQUIRE( !db.find(order_id_1) );\n      BOOST_REQUIRE( !db.find(order_id_2) );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( call_order_update_test )\n{ try {\n\n      ACTORS((dan)(sam));\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id());\n      const auto& core   = asset_id_type()(db);\n\n      transfer(committee_account, dan_id, asset(10000000));\n      transfer(committee_account, sam_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );\n\n      BOOST_TEST_MESSAGE( \"covering 2500 usd and freeing 5000 core...\" );\n      cover( dan, bitusd.amount(2500), asset(5000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 5000  );\n\n      BOOST_TEST_MESSAGE( \"verifying that attempting to cover the full amount without claiming the collateral fails\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(0)  ), fc::exception );\n\n      cover( dan, bitusd.amount(2500), core.amount(5000));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 0 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000  );\n\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000  );\n\n\n      // test just increasing collateral\n      BOOST_TEST_MESSAGE( \"increasing collateral\" );\n      borrow( dan, bitusd.amount(0), asset(10000));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000  );\n\n      // test just decreasing debt\n      BOOST_TEST_MESSAGE( \"decreasing debt\" );\n      cover( dan, bitusd.amount(1000), asset(0));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 4000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000  );\n\n      BOOST_TEST_MESSAGE( \"increasing debt without increasing collateral\" );\n      borrow( dan, bitusd.amount(1000), asset(0));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000  );\n\n      BOOST_TEST_MESSAGE( \"increasing debt a lot without increasing collateral, fails due to black swan\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(80000), asset(0)), fc::exception );\n      BOOST_TEST_MESSAGE( \"attempting to claim most of collateral without paying off debt, fails due to black swan\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(20000-1)), fc::exception );\n      BOOST_TEST_MESSAGE( \"attempting to claim all collateral without paying off debt\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(20000)), fc::exception );\n\n      borrow( sam, bitusd.amount(1000), asset(10000));\n      transfer( sam, dan, bitusd.amount(1000) );\n\n      BOOST_TEST_MESSAGE( \"attempting to claim more collateral than available\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(4000), asset(20001)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(4000), asset(20100)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(4000), asset(30000)), fc::exception );\n\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(5000), asset(20001)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(5000), asset(20100)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(5000), asset(30000)), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"attempting to pay more debt than required\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(15000)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(20000)), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"attempting to pay more debt than required, and claim more collateral than available\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(20001)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(40000)), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"attempting reduce collateral without paying off any debt\" );\n      cover( dan, bitusd.amount(0), asset(1000));\n\n      BOOST_TEST_MESSAGE( \"attempting change call price to be below minimum for debt/collateral ratio\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0)), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 )\n{ try {\n\n      auto hf_time = HARDFORK_CORE_583_TIME;\n      if( bsip77 )\n         hf_time = HARDFORK_BSIP_77_TIME;\n      generate_blocks( hf_time );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam));\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id());\n      const auto& core   = asset_id_type()(db);\n\n      transfer(committee_account, dan_id, asset(10000000));\n      transfer(committee_account, sam_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );\n\n      BOOST_TEST_MESSAGE( \"covering 2500 usd and freeing 5000 core...\" );\n      cover( dan, bitusd.amount(2500), asset(5000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 5000  );\n\n      BOOST_TEST_MESSAGE( \"verifying that attempting to cover the full amount without claiming the collateral fails\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(0)  ), fc::exception );\n\n      cover( dan, bitusd.amount(2500), core.amount(5000));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 0 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000  );\n\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000  );\n\n\n      // test just increasing collateral\n      BOOST_TEST_MESSAGE( \"increasing collateral\" );\n      borrow( dan, bitusd.amount(0), asset(10000));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000  );\n\n      // test just decreasing debt\n      BOOST_TEST_MESSAGE( \"decreasing debt\" );\n      cover( dan, bitusd.amount(1000), asset(0));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 4000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000  );\n\n      BOOST_TEST_MESSAGE( \"increasing debt without increasing collateral\" );\n      borrow( dan, bitusd.amount(1000), asset(0));\n\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 20000  );\n\n      BOOST_TEST_MESSAGE( \"increasing debt a lot without increasing collateral, fails due to black swan\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(80000), asset(0)), fc::exception );\n      BOOST_TEST_MESSAGE( \"attempting to claim most of collateral without paying off debt, fails due to black swan\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(20000-1)), fc::exception );\n      BOOST_TEST_MESSAGE( \"attempting to claim all collateral without paying off debt\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(20000)), fc::exception );\n\n      borrow( sam, bitusd.amount(1000), asset(10000));\n      transfer( sam, dan, bitusd.amount(1000) );\n\n      BOOST_TEST_MESSAGE( \"attempting to claim more collateral than available\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(4000), asset(20001)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(4000), asset(20100)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(4000), asset(30000)), fc::exception );\n\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(5000), asset(20001)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(5000), asset(20100)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(5000), asset(30000)), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"attempting to pay more debt than required\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(15000)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(20000)), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"attempting to pay more debt than required, and claim more collateral than available\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(20001)), fc::exception );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(6000), asset(40000)), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"attempting reduce collateral without paying off any debt\" );\n      cover( dan, bitusd.amount(0), asset(1000));\n\n      BOOST_TEST_MESSAGE( \"attempting change call price to be below minimum for debt/collateral ratio\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0)), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test )\n{ try {\n\n      generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) );\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam));\n\n      const auto& backasset = create_user_issued_asset(\"BACK\", sam, white_list | charge_market_fee);\n      asset_id_type back_id = backasset.get_id();\n\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id(), 10, white_list | charge_market_fee, 3, back_id);\n      asset_id_type usd_id = bitusd.get_id();\n\n      issue_uia( dan_id, backasset.amount(10000000) );\n      issue_uia( sam_id, backasset.amount(10000000) );\n\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed;\n      current_feed.core_exchange_rate = bitusd.amount( 100 ) / asset( 100 );\n      current_feed.settlement_price = bitusd.amount( 100 ) / backasset.amount( 100 );\n      current_feed.maintenance_collateral_ratio = 1750; // need to set explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), backasset.amount(10000) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, backasset ), 10000000 - 10000 );\n\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // For BACK\n         uop.asset_to_update = back_id;\n         uop.new_options = back_id(db).options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Sam to the whitelist, but do not add Dan\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to borrow\n      BOOST_TEST_MESSAGE( \"Dan attempting to borrow using 2x collateral at 1:1 price again\" );\n      borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 5000 + 5000);\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, back_id ), 10000000 - 10000 - 10000 );\n\n      // Apply core-973 hardfork\n      generate_blocks( HARDFORK_CORE_973_TIME );\n      set_expiration( db, trx );\n\n      // Update price feed\n      publish_feed( usd_id(db), sam_id(db), current_feed );\n\n      // Sam should be able to borrow, but Dan should be unable to borrow\n      borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, back_id ), 10000000 - 10000 );\n\n      GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ),\n                              fc::exception );\n\n      // Update USDBIT, disable remove whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Disable USDBIT whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         uop.new_options.whitelist_authorities.clear();\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam should be able to borrow, but Dan should be unable to borrow\n      borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) );\n      GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ),\n                              fc::exception );\n\n      // Update BACK, disable whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Disable BACK whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = back_id;\n         uop.new_options = back_id(db).options;\n         uop.new_options.whitelist_authorities.clear();\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Both Sam and Dan should be able to borrow\n      borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) );\n      borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) );\n\n      // Update USDBIT, enable whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Enable USDBIT whitelisting again\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         uop.new_options.whitelist_authorities.insert( sam_id );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam should be able to borrow, but Dan should be unable to borrow\n      borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) );\n      GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ),\n                              fc::exception );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test )\n{ try {\n\n      generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) );\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam));\n\n      const auto& backasset = create_user_issued_asset(\"BACK\", sam, white_list | charge_market_fee);\n      asset_id_type back_id = backasset.get_id();\n\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id(), 10, white_list | charge_market_fee, 3, back_id);\n      asset_id_type usd_id = bitusd.get_id();\n\n      issue_uia( dan_id, backasset.amount(10000000) );\n      issue_uia( sam_id, backasset.amount(10000000) );\n\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed;\n      current_feed.core_exchange_rate = bitusd.amount( 100 ) / asset( 100 );\n      current_feed.settlement_price = bitusd.amount( 100 ) / backasset.amount( 100 );\n      current_feed.maintenance_collateral_ratio = 1750; // need to set explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), backasset.amount(10000) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, backasset ), 10000000 - 10000 );\n\n      transfer( dan, sam, bitusd.amount(2000) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 3000 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 2000 );\n\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // For BACK\n         uop.asset_to_update = back_id;\n         uop.new_options = back_id(db).options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Sam to the whitelist, but do not add Dan\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to force-settle\n      BOOST_TEST_MESSAGE( \"Dan and Sam attempting to force-settle\" );\n      force_settle( dan_id(db), usd_id(db).amount(100) );\n      force_settle( sam_id(db), usd_id(db).amount(100) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2900 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1900 );\n\n      // Apply core-973 hardfork\n      BOOST_TEST_MESSAGE( \"Apply core-973 hardfork\" );\n      generate_blocks( HARDFORK_CORE_973_TIME );\n      set_expiration( db, trx );\n\n      // Update price feed\n      publish_feed( usd_id(db), sam_id(db), current_feed );\n\n      // Sam should be able to force-settle, but Dan should be unable to force-settle\n      BOOST_TEST_MESSAGE( \"Dan and Sam attempting to force-settle again\" );\n      GRAPHENE_REQUIRE_THROW( force_settle( dan_id(db), usd_id(db).amount(100) ), fc::exception );\n      force_settle( sam_id(db), usd_id(db).amount(100) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2900 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1800 );\n\n      // Update USDBIT, disable remove whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Disable USDBIT whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         uop.new_options.whitelist_authorities.clear();\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam should be able to force-settle, but Dan should be unable to force-settle\n      GRAPHENE_REQUIRE_THROW( force_settle( dan_id(db), usd_id(db).amount(100) ), fc::exception );\n      force_settle( sam_id(db), usd_id(db).amount(100) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2900 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1700 );\n\n      // Update BACK, disable whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Disable BACK whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = back_id;\n         uop.new_options = back_id(db).options;\n         uop.new_options.whitelist_authorities.clear();\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Both Sam and Dan should be able to force-settle\n      force_settle( dan_id(db), usd_id(db).amount(100) );\n      force_settle( sam_id(db), usd_id(db).amount(100) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2800 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1600 );\n\n      // Update USDBIT, enable whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Enable USDBIT whitelisting again\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         uop.new_options.whitelist_authorities.insert( sam_id );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam should be able to force-settle, but Dan should be unable to force-settle\n      GRAPHENE_REQUIRE_THROW( force_settle( dan_id(db), usd_id(db).amount(100) ), fc::exception );\n      force_settle( sam_id(db), usd_id(db).amount(100) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2800 );\n      BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1500 );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test )\n{ try {\n\n      generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) );\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam));\n\n      const auto& backasset = create_user_issued_asset(\"BACK\", sam, white_list | charge_market_fee);\n      asset_id_type back_id = backasset.get_id();\n\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id(), 10, white_list | charge_market_fee, 3, back_id);\n      asset_id_type usd_id = bitusd.get_id();\n\n      issue_uia( dan_id, backasset.amount(10000000) );\n      issue_uia( sam_id, backasset.amount(10000000) );\n\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed;\n      current_feed.core_exchange_rate = bitusd.amount( 100 ) / asset( 100 );\n      current_feed.settlement_price = bitusd.amount( 100 ) / backasset.amount( 100 );\n      current_feed.maintenance_collateral_ratio = 1750; // need to set explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), backasset.amount(10000) );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, backasset ), 10000000 - 10000 );\n\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // For BACK\n         uop.asset_to_update = back_id;\n         uop.new_options = back_id(db).options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Sam to the whitelist, but do not add Dan\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Trigger a black swan event, globally settle USDBIT\n      BOOST_TEST_MESSAGE( \"Trigger a black swan event\" );\n      current_feed.settlement_price = bitusd.amount( 10 ) / backasset.amount( 100 );\n      publish_feed( bitusd, sam, current_feed );\n      BOOST_REQUIRE( bitusd.bitasset_data(db).is_globally_settled() );\n\n      // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to bid collateral\n      BOOST_TEST_MESSAGE( \"Dan and Sam attempting to bid collateral\" );\n      bid_collateral( dan_id(db), back_id(db).amount(1), usd_id(db).amount(100) );\n      bid_collateral( sam_id(db), back_id(db).amount(1), usd_id(db).amount(100) );\n\n      // Apply core-973 hardfork\n      BOOST_TEST_MESSAGE( \"Apply core-973 hardfork\" );\n      generate_blocks( HARDFORK_CORE_973_TIME );\n      set_expiration( db, trx );\n\n      // Update price feed\n      publish_feed( usd_id(db), sam_id(db), current_feed );\n\n      // Sam should be able to bid collateral, but Dan should be unable to bid\n      BOOST_TEST_MESSAGE( \"Dan and Sam attempting to bid collateral again\" );\n      GRAPHENE_REQUIRE_THROW( bid_collateral( dan_id(db), back_id(db).amount(2), usd_id(db).amount(200) ),\n                              fc::exception );\n      bid_collateral( sam_id(db), back_id(db).amount(2), usd_id(db).amount(200) );\n\n      // Update USDBIT, disable remove whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Disable USDBIT whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         uop.new_options.whitelist_authorities.clear();\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam should be able to bid collateral, but Dan should be unable to bid\n      GRAPHENE_REQUIRE_THROW( bid_collateral( dan_id(db), back_id(db).amount(3), usd_id(db).amount(300) ),\n                              fc::exception );\n      bid_collateral( sam_id(db), back_id(db).amount(3), usd_id(db).amount(300) );\n\n      // Update BACK, disable whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Disable BACK whitelisting\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = back_id;\n         uop.new_options = back_id(db).options;\n         uop.new_options.whitelist_authorities.clear();\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Both Sam and Dan should be able to bid collateral\n      bid_collateral( dan_id(db), back_id(db).amount(4), usd_id(db).amount(400) );\n      bid_collateral( sam_id(db), back_id(db).amount(4), usd_id(db).amount(400) );\n\n      // Update USDBIT, enable whitelisting\n      {\n         BOOST_TEST_MESSAGE( \"Enable USDBIT whitelisting again\" );\n         asset_update_operation uop;\n         uop.issuer = sam_id;\n\n         // For USDBIT\n         uop.asset_to_update = usd_id;\n         uop.new_options = usd_id(db).options;\n         uop.new_options.whitelist_authorities.insert( sam_id );\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam should be able to bid collateral, but Dan should be unable to bid\n      GRAPHENE_REQUIRE_THROW( bid_collateral( dan_id(db), back_id(db).amount(5), usd_id(db).amount(500) ),\n                              fc::exception );\n      bid_collateral( sam_id(db), back_id(db).amount(5), usd_id(db).amount(500) );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 )\n{ try {\n\n   set_expiration( db, trx );\n\n   BOOST_TEST_MESSAGE( \"Creating a proposal containing a asset_settle_cancel_operation\" );\n   {\n      proposal_create_operation pcop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time());\n      pcop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      pcop.expiration_time = db.head_block_time() + *pcop.review_period_seconds + 10;\n      asset_settle_cancel_operation ascop;\n      ascop.amount.amount = 1;\n      pcop.proposed_ops.emplace_back(ascop);\n      trx.operations.push_back(pcop);\n\n      BOOST_CHECK_EXCEPTION(PUSH_TX(db, trx), fc::assert_exception,\n            [](fc::assert_exception const &e) -> bool {\n               std::cout << e.to_string() << std::endl;\n               if (e.to_string().find(\"Virtual operation\") != std::string::npos)\n                  return true;\n\n               return false;\n            });\n   }\n\n   BOOST_TEST_MESSAGE( \"Creating a recursive proposal containing asset_settle_cancel_operation\" );\n   {\n      proposal_create_operation pcop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time());\n\n      pcop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      pcop.expiration_time = db.head_block_time() + *pcop.review_period_seconds + 10;\n      proposal_create_operation inner_pcop = proposal_create_operation::committee_proposal(\n            db.get_global_properties().parameters, db.head_block_time());\n\n      inner_pcop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n      inner_pcop.expiration_time = db.head_block_time() + *inner_pcop.review_period_seconds + 10;\n\n      asset_settle_cancel_operation ascop;\n      ascop.amount.amount = 1;\n      inner_pcop.proposed_ops.emplace_back(ascop);\n      pcop.proposed_ops.emplace_back(inner_pcop);\n\n      trx.operations.push_back(pcop);\n\n      BOOST_CHECK_EXCEPTION(PUSH_TX(db, trx), fc::assert_exception,\n            [](fc::assert_exception const &e) -> bool {\n               std::cout << e.to_string() << std::endl;\n               if (e.to_string().find(\"Virtual operation\") != std::string::npos)\n                  return true;\n\n               return false;\n            });\n   }\n\n} FC_LOG_AND_RETHROW() }\n\n/// Test case for bsip77:\n/// * the \"initial_collateral_ratio\" parameter can only be set after the BSIP77 hard fork\n/// * the parameter should be within a range\n// TODO removed the hard fork part after the hard fork, keep the valid range part\nBOOST_AUTO_TEST_CASE( bsip77_hardfork_time_and_param_valid_range_test )\n{ try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_583_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      // Before bsip77 hard fork, unable to create a bitasset with ICR\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 1 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 1000 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 1001 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 1750 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 32000 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBIT\", sam_id, 100, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 32001 ), fc::exception );\n\n      // Can create a bitasset without ICR\n      const auto& bitusd = create_bitasset( \"USDBIT\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                            GRAPHENE_MAX_SHARE_SUPPLY );\n      asset_id_type usd_id = bitusd.get_id();\n\n      // helper function for setting ICR for an asset\n      auto set_icr_for_asset = [&](asset_id_type aid, optional<uint16_t> icr) {\n         const asset_object& ao = aid(db);\n         const asset_bitasset_data_object& abo = ao.bitasset_data(db);\n         asset_update_bitasset_operation uop;\n         uop.issuer = ao.issuer;\n         uop.asset_to_update = aid;\n         uop.new_options = abo.options;\n         uop.new_options.extensions.value.initial_collateral_ratio = icr;\n         trx.operations.clear();\n         trx.operations.push_back( uop );\n         trx.validate();\n         set_expiration( db, trx );\n         PUSH_TX(db, trx, ~0);\n      };\n\n      // Before bsip77 hard fork, unable to update a bitasset with ICR\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 0 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1000 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1001 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1750 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 32000 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 32001 ), fc::exception );\n\n      // helper function for creating a proposal which contains an asset_create_operation with ICR\n      auto propose_create_bitasset = [&]( string name, optional<uint16_t> icr ) {\n         asset_create_operation acop = make_bitasset( name, sam_id, 100, charge_market_fee, 2, {},\n                                                      GRAPHENE_MAX_SHARE_SUPPLY, icr );\n         proposal_create_operation cop;\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.expiration_time = db.head_block_time() + 100;\n         cop.proposed_ops.emplace_back( acop );\n         trx.operations.clear();\n         trx.operations.push_back( cop );\n         trx.validate();\n         set_expiration( db, trx );\n         processed_transaction ptx = PUSH_TX(db, trx, ~0);\n         trx.operations.clear();\n      };\n\n      // Before bsip77 hard fork, unable to create a proposal with an asset_create_operation with ICR\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 0 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 1 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 1000 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 1001 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 1750 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 32000 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITA\", 32001 ), fc::exception );\n\n      // helper function for creating a proposal which contains an asset_update_bitasset_operation with ICR\n      auto propose_set_icr_for_asset = [&](asset_id_type aid, optional<uint16_t> icr) {\n         const asset_object& ao = aid(db);\n         const asset_bitasset_data_object& abo = ao.bitasset_data(db);\n         asset_update_bitasset_operation uop;\n         uop.issuer = ao.issuer;\n         uop.asset_to_update = aid;\n         uop.new_options = abo.options;\n         uop.new_options.extensions.value.initial_collateral_ratio = icr;\n\n         proposal_create_operation cop;\n         cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n         cop.expiration_time = db.head_block_time() + 100;\n         cop.proposed_ops.emplace_back( uop );\n         trx.operations.clear();\n         trx.operations.push_back( cop );\n         trx.validate();\n         set_expiration( db, trx );\n         PUSH_TX(db, trx, ~0);\n         trx.operations.clear();\n      };\n\n      // Before bsip77 hard fork, unable to create a proposal with an asset_update_bitasset_op with ICR\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 0 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1000 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1001 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1750 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 32000 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 32001 ), fc::exception );\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_BSIP_77_TIME );\n      set_expiration( db, trx );\n\n      // Unable to create a bitasset with an invalid ICR\n      BOOST_CHECK_THROW( create_bitasset( \"USDBITB\", sam_id, 0, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBITB\", sam_id, 1, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBITB\", sam_id, 1000, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception );\n      BOOST_CHECK_THROW( create_bitasset( \"USDBITB\", sam_id, 32001, charge_market_fee, 2, {},\n                                          GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception );\n      // Able to create a bitasset with a valid ICR\n      asset_id_type usdc_id = create_bitasset( \"USDBITC\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                               GRAPHENE_MAX_SHARE_SUPPLY, 1001 ).get_id();\n      asset_id_type usdd_id = create_bitasset( \"USDBITD\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                               GRAPHENE_MAX_SHARE_SUPPLY, 1750 ).get_id();\n      asset_id_type usde_id = create_bitasset( \"USDBITE\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                               GRAPHENE_MAX_SHARE_SUPPLY, 32000 ).get_id();\n      // Able to create a bitasset without ICR\n      asset_id_type usdf_id = create_bitasset( \"USDBITF\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                               GRAPHENE_MAX_SHARE_SUPPLY, {} ).get_id();\n\n      BOOST_CHECK( usdc_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1001 );\n      BOOST_CHECK( usdd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1750 );\n      BOOST_CHECK( usde_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 32000 );\n      BOOST_CHECK( !usdf_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n\n      // Unable to update a bitasset with an invalid ICR\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 0 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1000 ), fc::exception );\n      BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 32001 ), fc::exception );\n      // Able to update a bitasset with a valid ICR\n      set_icr_for_asset( usd_id, 1001 );\n      BOOST_CHECK( usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1001 );\n      set_icr_for_asset( usd_id, 1750 );\n      BOOST_CHECK( usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1750 );\n      set_icr_for_asset( usd_id, 32000 );\n      BOOST_CHECK( usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 32000 );\n      // Able to update a bitasset, unset its ICR\n      set_icr_for_asset( usd_id, {} );\n      BOOST_CHECK( !usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() );\n\n      // Unable to create a proposal with an asset_create_operation with an invalid ICR\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITG\", 0 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITG\", 1 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITG\", 1000 ), fc::exception );\n      BOOST_CHECK_THROW( propose_create_bitasset( \"USDBITG\", 32001 ), fc::exception );\n      // able to create a proposal with a valid ICR or no ICR\n      propose_create_bitasset( \"USDBITG\", 1001 );\n      propose_create_bitasset( \"USDBITG\", 1750 );\n      propose_create_bitasset( \"USDBITG\", 32000 );\n      propose_create_bitasset( \"USDBITG\", {} );\n\n      // Unable to create a proposal with an asset_update_bitasset_op with an invalid ICR\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 0 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1000 ), fc::exception );\n      BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 32001 ), fc::exception );\n      // Able to create a proposal with a valid ICR or no ICR\n      propose_set_icr_for_asset( usd_id, 1001 );\n      propose_set_icr_for_asset( usd_id, 1750 );\n      propose_set_icr_for_asset( usd_id, 32000 );\n      propose_set_icr_for_asset( usd_id, {} );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_not_set )\n{\n   bsip77 = true;\n   INVOKE( old_call_order_update_test_after_hardfork_583 );\n}\n\nBOOST_AUTO_TEST_CASE( more_call_order_update_test )\n{ try {\n\n      ACTORS((dan)(sam)(alice)(bob));\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id());\n      const auto& core   = asset_id_type()(db);\n\n      transfer(committee_account, dan_id, asset(10000000));\n      transfer(committee_account, sam_id, asset(10000000));\n      transfer(committee_account, alice_id, asset(10000000));\n      transfer(committee_account, bob_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      current_feed.maximum_short_squeeze_ratio = 1100; // need to set this explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 4x collateral at 1:1 price\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(100000), core.amount(400000) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n\n      BOOST_TEST_MESSAGE( \"alice place an order to sell usd at 1.05\" );\n      const limit_order_id_type alice_sell_id = create_sell_order( alice, bitusd.amount(1000), core.amount(1050) )->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow less using 1.75x collateral at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(100), core.amount(175) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      const call_order_id_type bob_call_id = borrow( bob, bitusd.amount(100), asset(200))->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 200 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much more using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000-100), core.amount(17500-200) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to reduce collateral to 1.75x at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(0), core.amount(175-200) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 + 105 );\n      BOOST_CHECK( !db.find( bob_call_id ) );\n\n      BOOST_TEST_MESSAGE( \"alice cancel sell order\" );\n      cancel_limit_order( alice_sell_id(db) );\n\n      BOOST_TEST_MESSAGE( \"dan attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );\n\n      BOOST_TEST_MESSAGE( \"sam update price feed so dan's position will enter margin call territory.\" );\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(180);\n      publish_feed( bitusd, sam, current_feed );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5000 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5001 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5001)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 4999 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(4999)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5000 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 4999 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(4999)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5001 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5001)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 0 usd and freeing 1 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan adding 1 core as collateral should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 )\n{ try {\n\n      auto hf_time = HARDFORK_CORE_583_TIME;\n      if( bsip77 )\n         hf_time = HARDFORK_BSIP_77_TIME;\n      generate_blocks( hf_time );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam)(alice)(bob));\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam.get_id());\n      const auto& core   = asset_id_type()(db);\n\n      transfer(committee_account, dan_id, asset(10000000));\n      transfer(committee_account, sam_id, asset(10000000));\n      transfer(committee_account, alice_id, asset(10000000));\n      transfer(committee_account, bob_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      current_feed.maximum_short_squeeze_ratio = 1100; // need to set this explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"attempting to borrow using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 4x collateral at 1:1 price\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(100000), core.amount(400000) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n\n      BOOST_TEST_MESSAGE( \"alice place an order to sell usd at 1.05\" );\n      const limit_order_id_type alice_sell_id = create_sell_order( alice, bitusd.amount(1000), core.amount(1050) )->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow less using 1.75x collateral at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(100), core.amount(175) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      const call_order_id_type bob_call_id = borrow( bob, bitusd.amount(100), asset(200))->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 200 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much more using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000-100), core.amount(17500-200) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to reduce collateral to 1.75x at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(0), core.amount(175-200) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 + 105 );\n      BOOST_CHECK( !db.find( bob_call_id ) );\n\n      BOOST_TEST_MESSAGE( \"alice cancel sell order\" );\n      cancel_limit_order( alice_sell_id(db) );\n\n      BOOST_TEST_MESSAGE( \"dan attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );\n\n      BOOST_TEST_MESSAGE( \"sam update price feed so dan's position will enter margin call territory.\" );\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(180);\n      publish_feed( bitusd, sam, current_feed );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5000 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5001 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5001)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5000 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 4999 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(4999)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 4999 core should be allowed...\" );\n      cover( dan, bitusd.amount(2500), asset(4999));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999  );\n\n      BOOST_TEST_MESSAGE( \"dan covering 0 usd and freeing 1 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan adding 1 core as collateral should be allowed...\" );\n      borrow( dan, bitusd.amount(0), asset(1));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999 - 1  );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5002 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5002)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5003 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), asset(5003) ), fc::exception );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_not_set )\n{\n   bsip77 = true;\n   INVOKE( more_call_order_update_test_after_hardfork_583 );\n}\n\nBOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_set )\n{ try {\n\n      auto hf_time = HARDFORK_BSIP_77_TIME;\n      generate_blocks( hf_time );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam)(alice)(bob));\n      const auto& bitusd = create_bitasset( \"USDBIT\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                            GRAPHENE_MAX_SHARE_SUPPLY, 1050 ); // ICR = 1.05\n      const auto& core   = asset_id_type()(db);\n\n      asset_id_type usd_id = bitusd.get_id();\n\n      // helper function for setting ICR for an asset\n      auto set_icr_for_asset = [&](asset_id_type aid, optional<uint16_t> icr) {\n         const asset_object& ao = aid(db);\n         const asset_bitasset_data_object& abo = ao.bitasset_data(db);\n         asset_update_bitasset_operation uop;\n         uop.issuer = ao.issuer;\n         uop.asset_to_update = aid;\n         uop.new_options = abo.options;\n         uop.new_options.extensions.value.initial_collateral_ratio = icr;\n         trx.operations.clear();\n         trx.operations.push_back( uop );\n         trx.validate();\n         set_expiration( db, trx );\n         PUSH_TX(db, trx, ~0);\n      };\n\n      transfer(committee_account, dan_id, asset(10000000));\n      transfer(committee_account, sam_id, asset(10000000));\n      transfer(committee_account, alice_id, asset(10000000));\n      transfer(committee_account, bob_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      current_feed.maximum_short_squeeze_ratio = 1100; // need to set this explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed );\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"ICR 1.05, MCR 1.75\" );\n      BOOST_TEST_MESSAGE( \"attempting to borrow using <=1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17499) ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 1.7501x collateral at 1:1 price should be allowed\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(10000), core.amount(17501) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 17501 );\n      BOOST_TEST_MESSAGE( \"ICR 1.05, MCR 1.75, Alice CR 1.7501\" );\n\n      // Update ICR\n      BOOST_TEST_MESSAGE( \"Updating ICR to 1.85\" );\n      set_icr_for_asset( usd_id, 1850 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.7501\" );\n\n      BOOST_TEST_MESSAGE( \"alice adding more collateral should be allowed\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(0), core.amount(18000-17501) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.8000\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral should not be allowed if CR<=1.85 and not margin called\" );\n      GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 1.8502x collateral at 1:1 price should be allowed\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(0), core.amount(18502-18000) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18502 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.8502\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to >1.85x should be allowed\" );\n      cover( alice, bitusd.amount(0), core.amount(1) );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18501 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.8501\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to <=1.85x should not be allowed if not margin called\" );\n      GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 4x collateral at 1:1 price\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(100000-10000), core.amount(400000-18501) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 4.0000\" );\n\n      BOOST_TEST_MESSAGE( \"alice place an order to sell usd at 1.05\" );\n      const limit_order_id_type alice_sell_id = create_sell_order( alice, bitusd.amount(1000), core.amount(1050) )->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow less using 1.75x collateral at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(100), core.amount(175) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      const call_order_id_type bob_call_id = borrow( bob, bitusd.amount(100), asset(200))->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 200 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much more using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000-100), core.amount(17500-200) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to reduce collateral to 1.75x at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(0), core.amount(175-200) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 + 105 );\n      BOOST_CHECK( !db.find( bob_call_id ) );\n\n      BOOST_TEST_MESSAGE( \"alice cancel sell order\" );\n      cancel_limit_order( alice_sell_id(db) );\n\n      BOOST_TEST_MESSAGE( \"dan attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );\n\n      BOOST_TEST_MESSAGE( \"sam update price feed so dan's position will enter margin call territory.\" );\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(180);\n      publish_feed( bitusd, sam, current_feed );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5000 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5001 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5001)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5000 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 4999 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(4999)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 4999 core should be allowed...\" );\n      cover( dan, bitusd.amount(2500), asset(4999));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999  );\n\n      BOOST_TEST_MESSAGE( \"dan covering 0 usd and freeing 1 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan adding 1 core as collateral should be allowed...\" );\n      borrow( dan, bitusd.amount(0), asset(1));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999 - 1  );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5002 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5002)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5003 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), asset(5003) ), fc::exception );\n\n      // CR of Alice's postion is now 4.0 / 1.8 ~= 2.2222\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 2.222222\" );\n\n      BOOST_TEST_MESSAGE( \"alice adding more collateral should be allowed\" );\n      const call_order_id_type alice_call_id = borrow( alice, bitusd.amount(0), asset(1))->get_id();\n      BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 400000 + 1 );\n      BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 2.222228\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to >1.85x should be allowed\" );\n      cover( alice, bitusd.amount(0), core.amount(67000) );\n      BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 333001 );\n      BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.850006\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to <=1.85x should not be allowed if not margin called\" );\n      GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1) ), fc::exception );\n\n      // Update ICR\n      BOOST_TEST_MESSAGE( \"Updating ICR to 1.84\" );\n      set_icr_for_asset( usd_id, 1840 );\n      BOOST_TEST_MESSAGE( \"ICR 1.84, MCR 1.75, Alice CR 1.850006\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to >1.84x should be allowed\" );\n      cover( alice, bitusd.amount(0), core.amount(1) );\n      BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 333000 );\n      BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_fed )\n{ try {\n\n      auto hf_time = HARDFORK_BSIP_77_TIME;\n      generate_blocks( hf_time );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((dan)(sam)(alice)(bob));\n      const auto& bitusd = create_bitasset( \"USDBIT\", sam.get_id(), 100, charge_market_fee, 2, {},\n                                            GRAPHENE_MAX_SHARE_SUPPLY, {} ); // ICR is not set\n      const auto& core   = asset_id_type()(db);\n\n      transfer(committee_account, dan_id, asset(10000000));\n      transfer(committee_account, sam_id, asset(10000000));\n      transfer(committee_account, alice_id, asset(10000000));\n      transfer(committee_account, bob_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      current_feed.maximum_short_squeeze_ratio = 1100; // need to set this explicitly, testnet has a different default\n      publish_feed( bitusd, sam, current_feed, 1050 ); // ICR = 1.05\n\n      FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"ICR 1.05, MCR 1.75\" );\n      BOOST_TEST_MESSAGE( \"attempting to borrow using <=1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17499) ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 1.7501x collateral at 1:1 price should be allowed\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(10000), core.amount(17501) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 17501 );\n      BOOST_TEST_MESSAGE( \"ICR 1.05, MCR 1.75, Alice CR 1.7501\" );\n\n      // Update ICR\n      BOOST_TEST_MESSAGE( \"Updating ICR to 1.85\" );\n      publish_feed( bitusd, sam, current_feed, 1850 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.7501\" );\n\n      BOOST_TEST_MESSAGE( \"alice adding more collateral should be allowed\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(0), core.amount(18000-17501) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.8000\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral should not be allowed if CR<=1.85 and not margin called\" );\n      GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 1.8502x collateral at 1:1 price should be allowed\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(0), core.amount(18502-18000) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18502 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.8502\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to >1.85x should be allowed\" );\n      cover( alice, bitusd.amount(0), core.amount(1) );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18501 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.8501\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to <=1.85x should not be allowed if not margin called\" );\n      GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"alice borrow using 4x collateral at 1:1 price\" );\n      BOOST_CHECK( borrow( alice, bitusd.amount(100000-10000), core.amount(400000-18501) ) != nullptr );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 4.0000\" );\n\n      BOOST_TEST_MESSAGE( \"alice place an order to sell usd at 1.05\" );\n      const limit_order_id_type alice_sell_id = create_sell_order( alice, bitusd.amount(1000), core.amount(1050) )->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow less using 1.75x collateral at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(100), core.amount(175) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      const call_order_id_type bob_call_id = borrow( bob, bitusd.amount(100), asset(200))->get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 200 );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to borrow too much more using 1.75x collateral at 1:1 price should not be allowed\" );\n      GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000-100), core.amount(17500-200) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"bob attempting to reduce collateral to 1.75x at 1:1 price should be allowed and margin called\" );\n      BOOST_CHECK( !borrow( bob, bitusd.amount(0), core.amount(175-200) ) );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 );\n      BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 105 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 );\n      BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 + 105 );\n      BOOST_CHECK( !db.find( bob_call_id ) );\n\n      BOOST_TEST_MESSAGE( \"alice cancel sell order\" );\n      cancel_limit_order( alice_sell_id(db) );\n\n      BOOST_TEST_MESSAGE( \"dan attempting to borrow using 2x collateral at 1:1 price now that there is a valid order\" );\n      borrow( dan, bitusd.amount(5000), asset(10000));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 );\n\n      BOOST_TEST_MESSAGE( \"sam update price feed so dan's position will enter margin call territory.\" );\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(180);\n      publish_feed( bitusd, sam, current_feed, 1850 );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5000 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 5001 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5001)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5000 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5000)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 4999 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(4999)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan covering 2500 usd and freeing 4999 core should be allowed...\" );\n      cover( dan, bitusd.amount(2500), asset(4999));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999  );\n\n      BOOST_TEST_MESSAGE( \"dan covering 0 usd and freeing 1 core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), core.amount(1)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan adding 1 core as collateral should be allowed...\" );\n      borrow( dan, bitusd.amount(0), asset(1));\n      BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 );\n      BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999 - 1  );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5002 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5002)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"dan borrow 2500 more usd wth 5003 more core should not be allowed...\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), asset(5003) ), fc::exception );\n\n      // CR of Alice's postion is now 4.0 / 1.8 ~= 2.2222\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 2.222222\" );\n\n      BOOST_TEST_MESSAGE( \"alice adding more collateral should be allowed\" );\n      const call_order_id_type alice_call_id = borrow( alice, bitusd.amount(0), asset(1))->get_id();\n      BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 400000 + 1 );\n      BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 2.222228\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to >1.85x should be allowed\" );\n      cover( alice, bitusd.amount(0), core.amount(67000) );\n      BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 333001 );\n      BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 );\n      BOOST_TEST_MESSAGE( \"ICR 1.85, MCR 1.75, Alice CR 1.850006\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to <=1.85x should not be allowed if not margin called\" );\n      GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1) ), fc::exception );\n\n      // Update ICR\n      BOOST_TEST_MESSAGE( \"Updating ICR to 1.84\" );\n      publish_feed( bitusd, sam, current_feed, 1840 );\n      BOOST_TEST_MESSAGE( \"ICR 1.84, MCR 1.75, Alice CR 1.850006\" );\n\n      BOOST_TEST_MESSAGE( \"alice reducing collateral to >1.84x should be allowed\" );\n      cover( alice, bitusd.amount(0), core.amount(1) );\n      BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 333000 );\n      BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( call_order_update_validation_test )\n{ try {\n\n   call_order_update_operation op;\n\n   // throw on default values\n   BOOST_CHECK_THROW( op.validate(), fc::assert_exception );\n\n   // minimum changes to make it valid\n   op.delta_debt = asset( 1, asset_id_type(1) );\n   op.validate(); // won't throw if has a non-zero debt with different asset_id_type than collateral\n\n   // throw on negative fee\n   op.fee = asset( -1 );\n   BOOST_CHECK_THROW( op.validate(), fc::assert_exception );\n   op.fee = asset( 0 );\n\n   // throw on identical debt and collateral asset id\n   op.delta_collateral = asset( 0, asset_id_type(1) );\n   BOOST_CHECK_THROW( op.validate(), fc::assert_exception );\n\n   // throw on zero debt and collateral amount\n   op.delta_debt = asset( 0, asset_id_type(0) );\n   BOOST_CHECK_THROW( op.validate(), fc::assert_exception );\n   op.delta_debt = asset( -1, asset_id_type(0) );\n\n   op.validate(); // valid now\n\n   op.extensions.value.target_collateral_ratio = 0;\n   op.validate(); // still valid\n\n   op.extensions.value.target_collateral_ratio = 65535;\n   op.validate(); // still valid\n\n} FC_LOG_AND_RETHROW() }\n\n/**\n *  This test sets up a situation where a margin call will be executed and ensures that\n *  it is properly filled.\n *\n *  A margin call can happen in the following situation:\n *  0. there exists a bid above the mas short squeeze price\n *  1. highest bid is lower than the call price of an order\n *  2. the asset is not a prediction market\n *  3. there is a valid price feed\n *\n *  This test creates two scenarios:\n *  a) when the bids are above the short squeese limit (should execute)\n *  b) when the bids are below the short squeeze limit (should not execute)\n */\nBOOST_AUTO_TEST_CASE( margin_call_limit_test )\n{ try {\n\n      ACTORS((buyer)(seller)(borrower)(borrower2)(feedproducer));\n\n      const auto& bitusd = create_bitasset(\"USDBIT\", feedproducer_id);\n      const auto& core   = asset_id_type()(db);\n\n      int64_t init_balance(1000000);\n\n      transfer(committee_account, buyer_id, asset(init_balance));\n      transfer(committee_account, borrower_id, asset(init_balance));\n      transfer(committee_account, borrower2_id, asset(init_balance));\n      update_feed_producers( bitusd, {feedproducer.get_id()} );\n\n      price_feed current_feed;\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      current_feed.maximum_short_squeeze_ratio  = 1500; // need to set this explicitly, testnet has a different default\n\n      // starting out with price 1:1\n      publish_feed( bitusd, feedproducer, current_feed );\n\n      // start out with 2:1 collateral\n      borrow( borrower, bitusd.amount(1000), asset(2000));\n      borrow( borrower2, bitusd.amount(1000), asset(4000) );\n\n      BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n      BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 );\n      BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 2000 );\n      BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 4000 );\n\n      // this should trigger margin call that is below the call limit, but above the\n      // protection threshold.\n      BOOST_TEST_MESSAGE( \"Creating a margin call that is NOT protected by the max short squeeze price\" );\n      auto order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1400) );\n      if( db.head_block_time() <= HARDFORK_436_TIME )\n      {\n         BOOST_CHECK( order == nullptr );\n\n         BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 4000 + 1400 );\n         BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 );\n\n         BOOST_CHECK_EQUAL( get_balance( borrower, core ), init_balance - 2000 + 600 );\n         BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n      }\n      else\n      {\n         BOOST_CHECK( order != nullptr );\n\n         BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 );\n         BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 );\n         BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 2000 );\n         BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 4000 );\n      }\n\n      BOOST_TEST_MESSAGE( \"Creating a margin call that is protected by the max short squeeze price\" );\n      borrow( borrower, bitusd.amount(1000), asset(2000) );\n      borrow( borrower2, bitusd.amount(1000), asset(4000) );\n\n      // this should trigger margin call without protection from the price feed.\n      order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) );\n      BOOST_CHECK( order != nullptr );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( prediction_market )\n{ try {\n\n      ACTORS((judge)(dan)(nathan));\n\n      const auto& pmark = create_prediction_market(\"PMARK\", judge_id);\n      const auto pmark_dd_id = pmark.dynamic_asset_data_id;\n      const auto& core  = asset_id_type()(db);\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, judge_id, asset(init_balance));\n      transfer(committee_account, dan_id, asset(init_balance));\n      transfer(committee_account, nathan_id, asset(init_balance));\n\n      update_feed_producers( pmark, { judge_id });\n      price_feed feed;\n      feed.settlement_price = asset( 1, pmark.get_id() ) / asset( 1 );\n      publish_feed( pmark, judge, feed );\n\n      BOOST_TEST_MESSAGE( \"Require throw for mismatch collateral amounts\" );\n      GRAPHENE_REQUIRE_THROW( borrow( dan, pmark.amount(1000), asset(2000) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Open position with equal collateral\" );\n      borrow( dan, pmark.amount(1000), asset(1000) );\n\n      BOOST_TEST_MESSAGE( \"Cover position with unequal asset should fail.\" );\n      GRAPHENE_REQUIRE_THROW( cover( dan, pmark.amount(500), asset(1000) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Cover half of position with equal ammounts\" );\n      cover( dan, pmark.amount(500), asset(500) );\n\n      BOOST_TEST_MESSAGE( \"Verify that forced settlment fails before global settlement\" );\n      GRAPHENE_REQUIRE_THROW( force_settle( dan, pmark.amount(100) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Shouldn't be allowed to force settle at more than 1 collateral per debt\" );\n      GRAPHENE_REQUIRE_THROW( force_global_settle( pmark, pmark.amount(100) / core.amount(105) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Globally settling\" );\n      force_global_settle( pmark, pmark.amount(100) / core.amount(95) );\n\n      BOOST_TEST_MESSAGE( \"Can not globally settle again\" );\n      GRAPHENE_REQUIRE_THROW( force_global_settle( pmark, pmark.amount(100) / core.amount(95) ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Verify that forced settlment succeedes after global settlement\" );\n      force_settle( dan, pmark.amount(100) );\n\n      // force settle the rest\n      force_settle( dan, pmark.amount(400) );\n      BOOST_CHECK_EQUAL( 0, pmark_dd_id(db).current_supply.value );\n\n      generate_block(~database::skip_transaction_dupe_check);\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( prediction_market_resolves_to_0 )\n{ try {\n\n      ACTORS((judge)(dan)(nathan));\n\n      const auto& pmark = create_prediction_market(\"PMARK\", judge_id);\n      const auto pmark_dd_id = pmark.dynamic_asset_data_id;\n      const auto& core  = asset_id_type()(db);\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, judge_id, asset(init_balance));\n      transfer(committee_account, dan_id, asset(init_balance));\n      transfer(committee_account, nathan_id, asset(init_balance));\n\n      update_feed_producers( pmark, { judge_id });\n      price_feed feed;\n      feed.settlement_price = asset( 1, pmark.get_id() ) / asset( 1 );\n      publish_feed( pmark, judge, feed );\n\n      borrow( dan, pmark.amount(1000), asset(1000) );\n      // force settle with 0 outcome\n      force_global_settle( pmark, pmark.amount(100) / core.amount(0) );\n\n      BOOST_TEST_MESSAGE( \"Verify that forced settlment succeedes after global settlement\" );\n      force_settle( dan, pmark.amount(100) );\n\n      // force settle the rest\n      force_settle( dan, pmark.amount(900) );\n      BOOST_CHECK_EQUAL( 0, pmark_dd_id(db).current_supply.value );\n\n      generate_block(~database::skip_transaction_dupe_check);\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/***\n * Prediction markets should not suffer a black swan (Issue #460)\n */\nBOOST_AUTO_TEST_CASE( prediction_market_black_swan )\n{ try {\n\n      ACTORS((judge)(dan)(nathan));\n\n      // progress to recent hardfork\n      generate_blocks( HARDFORK_CORE_1270_TIME );\n      set_expiration( db, trx );\n\n      const auto& pmark = create_prediction_market(\"PMARK\", judge_id);\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, judge_id, asset(init_balance));\n      transfer(committee_account, dan_id, asset(init_balance));\n\n      update_feed_producers( pmark, { judge_id });\n      price_feed feed;\n      feed.settlement_price = asset( 1, pmark.get_id() ) / asset( 1 );\n      publish_feed( pmark, judge, feed );\n\n      borrow( dan, pmark.amount(1000), asset(1000) );\n\n      // feed a price that will cause a black swan\n      feed.settlement_price = asset( 1, pmark.get_id() ) / asset( 1000 );\n      publish_feed( pmark, judge, feed );\n\n      // verify a black swan happened\n      GRAPHENE_REQUIRE_THROW(borrow( dan, pmark.amount(1000), asset(1000) ), fc::exception);\n      trx.clear();\n\n      // progress past hardfork\n      generate_blocks( HARDFORK_CORE_460_TIME + db.get_global_properties().parameters.maintenance_interval );\n      set_expiration( db, trx );\n\n      // create another prediction market to test the hardfork\n      const auto& pmark2 = create_prediction_market(\"PMARKII\", judge_id);\n      update_feed_producers( pmark2, { judge_id });\n      price_feed feed2;\n      feed2.settlement_price = asset( 1, pmark2.get_id() ) / asset( 1 );\n      publish_feed( pmark2, judge, feed2 );\n\n      borrow( dan, pmark2.amount(1000), asset(1000) );\n\n      // feed a price that would have caused a black swan\n      feed2.settlement_price = asset( 1, pmark2.get_id() ) / asset( 1000 );\n      publish_feed( pmark2, judge, feed2 );\n\n      // verify a black swan did not happen\n      borrow( dan, pmark2.amount(1000), asset(1000) );\n\n      generate_block(~database::skip_transaction_dupe_check);\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_account_test )\n{ try {\n\n      generate_blocks( HARDFORK_CORE_143_TIME );\n      set_expiration( db, trx );\n      trx.operations.push_back(make_account());\n      account_create_operation op = trx.operations.back().get<account_create_operation>();\n\n      REQUIRE_THROW_WITH_VALUE(op, registrar, account_id_type(9999999));\n      REQUIRE_THROW_WITH_VALUE(op, fee, asset(-1));\n      REQUIRE_THROW_WITH_VALUE(op, name, \"!\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"Sam\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"saM\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"sAm\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"6j\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"j-\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"-j\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \"aaaa.\");\n      REQUIRE_THROW_WITH_VALUE(op, name, \".aaaa\");\n      REQUIRE_THROW_WITH_VALUE(op, options.voting_account, account_id_type(999999999));\n\n      // Not allow voting for non-exist entities.\n      auto save_num_committee = op.options.num_committee;\n      auto save_num_witness = op.options.num_witness;\n      op.options.num_committee = 1;\n      op.options.num_witness = 0;\n      REQUIRE_THROW_WITH_VALUE(op, options.votes, boost::assign::list_of<vote_id_type>(vote_id_type(\"0:1\")).convert_to_container<flat_set<vote_id_type>>());\n      op.options.num_witness = 1;\n      op.options.num_committee = 0;\n      REQUIRE_THROW_WITH_VALUE(op, options.votes, boost::assign::list_of<vote_id_type>(vote_id_type(\"1:19\")).convert_to_container<flat_set<vote_id_type>>());\n      op.options.num_witness = 0;\n      REQUIRE_THROW_WITH_VALUE(op, options.votes, boost::assign::list_of<vote_id_type>(vote_id_type(\"2:19\")).convert_to_container<flat_set<vote_id_type>>());\n      REQUIRE_THROW_WITH_VALUE(op, options.votes, boost::assign::list_of<vote_id_type>(vote_id_type(\"3:99\")).convert_to_container<flat_set<vote_id_type>>());\n      GRAPHENE_REQUIRE_THROW( vote_id_type(\"2:a\"), fc::exception );\n      GRAPHENE_REQUIRE_THROW( vote_id_type(\"\"), fc::exception );\n      op.options.num_committee = save_num_committee;\n      op.options.num_witness = save_num_witness;\n\n      auto auth_bak = op.owner;\n      op.owner.add_authority(account_id_type(9999999999), 10);\n      trx.operations.back() = op;\n      op.owner = auth_bak;\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n      op.owner = auth_bak;\n\n      trx.operations.back() = op;\n      sign( trx,  init_account_priv_key );\n      trx.validate();\n      PUSH_TX( db, trx, ~0 );\n\n      const account_object& nathan_account = *db.get_index_type<account_index>().indices().get<by_name>().find(\"nathan\");\n      BOOST_CHECK(nathan_account.id.space() == protocol_ids);\n      BOOST_CHECK(nathan_account.id.type() == account_object_type);\n      BOOST_CHECK(nathan_account.name == \"nathan\");\n\n      BOOST_REQUIRE(nathan_account.owner.num_auths() == 1);\n      BOOST_CHECK(nathan_account.owner.key_auths.at(committee_key) == 123);\n      BOOST_REQUIRE(nathan_account.active.num_auths() == 1);\n      BOOST_CHECK(nathan_account.active.key_auths.at(committee_key) == 321);\n      BOOST_CHECK(nathan_account.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT);\n      BOOST_CHECK(nathan_account.options.memo_key == committee_key);\n\n      const account_statistics_object& statistics = nathan_account.statistics(db);\n      BOOST_CHECK(statistics.id.space() == implementation_ids);\n      BOOST_CHECK(statistics.id.type() == impl_account_statistics_object_type);\n\n      account_id_type nathan_id = nathan_account.get_id();\n\n      generate_block();\n\n      BOOST_CHECK_EQUAL( nathan_id(db).creation_block_num, db.head_block_num() );\n      BOOST_CHECK( nathan_id(db).creation_time == db.head_block_time() );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( update_account )\n{ try {\n\n      const account_object& nathan = create_account(\"nathan\", init_account_pub_key);\n      const fc::ecc::private_key nathan_new_key = fc::ecc::private_key::generate();\n      const public_key_type key_id = nathan_new_key.get_public_key();\n      const auto& active_committee_members = db.get_global_properties().active_committee_members;\n\n      transfer(account_id_type()(db), nathan, asset(1000000000));\n\n      trx.operations.clear();\n      account_update_operation op;\n      op.account = nathan.id;\n      op.owner = authority(2, key_id, 1, init_account_pub_key, 1);\n      op.active = authority(2, key_id, 1, init_account_pub_key, 1);\n      op.new_options = nathan.options;\n      op.new_options->votes = flat_set<vote_id_type>({active_committee_members[0](db).vote_id, active_committee_members[5](db).vote_id});\n      op.new_options->num_committee = 2;\n      trx.operations.push_back(op);\n      BOOST_TEST_MESSAGE( \"Updating account\" );\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK(nathan.options.memo_key == init_account_pub_key);\n      BOOST_CHECK(nathan.active.weight_threshold == 2);\n      BOOST_CHECK(nathan.active.num_auths() == 2);\n      BOOST_CHECK(nathan.active.key_auths.at(key_id) == 1);\n      BOOST_CHECK(nathan.active.key_auths.at(init_account_pub_key) == 1);\n      BOOST_CHECK(nathan.owner.weight_threshold == 2);\n      BOOST_CHECK(nathan.owner.num_auths() == 2);\n      BOOST_CHECK(nathan.owner.key_auths.at(key_id) == 1);\n      BOOST_CHECK(nathan.owner.key_auths.at(init_account_pub_key) == 1);\n      BOOST_CHECK(nathan.options.votes.size() == 2);\n\n      enable_fees();\n      {\n         account_upgrade_operation op;\n         op.account_to_upgrade = nathan.id;\n         op.upgrade_to_lifetime_member = true;\n         op.fee = db.get_global_properties().parameters.get_current_fees().calculate_fee(op);\n         trx.operations = {op};\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( nathan.is_lifetime_member() );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( transfer_core_asset )\n{ try {\n\n      INVOKE(create_account_test);\n\n      account_id_type committee_account;\n      asset committee_balance = db.get_balance(account_id_type(), asset_id_type());\n\n      const account_object& nathan_account = *db.get_index_type<account_index>().indices().get<by_name>().find(\"nathan\");\n      transfer_operation top;\n      top.from = committee_account;\n      top.to = nathan_account.id;\n      top.amount = asset( 10000);\n      trx.operations.push_back(top);\n      for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n\n      asset fee = trx.operations.front().get<transfer_operation>().fee;\n      trx.validate();\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(get_balance(account_id_type()(db), asset_id_type()(db)),\n                        (committee_balance.amount - 10000 - fee.amount).value);\n      committee_balance = db.get_balance(account_id_type(), asset_id_type());\n\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 10000);\n\n      trx = signed_transaction();\n      top.from = nathan_account.id;\n      top.to = committee_account;\n      top.amount = asset(2000);\n      trx.operations.push_back(top);\n\n      for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n\n      fee = trx.operations.front().get<transfer_operation>().fee;\n      set_expiration( db, trx );\n      trx.validate();\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 8000 - fee.amount.value);\n      BOOST_CHECK_EQUAL(get_balance(account_id_type()(db), asset_id_type()(db)), committee_balance.amount.value + 2000);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_committee_member )\n{ try {\n\n      committee_member_create_operation op;\n      op.committee_member_account = account_id_type();\n      op.fee = asset();\n      trx.operations.push_back(op);\n\n      REQUIRE_THROW_WITH_VALUE(op, committee_member_account, account_id_type(99999999));\n      REQUIRE_THROW_WITH_VALUE(op, fee, asset(-600));\n      trx.operations.back() = op;\n\n      committee_member_id_type committee_member_id { db.get_index_type<committee_member_index>().get_next_id() };\n      PUSH_TX( db, trx, ~0 );\n      const committee_member_object& d = committee_member_id(db);\n\n      BOOST_CHECK(d.committee_member_account == account_id_type());\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_mia )\n{ try {\n\n      const asset_object& bitusd = create_bitasset( \"USDBIT\" );\n      BOOST_CHECK(bitusd.symbol == \"USDBIT\");\n      BOOST_CHECK(bitusd.bitasset_data(db).options.short_backing_asset == asset_id_type());\n      BOOST_CHECK(bitusd.dynamic_asset_data_id(db).current_supply == 0);\n      GRAPHENE_REQUIRE_THROW( create_bitasset(\"USDBIT\"), fc::exception);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( update_mia )\n{ try {\n\n      INVOKE(create_mia);\n      generate_block();\n      const asset_object& bit_usd = get_asset(\"USDBIT\");\n\n      asset_update_operation op;\n      op.issuer = bit_usd.issuer;\n      op.asset_to_update = bit_usd.id;\n      op.new_options = bit_usd.options;\n      trx.operations.emplace_back(op);\n\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n      std::swap(op.new_options.flags, op.new_options.issuer_permissions);\n      op.new_issuer = account_id_type();\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      {\n         asset_publish_feed_operation pop;\n         pop.asset_id = bit_usd.get_id();\n         pop.publisher = get_account(\"init0\").get_id();\n         price_feed feed;\n         feed.settlement_price = feed.core_exchange_rate = price(bit_usd.amount(5), bit_usd.amount(5));\n         REQUIRE_THROW_WITH_VALUE(pop, feed, feed);\n         feed.settlement_price = feed.core_exchange_rate = ~price(bit_usd.amount(5), asset(5));\n         REQUIRE_THROW_WITH_VALUE(pop, feed, feed);\n         feed.settlement_price = feed.core_exchange_rate = price(bit_usd.amount(5), asset(5));\n         pop.feed = feed;\n         REQUIRE_THROW_WITH_VALUE(pop, feed.maintenance_collateral_ratio, 0);\n         trx.operations.back() = pop;\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      trx.operations.clear();\n      auto nathan = create_account(\"nathan\");\n      op.issuer = account_id_type();\n      op.new_issuer = nathan.id;\n      trx.operations.emplace_back(op);\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK(bit_usd.issuer == nathan.id);\n\n      op.issuer = nathan.id;\n      op.new_issuer = account_id_type();\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK(bit_usd.issuer == account_id_type());\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_uia )\n{ try {\n\n      asset_id_type test_asset_id { db.get_index<asset_object>().get_next_id() };\n      asset_create_operation creator;\n      creator.issuer = account_id_type();\n      creator.fee = asset();\n      creator.symbol = UIA_TEST_SYMBOL;\n      creator.common_options.max_supply = 100000000;\n      creator.precision = 2;\n      creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/\n      creator.common_options.issuer_permissions = DEFAULT_UIA_ASSET_ISSUER_PERMISSION;\n      creator.common_options.flags = charge_market_fee;\n      creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1)));\n      trx.operations.push_back(std::move(creator));\n      PUSH_TX( db, trx, ~0 );\n\n      const asset_object& test_asset = test_asset_id(db);\n      BOOST_CHECK(test_asset.symbol == UIA_TEST_SYMBOL);\n      BOOST_CHECK(asset(1, test_asset_id) * test_asset.options.core_exchange_rate == asset(2));\n      BOOST_CHECK((test_asset.options.flags & white_list) == 0);\n      BOOST_CHECK(test_asset.options.max_supply == 100000000);\n      BOOST_CHECK(!test_asset.bitasset_data_id.valid());\n      BOOST_CHECK(test_asset.options.market_fee_percent == GRAPHENE_MAX_MARKET_FEE_PERCENT/100);\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n\n      const asset_dynamic_data_object& test_asset_dynamic_data = test_asset.dynamic_asset_data_id(db);\n      BOOST_CHECK(test_asset_dynamic_data.current_supply == 0);\n      BOOST_CHECK(test_asset_dynamic_data.accumulated_fees == 0);\n      BOOST_CHECK(test_asset_dynamic_data.fee_pool == 0);\n\n      auto op = trx.operations.back().get<asset_create_operation>();\n      op.symbol = \"TESTFAIL\";\n      REQUIRE_THROW_WITH_VALUE(op, issuer, account_id_type(99999999));\n      REQUIRE_THROW_WITH_VALUE(op, common_options.max_supply, -1);\n      REQUIRE_THROW_WITH_VALUE(op, common_options.max_supply, 0);\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \"A\");\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \"qqq\");\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \"11\");\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \".AAA\");\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \"AAA.\");\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \"AB CD\");\n      REQUIRE_THROW_WITH_VALUE(op, symbol, \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\");\n      REQUIRE_THROW_WITH_VALUE(op, common_options.core_exchange_rate, price(asset(-100), asset(1)));\n      REQUIRE_THROW_WITH_VALUE(op, common_options.core_exchange_rate, price(asset(100),asset(-1)));\n\n      generate_block();\n\n      BOOST_CHECK_EQUAL( test_asset_id(db).creation_block_num, db.head_block_num() );\n      BOOST_CHECK( test_asset_id(db).creation_time == db.head_block_time() );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( update_uia )\n{ try {\n\n      INVOKE(create_uia);\n      const auto& test = get_asset(UIA_TEST_SYMBOL);\n      const auto& nathan = create_account(\"nathan\");\n\n      asset_update_operation op;\n      op.issuer = test.issuer;\n      op.asset_to_update = test.id;\n      op.new_options = test.options;\n\n      trx.operations.push_back(op);\n\n      //Cannot change issuer to same as before\n      BOOST_TEST_MESSAGE( \"Make sure changing issuer to same as before is forbidden\" );\n      REQUIRE_THROW_WITH_VALUE(op, new_issuer, test.issuer);\n\n      //Cannot convert to an MIA\n      BOOST_TEST_MESSAGE( \"Make sure we can't convert UIA to MIA\" );\n      REQUIRE_THROW_WITH_VALUE(op, new_options.issuer_permissions, ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK);\n      REQUIRE_THROW_WITH_VALUE(op, new_options.core_exchange_rate, price(asset(5), asset(5)));\n\n      BOOST_TEST_MESSAGE( \"Test updating core_exchange_rate\" );\n      op.new_options.core_exchange_rate = price(asset(3), test.amount(5));\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n      REQUIRE_THROW_WITH_VALUE(op, new_options.core_exchange_rate, price());\n      op.new_options.core_exchange_rate = test.options.core_exchange_rate;\n      op.new_issuer = nathan.id;\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_TEST_MESSAGE( \"Test setting flags\" );\n      op.issuer = nathan.id;\n      op.new_issuer.reset();\n      op.new_options.flags = transfer_restricted | white_list;\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_TEST_MESSAGE( \"Disable white_list permission\" );\n      op.new_options.issuer_permissions = test.options.issuer_permissions & ~white_list;\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_TEST_MESSAGE( \"Can't toggle white_list\" );\n      REQUIRE_THROW_WITH_VALUE(op, new_options.flags, test.options.flags & ~white_list);\n\n      BOOST_TEST_MESSAGE( \"Can toggle transfer_restricted\" );\n      for( int i=0; i<2; i++ )\n      {\n         op.new_options.flags = test.options.flags ^ transfer_restricted;\n         trx.operations.back() = op;\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      asset_issue_operation issue_op;\n      issue_op.issuer = op.issuer;\n      issue_op.asset_to_issue =  asset(5000000,op.asset_to_update);\n      issue_op.issue_to_account = nathan.get_id();\n      trx.operations.push_back(issue_op);\n      PUSH_TX(db, trx, ~0);\n\n      BOOST_TEST_MESSAGE( \"Make sure white_list can't be re-enabled (after tokens issued)\" );\n      op.new_options.issuer_permissions = test.options.issuer_permissions;\n      op.new_options.flags = test.options.flags;\n      BOOST_CHECK(!(test.options.issuer_permissions & white_list));\n      REQUIRE_THROW_WITH_VALUE(op, new_options.issuer_permissions, DEFAULT_UIA_ASSET_ISSUER_PERMISSION);\n\n      BOOST_TEST_MESSAGE( \"We can change issuer to account_id_type(), but can't do it again\" );\n      op.new_issuer = account_id_type();\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n      op.issuer = account_id_type();\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n      op.new_issuer.reset();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( update_uia_issuer )\n{ try {\n\n      // Lambda for creating accounts with 2 different keys\n      auto create_account_2_keys = [&]( const string name,\n           fc::ecc::private_key active,\n           fc::ecc::private_key owner ) {\n\n         trx.operations.push_back(make_account());\n         account_create_operation op = trx.operations.back().get<account_create_operation>();\n         op.name = name;\n         op.active = authority(1, public_key_type(active.get_public_key()), 1);\n         op.owner = authority(1, public_key_type(owner.get_public_key()), 1);\n         signed_transaction trx;\n         trx.operations.push_back(op);\n         db.current_fee_schedule().set_fee( trx.operations.back() );\n         set_expiration( db, trx );\n         PUSH_TX( db, trx, ~0 );\n\n         return get_account(name);\n      };\n\n      auto update_asset_issuer = [&](const asset_object& current,\n                                     const account_object& new_issuer) {\n         asset_update_operation op;\n         op.issuer =  current.issuer;\n         op.asset_to_update = current.id;\n         op.new_options = current.options;\n         op.new_issuer = new_issuer.id;\n         signed_transaction tx;\n         tx.operations.push_back( op );\n         db.current_fee_schedule().set_fee( tx.operations.back() );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, ~0 );\n      };\n\n      // Lambda for updating the issuer on chain using a particular key\n      auto update_issuer = [&](const asset_id_type asset_id,\n                               const account_object& issuer,\n                               const account_object& new_issuer,\n                               const fc::ecc::private_key& key)\n      {\n         asset_update_issuer_operation op;\n         op.issuer = issuer.id;\n         op.new_issuer = new_issuer.id;\n         op.asset_to_update = asset_id;\n         signed_transaction tx;\n         tx.operations.push_back( op );\n         db.current_fee_schedule().set_fee( tx.operations.back() );\n         set_expiration( db, tx );\n         sign(tx, key);\n         PUSH_TX( db, tx, database::skip_transaction_dupe_check );\n      };\n\n      auto update_issuer_proposal = [&](const asset_id_type asset_id,\n                                        const account_object& issuer,\n                                        const account_object& new_issuer,\n                                        const fc::ecc::private_key& key)\n      {\n          asset_update_issuer_operation op;\n          op.issuer = issuer.id;\n          op.new_issuer = new_issuer.id;\n          op.asset_to_update = asset_id;\n\n          const auto& curfees = db.get_global_properties().parameters.get_current_fees();\n          const auto& proposal_create_fees = curfees.get<proposal_create_operation>();\n          proposal_create_operation prop;\n          prop.fee_paying_account = issuer.id;\n          prop.proposed_ops.emplace_back( op );\n          prop.expiration_time =  db.head_block_time() + fc::days(1);\n          prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n\n          signed_transaction tx;\n          tx.operations.push_back( prop );\n          db.current_fee_schedule().set_fee( tx.operations.back() );\n          set_expiration( db, tx );\n          sign( tx, key );\n          PUSH_TX( db, tx );\n\n      };\n\n      // Create alice account\n      fc::ecc::private_key alice_owner  = fc::ecc::private_key::regenerate(fc::digest(\"key1\"));\n      fc::ecc::private_key alice_active = fc::ecc::private_key::regenerate(fc::digest(\"key2\"));\n      fc::ecc::private_key bob_owner    = fc::ecc::private_key::regenerate(fc::digest(\"key3\"));\n      fc::ecc::private_key bob_active   = fc::ecc::private_key::regenerate(fc::digest(\"key4\"));\n\n      // Create accounts\n      const auto& alice = create_account_2_keys(\"alice\", alice_active, alice_owner);\n      const auto& bob = create_account_2_keys(\"bob\", bob_active, bob_owner);\n      const account_id_type alice_id = alice.get_id();\n      const account_id_type bob_id = bob.get_id();\n\n      // Create asset\n      const auto& test = create_user_issued_asset(\"UPDATEISSUER\", alice_id(db), 0);\n      const asset_id_type test_id = test.get_id();\n\n      // Fast Forward to Hardfork time\n      generate_blocks( HARDFORK_CORE_199_TIME );\n\n      update_issuer_proposal( test_id, alice_id(db), bob_id(db), alice_owner);\n\n      BOOST_TEST_MESSAGE( \"Can't change issuer if not my asset\" );\n      GRAPHENE_REQUIRE_THROW( update_issuer( test_id, bob_id(db), alice_id(db), bob_active ), fc::exception );\n      GRAPHENE_REQUIRE_THROW( update_issuer( test_id, bob_id(db), alice_id(db), bob_owner ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Can't change issuer with alice's active key\" );\n      GRAPHENE_REQUIRE_THROW( update_issuer( test_id, alice_id(db), bob_id(db), alice_active ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Old method with asset_update needs to fail\" );\n      GRAPHENE_REQUIRE_THROW( update_asset_issuer( test_id(db), bob_id(db)  ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Updating issuer to bob\" );\n      update_issuer( test_id, alice_id(db), bob_id(db), alice_owner );\n\n      BOOST_CHECK(test_id(db).issuer == bob_id);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( issue_uia )\n{ try {\n\n      INVOKE(create_uia);\n      INVOKE(create_account_test);\n\n      const asset_object& test_asset = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(UIA_TEST_SYMBOL);\n      const account_object& nathan_account = *db.get_index_type<account_index>().indices().get<by_name>().find(\"nathan\");\n\n      asset_issue_operation op;\n      op.issuer = test_asset.issuer;\n      op.asset_to_issue =  test_asset.amount(5000000);\n      op.issue_to_account = nathan_account.id;\n      trx.operations.push_back(op);\n\n      REQUIRE_THROW_WITH_VALUE(op, asset_to_issue, asset(200));\n      REQUIRE_THROW_WITH_VALUE(op, fee, asset(-1));\n      REQUIRE_THROW_WITH_VALUE(op, issue_to_account, account_id_type(999999999));\n\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      const asset_dynamic_data_object& test_dynamic_data = test_asset.dynamic_asset_data_id(db);\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, test_asset), 5000000);\n      BOOST_CHECK(test_dynamic_data.current_supply == 5000000);\n      BOOST_CHECK(test_dynamic_data.accumulated_fees == 0);\n      BOOST_CHECK(test_dynamic_data.fee_pool == 0);\n\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, test_asset), 10000000);\n      BOOST_CHECK(test_dynamic_data.current_supply == 10000000);\n      BOOST_CHECK(test_dynamic_data.accumulated_fees == 0);\n      BOOST_CHECK(test_dynamic_data.fee_pool == 0);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( transfer_uia )\n{ try {\n\n      INVOKE(issue_uia);\n\n      const asset_object& uia = *db.get_index_type<asset_index>().indices().get<by_symbol>().find(UIA_TEST_SYMBOL);\n      const account_object& nathan = *db.get_index_type<account_index>().indices().get<by_name>().find(\"nathan\");\n      const account_object& committee = account_id_type()(db);\n\n      BOOST_CHECK_EQUAL(get_balance(nathan, uia), 10000000);\n      transfer_operation top;\n      top.from = nathan.id;\n      top.to = committee.id;\n      top.amount = uia.amount(5000);\n      trx.operations.push_back(top);\n      BOOST_TEST_MESSAGE( \"Transfering 5000 TEST from nathan to committee\" );\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK_EQUAL(get_balance(nathan, uia), 10000000 - 5000);\n      BOOST_CHECK_EQUAL(get_balance(committee, uia), 5000);\n\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK_EQUAL(get_balance(nathan, uia), 10000000 - 10000);\n      BOOST_CHECK_EQUAL(get_balance(committee, uia), 10000);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new )\n{ try {\n\n   INVOKE( issue_uia );\n   const asset_object&   core_asset     = get_asset( UIA_TEST_SYMBOL );\n   const asset_object&   test_asset     = get_asset( GRAPHENE_SYMBOL );\n   const account_object& nathan_account = get_account( \"nathan\" );\n   const account_object& buyer_account  = create_account( \"buyer\" );\n   const account_object& seller_account = create_account( \"seller\" );\n\n   transfer( committee_account(db), buyer_account, test_asset.amount( 10000 ) );\n   transfer( nathan_account, seller_account, core_asset.amount(10000) );\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 10000 );\n\n   limit_order_id_type first_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(100) )->get_id();\n   limit_order_id_type second_id = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(200) )->get_id();\n   limit_order_id_type third_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(300) )->get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );\n\n   //print_market( \"\", \"\" );\n   auto unmatched = create_sell_order( seller_account, core_asset.amount(300), test_asset.amount(150) );\n   //print_market( \"\", \"\" );\n   BOOST_CHECK( !db.find( first_id ) );\n   BOOST_CHECK( !db.find( second_id ) );\n   BOOST_CHECK( db.find( third_id ) );\n   if( unmatched ) wdump((*unmatched));\n   BOOST_CHECK( !unmatched );\n\n   BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 200 );\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 297 );\n   BOOST_CHECK_EQUAL( core_asset.dynamic_asset_data_id(db).accumulated_fees.value , 3 );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_buy_exact_match_uia )\n{ try {\n\n   INVOKE( issue_uia );\n   const asset_object&   test_asset     = get_asset( UIA_TEST_SYMBOL );\n   const asset_object&   core_asset     = get_asset( GRAPHENE_SYMBOL );\n   const account_object& nathan_account = get_account( \"nathan\" );\n   const account_object& buyer_account  = create_account( \"buyer\" );\n   const account_object& seller_account = create_account( \"seller\" );\n\n   transfer( committee_account(db), seller_account, asset( 10000 ) );\n   transfer( nathan_account, buyer_account, test_asset.amount(10000) );\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 10000 );\n\n   limit_order_id_type first_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(100) )->get_id();\n   limit_order_id_type second_id = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(200) )->get_id();\n   limit_order_id_type third_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(300) )->get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );\n\n   //print_market( \"\", \"\" );\n   auto unmatched = create_sell_order( seller_account, core_asset.amount(100), test_asset.amount(100) );\n   //print_market( \"\", \"\" );\n   BOOST_CHECK( !db.find( first_id ) );\n   BOOST_CHECK( db.find( second_id ) );\n   BOOST_CHECK( db.find( third_id ) );\n   if( unmatched ) wdump((*unmatched));\n   BOOST_CHECK( !unmatched );\n\n   BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 99 );\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 100 );\n   BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 1 );\n\n} FC_LOG_AND_RETHROW() }\n\n\nBOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse )\n{ try {\n\n   INVOKE( issue_uia );\n   const asset_object&   test_asset     = get_asset( UIA_TEST_SYMBOL );\n   const asset_object&   core_asset     = get_asset( GRAPHENE_SYMBOL );\n   const account_object& nathan_account = get_account( \"nathan\" );\n   const account_object& buyer_account  = create_account( \"buyer\" );\n   const account_object& seller_account = create_account( \"seller\" );\n\n   transfer( committee_account(db), seller_account, asset( 10000 ) );\n   transfer( nathan_account, buyer_account, test_asset.amount(10000),test_asset.amount(0) );\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 10000 );\n\n   limit_order_id_type first_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(100) )->get_id();\n   limit_order_id_type second_id = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(200) )->get_id();\n   limit_order_id_type third_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(300) )->get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );\n\n   //print_market( \"\", \"\" );\n   auto unmatched = create_sell_order( seller_account, core_asset.amount(300), test_asset.amount(150) );\n   //print_market( \"\", \"\" );\n   BOOST_CHECK( !db.find( first_id ) );\n   BOOST_CHECK( !db.find( second_id ) );\n   BOOST_CHECK( db.find( third_id ) );\n   if( unmatched ) wdump((*unmatched));\n   BOOST_CHECK( !unmatched );\n\n   BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 198 );\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 300 );\n   BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 2 );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse_fract )\n{ try {\n\n   INVOKE( issue_uia );\n   const asset_object&   test_asset     = get_asset( UIA_TEST_SYMBOL );\n   const asset_object&   core_asset     = get_asset( GRAPHENE_SYMBOL );\n   const account_object& nathan_account = get_account( \"nathan\" );\n   const account_object& buyer_account  = create_account( \"buyer\" );\n   const account_object& seller_account = create_account( \"seller\" );\n\n   transfer( committee_account(db), seller_account, asset( 30 ) );\n   transfer( nathan_account, buyer_account, test_asset.amount(10000),test_asset.amount(0) );\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 10000 );\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 0 );\n   BOOST_CHECK_EQUAL( get_balance( seller_account, core_asset ), 30 );\n\n   limit_order_id_type first_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(10) )->get_id();\n   limit_order_id_type second_id = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(20) )->get_id();\n   limit_order_id_type third_id  = create_sell_order( buyer_account, test_asset.amount(100), core_asset.amount(30) )->get_id();\n\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, test_asset ), 9700 );\n\n   //print_market( \"\", \"\" );\n   auto unmatched = create_sell_order( seller_account, core_asset.amount(30), test_asset.amount(150) );\n   //print_market( \"\", \"\" );\n   BOOST_CHECK( !db.find( first_id ) );\n   BOOST_CHECK( !db.find( second_id ) );\n   BOOST_CHECK( db.find( third_id ) );\n   if( unmatched ) wdump((*unmatched));\n   BOOST_CHECK( !unmatched );\n\n   BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 198 );\n   BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 30 );\n   BOOST_CHECK_EQUAL( get_balance( seller_account, core_asset ), 0 );\n   BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 2 );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( uia_fees )\n{ try {\n\n      INVOKE( issue_uia );\n\n      enable_fees();\n\n      const asset_object& test_asset = get_asset(UIA_TEST_SYMBOL);\n      const asset_dynamic_data_object& asset_dynamic = test_asset.dynamic_asset_data_id(db);\n      const account_object& nathan_account = get_account(\"nathan\");\n      const account_object& committee_account = account_id_type()(db);\n      const share_type prec = asset::scaled_precision( asset_id_type()(db).precision );\n\n      fund_fee_pool(committee_account, test_asset, 1000*prec);\n      BOOST_CHECK(asset_dynamic.fee_pool == 1000*prec);\n\n      transfer_operation op;\n      op.fee = test_asset.amount(0);\n      op.from = nathan_account.id;\n      op.to   = committee_account.id;\n      op.amount = test_asset.amount(100);\n      op.fee = db.current_fee_schedule().calculate_fee( op, test_asset.options.core_exchange_rate );\n      BOOST_CHECK(op.fee.asset_id == test_asset.id);\n      asset old_balance = db.get_balance(nathan_account.get_id(), test_asset.get_id());\n      asset fee = op.fee;\n      BOOST_CHECK(fee.amount > 0);\n      asset core_fee = fee*test_asset.options.core_exchange_rate;\n      trx.operations.push_back(std::move(op));\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, test_asset),\n                        (old_balance - fee - test_asset.amount(100)).amount.value);\n      BOOST_CHECK_EQUAL(get_balance(committee_account, test_asset), 100);\n      BOOST_CHECK(asset_dynamic.accumulated_fees == fee.amount);\n      BOOST_CHECK(asset_dynamic.fee_pool == 1000*prec - core_fee.amount);\n\n      //Do it again, for good measure.\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, test_asset),\n                        (old_balance - fee - fee - test_asset.amount(200)).amount.value);\n      BOOST_CHECK_EQUAL(get_balance(committee_account, test_asset), 200);\n      BOOST_CHECK(asset_dynamic.accumulated_fees == fee.amount + fee.amount);\n      BOOST_CHECK(asset_dynamic.fee_pool == 1000*prec - core_fee.amount - core_fee.amount);\n\n      op = std::move(trx.operations.back().get<transfer_operation>());\n      trx.operations.clear();\n      op.amount = asset(20);\n\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 0);\n      transfer(committee_account, nathan_account, asset(20));\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 20);\n\n      trx.operations.emplace_back(std::move(op));\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(nathan_account, test_asset),\n                        (old_balance - fee - fee - fee - test_asset.amount(200)).amount.value);\n      BOOST_CHECK_EQUAL(get_balance(committee_account, test_asset), 200);\n      BOOST_CHECK(asset_dynamic.accumulated_fees == fee.amount.value * 3);\n      BOOST_CHECK(asset_dynamic.fee_pool == 1000*prec - core_fee.amount.value * 3);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( cancel_limit_order_test )\n{ try {\n\n   INVOKE( issue_uia );\n   const asset_object&   test_asset     = get_asset( UIA_TEST_SYMBOL );\n   const account_object& buyer_account  = create_account( \"buyer\" );\n\n   transfer( committee_account(db), buyer_account, asset( 10000 ) );\n\n   BOOST_CHECK_EQUAL( get_balance(buyer_account, asset_id_type()(db)), 10000 );\n   auto sell_order = create_sell_order( buyer_account, asset(1000), test_asset.amount(100+450*1) );\n   FC_ASSERT( sell_order );\n   auto refunded = cancel_limit_order( *sell_order );\n   BOOST_CHECK( refunded == asset(1000) );\n   BOOST_CHECK_EQUAL( get_balance(buyer_account, asset_id_type()(db)), 10000 );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( witness_feeds )\n{ try {\n\n      INVOKE( create_mia );\n      {\n         auto& current = get_asset( \"USDBIT\" );\n         asset_update_operation uop;\n         uop.issuer =  current.issuer;\n         uop.asset_to_update = current.id;\n         uop.new_options = current.options;\n         uop.new_issuer = account_id_type();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n         trx.clear();\n      }\n      generate_block();\n      const asset_object& bit_usd = get_asset(\"USDBIT\");\n      auto& global_props = db.get_global_properties();\n      vector<account_id_type> active_witnesses;\n      for( const witness_id_type& wit_id : global_props.active_witnesses )\n         active_witnesses.push_back( wit_id(db).witness_account );\n      BOOST_REQUIRE_EQUAL(active_witnesses.size(), INITIAL_WITNESS_COUNT);\n\n      asset_publish_feed_operation op;\n      op.publisher = active_witnesses[0];\n      op.asset_id = bit_usd.get_id();\n      op.feed.settlement_price = op.feed.core_exchange_rate = ~price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(30));\n      // Accept defaults for required collateral\n      trx.operations.emplace_back(op);\n      PUSH_TX( db, trx, ~0 );\n\n      const asset_bitasset_data_object& bitasset = bit_usd.bitasset_data(db);\n      BOOST_CHECK(bitasset.current_feed.settlement_price.to_real() == 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      op.publisher = active_witnesses[1];\n      op.feed.settlement_price = op.feed.core_exchange_rate = ~price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(25));\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      op.publisher = active_witnesses[2];\n      op.feed.settlement_price = op.feed.core_exchange_rate = ~price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(40));\n      // But this witness is an idiot.\n      op.feed.maintenance_collateral_ratio = 1001;\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n} FC_LOG_AND_RETHROW() }\n\n/**\n *  Create an order that cannot be filled immediately and have the\n *  transaction fail.\n */\nBOOST_AUTO_TEST_CASE( limit_order_fill_or_kill )\n{ try {\n\n   INVOKE(issue_uia);\n   const account_object& nathan = get_account(\"nathan\");\n   const asset_object& test = get_asset(UIA_TEST_SYMBOL);\n   const asset_object& core = asset_id_type()(db);\n\n   limit_order_create_operation op;\n   op.seller = nathan.id;\n   op.amount_to_sell = test.amount(500);\n   op.min_to_receive = core.amount(500);\n   op.fill_or_kill = true;\n\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n   op.fill_or_kill = false;\n   trx.operations.back() = op;\n   PUSH_TX( db, trx, ~0 );\n\n} FC_LOG_AND_RETHROW() }\n\n/// Shameless code coverage plugging. Otherwise, these calls never happen.\nBOOST_AUTO_TEST_CASE( fill_order )\n{ try {\n   fill_order_operation o;\n   GRAPHENE_CHECK_THROW(o.validate(), fc::exception);\n   //o.calculate_fee(db.current_fee_schedule());\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( witness_pay_test )\n{ try {\n\n   const share_type prec = asset::scaled_precision( asset_id_type()(db).precision );\n\n   // there is an immediate maintenance interval in the first block\n   //   which will initialize last_budget_time\n   generate_block();\n\n   // Make an account and upgrade it to prime, so that witnesses get some pay\n   create_account(\"nathan\", init_account_pub_key);\n   transfer(account_id_type()(db), get_account(\"nathan\"), asset(20000*prec));\n   transfer(account_id_type()(db), get_account(\"init3\"), asset(20*prec));\n   generate_block();\n\n   auto last_witness_vbo_balance = [&]() -> share_type\n   {\n      const witness_object& wit = db.fetch_block_by_number(db.head_block_num())->witness(db);\n      if( !wit.pay_vb.valid() )\n         return 0;\n      return (*wit.pay_vb)(db).balance.amount;\n   };\n\n   const auto block_interval = db.get_global_properties().parameters.block_interval;\n   const asset_object* core = &asset_id_type()(db);\n   const account_object* nathan = &get_account(\"nathan\");\n   enable_fees();\n   BOOST_CHECK_GT(db.current_fee_schedule().get<account_upgrade_operation>().membership_lifetime_fee, 0u);\n   // Based on the size of the reserve fund later in the test, the witness budget will be set to this value\n   const uint64_t ref_budget =\n      ((uint64_t( db.current_fee_schedule().get<account_upgrade_operation>().membership_lifetime_fee )\n         * GRAPHENE_CORE_ASSET_CYCLE_RATE * 30\n         * block_interval\n       ) + ((uint64_t(1) << GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS)-1)\n      ) >> GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS\n      ;\n   // change this if ref_budget changes\n   BOOST_CHECK_EQUAL( ref_budget, 594u );\n   const uint64_t witness_ppb = ref_budget * 10 / 23 + 1;\n   // change this if ref_budget changes\n   BOOST_CHECK_EQUAL( witness_ppb, 259u );\n   // following two inequalities need to hold for maximal code coverage\n   BOOST_CHECK_LT( witness_ppb * 2, ref_budget );\n   BOOST_CHECK_GT( witness_ppb * 3, ref_budget );\n\n   db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )\n   {\n      _gpo.parameters.witness_pay_per_block = witness_ppb;\n   } );\n\n   BOOST_CHECK_EQUAL(core->dynamic_asset_data_id(db).accumulated_fees.value, 0);\n   BOOST_TEST_MESSAGE( \"Upgrading account\" );\n   account_upgrade_operation uop;\n   uop.account_to_upgrade = nathan->get_id();\n   uop.upgrade_to_lifetime_member = true;\n   set_expiration( db, trx );\n   trx.operations.push_back(uop);\n   for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);\n   trx.validate();\n   sign( trx, init_account_priv_key );\n   PUSH_TX( db, trx );\n   auto pay_fee_time = db.head_block_time().sec_since_epoch();\n   trx.clear();\n   BOOST_CHECK( get_balance(*nathan, *core) == 20000*prec - account_upgrade_operation::fee_params_t().membership_lifetime_fee );;\n\n   generate_block();\n   nathan = &get_account(\"nathan\");\n   core = &asset_id_type()(db);\n   BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, 0 );\n\n   auto schedule_maint = [&]()\n   {\n      // now we do maintenance\n      db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo )\n      {\n         _dpo.next_maintenance_time = db.head_block_time() + 1;\n      } );\n   };\n   BOOST_TEST_MESSAGE( \"Generating some blocks\" );\n\n   // generate some blocks\n   while( db.head_block_time().sec_since_epoch() - pay_fee_time < 24 * block_interval )\n   {\n      generate_block();\n      BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, 0 );\n   }\n   BOOST_CHECK_EQUAL( db.head_block_time().sec_since_epoch() - pay_fee_time, 24u * block_interval );\n\n   schedule_maint();\n   // The 80% lifetime referral fee went to the committee account, which burned it. Check that it's here.\n   BOOST_CHECK( core->reserved(db).value == 8000*prec );\n   generate_block();\n   BOOST_CHECK_EQUAL( core->reserved(db).value, 999999406 );\n   BOOST_CHECK_EQUAL( db.get_dynamic_global_properties().witness_budget.value, (int64_t)ref_budget );\n   // first witness paid from old budget (so no pay)\n   BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, 0 );\n   // second witness finally gets paid!\n   generate_block();\n   BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, (int64_t)witness_ppb );\n   BOOST_CHECK_EQUAL( db.get_dynamic_global_properties().witness_budget.value, (int64_t)(ref_budget - witness_ppb) );\n\n   generate_block();\n   BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, (int64_t)witness_ppb );\n   BOOST_CHECK_EQUAL( db.get_dynamic_global_properties().witness_budget.value, (int64_t)(ref_budget - 2 * witness_ppb) );\n\n   generate_block();\n   BOOST_CHECK_LT( last_witness_vbo_balance().value, (int64_t)witness_ppb );\n   BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, (int64_t)(ref_budget - 2 * witness_ppb) );\n   BOOST_CHECK_EQUAL( db.get_dynamic_global_properties().witness_budget.value, 0 );\n\n   generate_block();\n   BOOST_CHECK_EQUAL( last_witness_vbo_balance().value, 0 );\n   BOOST_CHECK_EQUAL( db.get_dynamic_global_properties().witness_budget.value, 0 );\n   BOOST_CHECK_EQUAL(core->reserved(db).value, 999999406 );\n\n} FC_LOG_AND_RETHROW() }\n\n/**\n *  Reserve asset test should make sure that all assets except bitassets\n *  can be burned, and all supplies add up.\n */\nBOOST_AUTO_TEST_CASE( reserve_asset_test )\n{ try {\n\n      ACTORS((alice)(bob)(sam)(judge));\n      const auto& basset = create_bitasset(\"USDBIT\", judge_id);\n      const auto& uasset = create_user_issued_asset(UIA_TEST_SYMBOL);\n      const auto& passet = create_prediction_market(\"PMARK\", judge_id);\n      const auto& casset = asset_id_type()(db);\n\n      auto reserve_asset = [&]( account_id_type payer, asset amount_to_reserve )\n      {\n         asset_reserve_operation op;\n         op.payer = payer;\n         op.amount_to_reserve = amount_to_reserve;\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      } ;\n\n      auto _issue_uia = [&]( const account_object& recipient, asset amount )\n      {\n         asset_issue_operation op;\n         op.issuer = amount.asset_id(db).issuer;\n         op.asset_to_issue = amount;\n         op.issue_to_account = recipient.id;\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      } ;\n\n      int64_t init_balance = 10000;\n      int64_t reserve_amount = 3000;\n      share_type initial_reserve;\n\n      BOOST_TEST_MESSAGE( \"Test reserve operation on core asset\" );\n      transfer( committee_account, alice_id, casset.amount( init_balance ) );\n\n      initial_reserve = casset.reserved( db );\n      reserve_asset( alice_id, casset.amount( reserve_amount  ) );\n      BOOST_CHECK_EQUAL( get_balance( alice, casset ), init_balance - reserve_amount );\n      BOOST_CHECK_EQUAL( (casset.reserved( db ) - initial_reserve).value, reserve_amount );\n      verify_asset_supplies(db);\n\n      BOOST_TEST_MESSAGE( \"Test reserve operation on market issued asset\" );\n      transfer( committee_account, alice_id, casset.amount( init_balance*100 ) );\n      update_feed_producers( basset, {sam.get_id()} );\n      price_feed current_feed;\n      current_feed.settlement_price = basset.amount( 2 ) / casset.amount(100);\n      current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n      publish_feed( basset, sam, current_feed );\n      borrow( alice_id, basset.amount( init_balance ), casset.amount( 100*init_balance ) );\n      BOOST_CHECK_EQUAL( get_balance( alice, basset ), init_balance );\n\n      GRAPHENE_REQUIRE_THROW( reserve_asset( alice_id, basset.amount( reserve_amount ) ), asset_reserve_invalid_on_mia );\n\n      BOOST_TEST_MESSAGE( \"Test reserve operation on prediction market asset\" );\n      transfer( committee_account, alice_id, casset.amount( init_balance ) );\n      borrow( alice_id, passet.amount( init_balance ), casset.amount( init_balance ) );\n      GRAPHENE_REQUIRE_THROW( reserve_asset( alice_id, passet.amount( reserve_amount ) ), asset_reserve_invalid_on_mia );\n\n      BOOST_TEST_MESSAGE( \"Test reserve operation on user issued asset\" );\n      _issue_uia( alice, uasset.amount( init_balance ) );\n      BOOST_CHECK_EQUAL( get_balance( alice, uasset ), init_balance );\n      verify_asset_supplies(db);\n\n      BOOST_TEST_MESSAGE( \"Reserving asset\" );\n      initial_reserve = uasset.reserved( db );\n      reserve_asset( alice_id, uasset.amount( reserve_amount  ) );\n      BOOST_CHECK_EQUAL( get_balance( alice, uasset ), init_balance - reserve_amount );\n      BOOST_CHECK_EQUAL( (uasset.reserved( db ) - initial_reserve).value, reserve_amount );\n      verify_asset_supplies(db);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( call_order_update_evaluator_test )\n{ try {\n\n      ACTORS( (alice) (bob) );\n      transfer(committee_account, alice_id, asset(10000000 * GRAPHENE_BLOCKCHAIN_PRECISION));\n\n      const auto& core   = asset_id_type()(db);\n\n      // attempt to increase current supply beyond max_supply\n      const auto& bitjmj = create_bitasset( \"JMJBIT\", alice_id, 100, charge_market_fee, 2U,\n            asset_id_type{}, GRAPHENE_MAX_SHARE_SUPPLY / 2 );\n      auto bitjmj_id = bitjmj.get_id();\n      share_type original_max_supply = bitjmj.options.max_supply;\n\n      {\n         BOOST_TEST_MESSAGE( \"Setting price feed to $100000 / 1\" );\n         update_feed_producers( bitjmj, {alice_id} );\n         price_feed current_feed;\n         current_feed.settlement_price = bitjmj.amount( 100000 ) / core.amount(1);\n         publish_feed( bitjmj, alice, current_feed );\n      }\n\n      {\n         BOOST_TEST_MESSAGE( \"Attempting a call_order_update that exceeds max_supply\" );\n         call_order_update_operation op;\n         op.funding_account = alice_id;\n         op.delta_collateral = asset( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         op.delta_debt = asset( bitjmj.options.max_supply + 1, bitjmj.get_id() );\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n         generate_block();\n      }\n\n      // advance past hardfork\n      generate_blocks( HARDFORK_CORE_1465_TIME );\n      set_expiration( db, trx );\n\n      // bitjmj should have its problem corrected\n      auto newbitjmj = bitjmj_id(db);\n      BOOST_REQUIRE_GT(newbitjmj.options.max_supply.value, original_max_supply.value);\n\n      // now try with an asset after the hardfork\n      const auto& bitusd = create_bitasset( \"USDBIT\", alice_id, 100, charge_market_fee, 2U,\n            asset_id_type{}, GRAPHENE_MAX_SHARE_SUPPLY / 2 );\n\n      {\n         BOOST_TEST_MESSAGE( \"Setting price feed to $100000 / 1\" );\n         update_feed_producers( bitusd, {alice_id} );\n         price_feed current_feed;\n         current_feed.settlement_price = bitusd.amount( 100000 ) / core.amount(1);\n         publish_feed( bitusd, alice_id(db), current_feed );\n      }\n\n      {\n         BOOST_TEST_MESSAGE( \"Attempting a call_order_update that exceeds max_supply\" );\n         call_order_update_operation op;\n         op.funding_account = alice_id;\n         op.delta_collateral = asset( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         op.delta_debt = asset( bitusd.options.max_supply + 1, bitusd.get_id() );\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         GRAPHENE_REQUIRE_THROW(PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ), fc::exception );\n      }\n\n      {\n         BOOST_TEST_MESSAGE( \"Creating 2 bitusd and transferring to bob (increases current supply)\" );\n         call_order_update_operation op;\n         op.funding_account = alice_id;\n         op.delta_collateral = asset( 100 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         op.delta_debt = asset( 2, bitusd.get_id() );\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n         transfer( alice_id(db), bob_id(db), asset( 2, bitusd.get_id() ) );\n      }\n\n      {\n         BOOST_TEST_MESSAGE( \"Again attempting a call_order_update_operation that is max_supply - 1 (should throw)\" );\n         call_order_update_operation op;\n         op.funding_account = alice_id;\n         op.delta_collateral = asset( 100000 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         op.delta_debt = asset( bitusd.options.max_supply - 1, bitusd.get_id() );\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         GRAPHENE_REQUIRE_THROW(PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ), fc::exception);\n      }\n\n      {\n         BOOST_TEST_MESSAGE( \"Again attempting a call_order_update_operation that equals max_supply (should work)\" );\n         call_order_update_operation op;\n         op.funding_account = alice_id;\n         op.delta_collateral = asset( 100000 * GRAPHENE_BLOCKCHAIN_PRECISION );\n         op.delta_debt = asset( bitusd.options.max_supply - 2, bitusd.get_id() );\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      }\n\n} FC_LOG_AND_RETHROW() }\n\n/**\n * This test demonstrates how using the call_order_update_operation to\n * trigger a margin call is legal if there is a matching order.\n */\nBOOST_AUTO_TEST_CASE( cover_with_collateral_test )\n{ try {\n\n      ACTORS((alice)(bob)(sam));\n      const auto& bitusd = create_bitasset(\"USDBIT\", sam_id);\n      const auto& core   = asset_id_type()(db);\n\n      BOOST_TEST_MESSAGE( \"Setting price feed to $0.02 / 100\" );\n      transfer(committee_account, alice_id, asset(10000000));\n      update_feed_producers( bitusd, {sam.get_id()} );\n\n      price_feed current_feed;\n      current_feed.settlement_price = bitusd.amount( 2 ) / core.amount(100);\n      publish_feed( bitusd, sam, current_feed );\n\n      BOOST_REQUIRE( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price );\n\n      BOOST_TEST_MESSAGE( \"Alice borrows some BitUSD at 2x collateral and gives it to Bob\" );\n      const call_order_object* call_order = borrow( alice, bitusd.amount(100), asset(10000) );\n      BOOST_REQUIRE( call_order != nullptr );\n\n      // wdump( (*call_order) );\n\n      transfer( alice_id, bob_id, bitusd.amount(100) );\n\n      auto update_call_order = [&]( account_id_type acct, asset delta_collateral, asset delta_debt )\n      {\n         call_order_update_operation op;\n         op.funding_account = acct;\n         op.delta_collateral = delta_collateral;\n         op.delta_debt = delta_debt;\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      } ;\n\n      // margin call requirement:  1.75x\n      BOOST_TEST_MESSAGE( \"Alice decreases her collateral to maint level plus one satoshi\" );\n      asset delta_collateral = asset(int64_t( current_feed.maintenance_collateral_ratio ) * 5000 / GRAPHENE_COLLATERAL_RATIO_DENOM - 10000 + 1 );\n      update_call_order( alice_id, delta_collateral, bitusd.amount(0) );\n      // wdump( (*call_order) );\n\n      BOOST_TEST_MESSAGE( \"Alice cannot decrease her collateral by one satoshi, there is no buyer\" );\n      GRAPHENE_REQUIRE_THROW( update_call_order( alice_id, asset(-1), bitusd.amount(0) ), call_order_update_unfilled_margin_call );\n      // wdump( (*call_order) );\n\n      BOOST_TEST_MESSAGE( \"Bob offers to sell most of the BitUSD at the feed\" );\n      const limit_order_object* order = create_sell_order( bob_id, bitusd.amount(99), asset(4950) );\n      BOOST_REQUIRE( order != nullptr );\n      limit_order_id_type order1_id = order->get_id();\n      BOOST_CHECK_EQUAL( order->for_sale.value, 99 );\n      // wdump( (*call_order) );\n\n      BOOST_TEST_MESSAGE( \"Alice still cannot decrease her collateral to maint level\" );\n      GRAPHENE_REQUIRE_THROW( update_call_order( alice_id, asset(-1), bitusd.amount(0) ), call_order_update_unfilled_margin_call );\n      // wdump( (*call_order) );\n\n      BOOST_TEST_MESSAGE( \"Bob offers to sell the last of his BitUSD in another order\" );\n      order = create_sell_order( bob_id, bitusd.amount(1), asset(50) );\n      BOOST_REQUIRE( order != nullptr );\n      limit_order_id_type order2_id = order->get_id();\n      BOOST_CHECK_EQUAL( order->for_sale.value, 1 );\n      // wdump( (*call_order) );\n\n      BOOST_TEST_MESSAGE( \"Alice decreases her collateral to maint level and Bob's orders fill\" );\n      update_call_order( alice_id, asset(-1), bitusd.amount(0) );\n\n      BOOST_CHECK( db.find( order1_id ) == nullptr );\n      BOOST_CHECK( db.find( order2_id ) == nullptr );\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( vesting_balance_create_test )\n{ try {\n   INVOKE( create_uia );\n\n   const asset_object& core = asset_id_type()(db);\n   const asset_object& test_asset = get_asset(UIA_TEST_SYMBOL);\n\n   vesting_balance_create_operation op;\n   op.fee = core.amount( 0 );\n   op.creator = account_id_type();\n   op.owner = account_id_type();\n   op.amount = test_asset.amount( 100 );\n   //op.vesting_seconds = 60*60*24;\n   op.policy = cdd_vesting_policy_initializer{ 60*60*24 };\n\n   // Fee must be non-negative\n   REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(1) );\n   REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(0) );\n   REQUIRE_OP_VALIDATION_FAILURE( op, fee, core.amount(-1) );\n\n   // Amount must be positive\n   REQUIRE_OP_VALIDATION_SUCCESS( op, amount, core.amount(1) );\n   REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(0) );\n   REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(-1) );\n\n   // Setup world state we will need to test actual evaluation\n   const account_object& alice_account = create_account(\"alice\");\n   const account_object& bob_account = create_account(\"bob\");\n\n   transfer(committee_account(db), alice_account, core.amount(100000));\n\n   op.creator = alice_account.get_id();\n   op.owner = alice_account.get_id();\n\n   account_id_type nobody = account_id_type(1234);\n\n   trx.operations.push_back(op);\n   // Invalid account_id's\n   REQUIRE_THROW_WITH_VALUE( op, creator, nobody );\n   REQUIRE_THROW_WITH_VALUE( op,   owner, nobody );\n\n   // Insufficient funds\n   REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(999999999) );\n   // Alice can fund a bond to herself or to Bob\n   op.amount = core.amount( 1000 );\n   REQUIRE_OP_EVALUATION_SUCCESS( op, owner, alice_account.get_id() );\n   REQUIRE_OP_EVALUATION_SUCCESS( op, owner,   bob_account.get_id() );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( vesting_balance_create_asset_auth_test )\n{ try {\n   INVOKE( create_uia );\n\n   generate_block();\n\n   ACTORS( (alice)(bob)(cindy) );\n\n   const asset_object& test_asset = get_asset(UIA_TEST_SYMBOL);\n\n   issue_uia( alice, test_asset.amount( 10000 ) );\n   issue_uia( bob, test_asset.amount( 10000 ) );\n\n   // Success when no whitelist configured\n   vesting_balance_create_operation op;\n   op.creator = alice_id;\n   op.owner = alice_id;\n   op.amount = test_asset.amount( 100 );\n   op.policy = cdd_vesting_policy_initializer{ 60*60*24 };\n\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   PUSH_TX( db, trx, ~0 );\n\n   vesting_balance_create_operation op2 = op;\n   op2.owner = bob_id;\n   trx.operations.clear();\n   trx.operations.push_back(op2);\n   PUSH_TX( db, trx, ~0 );\n\n   vesting_balance_create_operation op3 = op;\n   op3.creator = bob_id;\n   trx.operations.clear();\n   trx.operations.push_back(op3);\n   PUSH_TX( db, trx, ~0 );\n\n   vesting_balance_create_operation op4 = op;\n   op4.creator = bob_id;\n   op4.owner = bob_id;\n   trx.operations.clear();\n   trx.operations.push_back(op4);\n   PUSH_TX( db, trx, ~0 );\n\n   generate_block();\n\n   // Make a whitelist\n   {\n      BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n      asset_update_operation uop;\n      uop.issuer = test_asset.issuer;\n      uop.asset_to_update = test_asset.id;\n      uop.new_options = test_asset.options;\n\n      // Enable whitelisting\n      uop.new_options.flags = white_list | charge_market_fee;\n      trx.operations.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX( db, trx, ~0 );\n\n      // The whitelist is managed by bob\n      uop.new_options.whitelist_authorities.insert(bob_id);\n      trx.operations.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX( db, trx, ~0 );\n\n      // Upgrade bob so that he can manage the whitelist\n      upgrade_to_lifetime_member( bob_id );\n\n      // Add bob to the whitelist, but do not add alice\n      account_whitelist_operation wop;\n      wop.authorizing_account = bob_id;\n      wop.account_to_list = bob_id;\n      wop.new_listing = account_whitelist_operation::white_listed;\n      trx.operations.clear();\n      trx.operations.push_back(wop);\n      PUSH_TX( db, trx, ~0 );\n   }\n\n   generate_block();\n\n   // Reproduces bitshares-core issue #972: the whitelist is ignored\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   trx.operations.push_back(op2);\n   trx.operations.push_back(op3);\n   trx.operations.push_back(op4);\n   PUSH_TX( db, trx, ~0 );\n\n   // Apply core-973 hardfork\n   generate_blocks( HARDFORK_CORE_973_TIME );\n   set_expiration( db, trx );\n\n   // Now asset authorization is in effect, Alice is unable to create vesting balances for herself\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   // Alice can not create vesting balances for Bob\n   trx.operations.clear();\n   trx.operations.push_back(op2);\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   // Bob can not create vesting balances for Alice\n   trx.operations.clear();\n   trx.operations.push_back(op3);\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   // Bob can still create vesting balances for himself\n   trx.operations.clear();\n   trx.operations.push_back(op4);\n   PUSH_TX( db, trx, ~0 );\n\n   {\n      // Add Alice to the whitelist\n      account_whitelist_operation wop;\n      wop.authorizing_account = bob_id;\n      wop.account_to_list = alice_id;\n      wop.new_listing = account_whitelist_operation::white_listed;\n      trx.operations.clear();\n      trx.operations.push_back(wop);\n      PUSH_TX( db, trx, ~0 );\n   }\n\n   // Success again\n   trx.operations.clear();\n   trx.operations.push_back(op);\n   trx.operations.push_back(op2);\n   trx.operations.push_back(op3);\n   trx.operations.push_back(op4);\n   PUSH_TX( db, trx, ~0 );\n\n   // And Alice still can not create vesting balances for Cindy\n   vesting_balance_create_operation op5 = op;\n   op5.owner = cindy_id;\n   trx.operations.clear();\n   trx.operations.push_back(op5);\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test )\n{ try {\n   INVOKE( create_uia );\n   // required for head block time\n   generate_block();\n\n   const asset_object& core = asset_id_type()(db);\n   const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL );\n\n   vesting_balance_withdraw_operation op;\n   op.fee = core.amount( 0 );\n   op.vesting_balance = vesting_balance_id_type();\n   op.owner = account_id_type();\n   op.amount = test_asset.amount( 100 );\n\n   // Fee must be non-negative\n   REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(  1 )  );\n   REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(  0 )  );\n   REQUIRE_OP_VALIDATION_FAILURE( op, fee, core.amount( -1 ) );\n\n   // Amount must be positive\n   REQUIRE_OP_VALIDATION_SUCCESS( op, amount, core.amount(  1 ) );\n   REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount(  0 ) );\n   REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount( -1 ) );\n\n   // Setup world state we will need to test actual evaluation\n   const account_object& alice_account = create_account( \"alice\" );\n   const account_object& bob_account = create_account( \"bob\" );\n\n   transfer( committee_account(db), alice_account, core.amount( 1000000 ) );\n\n   auto spin_vbo_clock = [&]( const vesting_balance_object& vbo, uint32_t dt_secs )\n   {\n      // HACK:  This just modifies the DB creation record to be further\n      //    in the past\n      db.modify( vbo, [&]( vesting_balance_object& _vbo )\n      {\n         _vbo.policy.get<cdd_vesting_policy>().coin_seconds_earned_last_update -= dt_secs;\n      } );\n   };\n\n   auto create_vbo = [&](\n      account_id_type creator, account_id_type owner,\n      asset amount, uint32_t vesting_seconds, uint32_t elapsed_seconds\n      ) -> const vesting_balance_object&\n   {\n      transaction tx;\n\n      vesting_balance_create_operation create_op;\n      create_op.fee = core.amount( 0 );\n      create_op.creator = creator;\n      create_op.owner = owner;\n      create_op.amount = amount;\n      create_op.policy = cdd_vesting_policy_initializer(vesting_seconds);\n      tx.operations.push_back( create_op );\n      set_expiration( db, tx );\n\n      processed_transaction ptx = PUSH_TX( db,  tx, ~0  );\n      const vesting_balance_object& vbo = vesting_balance_id_type(\n         ptx.operation_results[0].get<object_id_type>())(db);\n\n      if( elapsed_seconds > 0 )\n         spin_vbo_clock( vbo, elapsed_seconds );\n      return vbo;\n   };\n\n   auto top_up = [&]()\n   {\n      trx.clear();\n      transfer( committee_account(db),\n         alice_account,\n         core.amount( 1000000 - db.get_balance( alice_account, core ).amount )\n         );\n      FC_ASSERT( db.get_balance( alice_account, core ).amount == 1000000 );\n      trx.clear();\n      trx.operations.push_back( op );\n   };\n\n   trx.clear();\n   trx.operations.push_back( op );\n\n   {\n      // Try withdrawing a single satoshi\n      const vesting_balance_object& vbo = create_vbo(\n         alice_account.get_id(), alice_account.get_id(), core.amount( 10000 ), 1000, 0);\n\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990000 );\n\n      op.vesting_balance = vbo.id;\n      op.owner = alice_account.id;\n\n      REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(1) );\n\n      // spin the clock and make sure we can withdraw 1/1000 in 1 second\n      spin_vbo_clock( vbo, 1 );\n      // Alice shouldn't be able to withdraw 11, it's too much\n      REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(11) );\n      op.amount = core.amount( 1 );\n      // Bob shouldn't be able to withdraw anything\n      REQUIRE_THROW_WITH_VALUE( op, owner, bob_account.id );\n      // Shouldn't be able to get out different asset than was put in\n      REQUIRE_THROW_WITH_VALUE( op, amount, test_asset.amount(1) );\n      // Withdraw the max, we are OK...\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(10) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990010 );\n      top_up();\n   }\n\n   // Make sure we can withdraw the correct amount after 999 seconds\n   {\n      const vesting_balance_object& vbo = create_vbo(\n         alice_account.get_id(), alice_account.get_id(), core.amount( 10000 ), 1000, 999);\n\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990000 );\n\n      op.vesting_balance = vbo.id;\n      op.owner = alice_account.id;\n      // Withdraw one satoshi too much, no dice\n      REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(9991) );\n      // Withdraw just the right amount, success!\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(9990) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  999990 );\n      top_up();\n   }\n\n   // Make sure we can withdraw the whole thing after 1000 seconds\n   {\n      const vesting_balance_object& vbo = create_vbo(\n         alice_account.get_id(), alice_account.get_id(), core.amount( 10000 ), 1000, 1000);\n\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990000 );\n\n      op.vesting_balance = vbo.id;\n      op.owner = alice_account.id;\n      // Withdraw one satoshi too much, no dice\n      REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(10001) );\n      // Withdraw just the right amount, success!\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(10000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount == 1000000 );\n   }\n\n   // Make sure that we can't withdraw a single extra satoshi no matter how old it is\n   {\n      const vesting_balance_object& vbo = create_vbo(\n         alice_account.get_id(), alice_account.get_id(), core.amount( 10000 ), 1000, 123456);\n\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990000 );\n\n      op.vesting_balance = vbo.id;\n      op.owner = alice_account.id;\n      // Withdraw one satoshi too much, no dice\n      REQUIRE_THROW_WITH_VALUE( op, amount, core.amount(10001) );\n      // Withdraw just the right amount, success!\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(10000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount == 1000000 );\n   }\n\n   // Try withdrawing in three max installments:\n   //   5000 after  500      seconds\n   //   2000 after  400 more seconds\n   //   3000 after 1000 more seconds\n   {\n      const vesting_balance_object& vbo = create_vbo(\n         alice_account.get_id(), alice_account.get_id(), core.amount( 10000 ), 1000, 0);\n\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990000 );\n\n      op.vesting_balance = vbo.id;\n      op.owner = alice_account.id;\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(   1) );\n      spin_vbo_clock( vbo, 499 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(5000) );\n      spin_vbo_clock( vbo,   1 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(5001) );\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(5000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  995000 );\n\n      spin_vbo_clock( vbo, 399 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(2000) );\n      spin_vbo_clock( vbo,   1 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(2001) );\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(2000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  997000 );\n\n      spin_vbo_clock( vbo, 999 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(3000) );\n      spin_vbo_clock( vbo, 1   );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(3001) );\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(3000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount == 1000000 );\n   }\n\n   //\n   // Increase by 10,000 csd / sec initially.\n   // After 500 seconds, we have 5,000,000 csd.\n   // Withdraw 2,000, we are now at 8,000 csd / sec.\n   // At 8,000 csd / sec, it will take us 625 seconds to mature.\n   //\n   {\n      const vesting_balance_object& vbo = create_vbo(\n         alice_account.get_id(), alice_account.get_id(), core.amount( 10000 ), 1000, 0);\n\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  990000 );\n\n      op.vesting_balance = vbo.id;\n      op.owner = alice_account.id;\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(   1) );\n      spin_vbo_clock( vbo, 500 );\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(2000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount ==  992000 );\n\n      spin_vbo_clock( vbo, 624 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(8000) );\n      spin_vbo_clock( vbo,   1 );\n      REQUIRE_THROW_WITH_VALUE     ( op, amount, core.amount(8001) );\n      REQUIRE_OP_EVALUATION_SUCCESS( op, amount, core.amount(8000) );\n      FC_ASSERT( db.get_balance( alice_account,       core ).amount == 1000000 );\n   }\n   // TODO:  Test with non-core asset and Bob account\n} FC_LOG_AND_RETHROW() }\n\n// TODO:  Write linear VBO tests\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/operation_tests2.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/chain/balance_object.hpp>\n#include <graphene/chain/budget_record_object.hpp>\n#include <graphene/chain/committee_member_object.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/withdraw_permission_object.hpp>\n#include <graphene/chain/witness_object.hpp>\n#include <graphene/chain/worker_object.hpp>\n\n#include <graphene/witness/witness.hpp>\n\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture )\n\n/***\n * A descriptor of a particular withdrawal period\n */\nstruct withdrawal_period_descriptor {\n   withdrawal_period_descriptor(const time_point_sec start, const time_point_sec end, const asset available, const asset claimed)\n      : period_start_time(start), period_end_time(end), available_this_period(available), claimed_this_period(claimed) {}\n\n   // Start of period\n   time_point_sec period_start_time;\n\n   // End of period\n   time_point_sec period_end_time;\n\n   // Quantify how much is still available to be withdrawn during this period\n   asset available_this_period;\n\n   // Quantify how much has already been claimed during this period\n   asset claimed_this_period;\n\n   string const to_string() const {\n       string asset_id = fc::to_string(available_this_period.asset_id.space_id)\n                         + \".\" + fc::to_string(available_this_period.asset_id.type_id)\n                         + \".\" + fc::to_string(available_this_period.asset_id.instance.value);\n       string text = fc::to_string(available_this_period.amount.value)\n                     + \" \" + asset_id\n                     + \" is available from \" + period_start_time.to_iso_string()\n                     + \" to \" + period_end_time.to_iso_string();\n       return text;\n   }\n};\n\n\n/***\n * Get a description of the current withdrawal period\n * @param current_time   Current time\n * @return A description of the current period\n */\nwithdrawal_period_descriptor current_period(const withdraw_permission_object& permit, fc::time_point_sec current_time) {\n   // @todo [6] Is there a potential race condition where a call to available_this_period might become out of sync with this function's later use of period start time?\n   asset available = permit.available_this_period(current_time);\n   asset claimed = asset(permit.withdrawal_limit.amount - available.amount, permit.withdrawal_limit.asset_id);\n   auto periods = (current_time - permit.period_start_time).to_seconds() / permit.withdrawal_period_sec;\n   time_point_sec current_period_start = permit.period_start_time + (periods * permit.withdrawal_period_sec);\n   time_point_sec current_period_end = current_period_start + permit.withdrawal_period_sec;\n   withdrawal_period_descriptor descriptor = withdrawal_period_descriptor(current_period_start, current_period_end, available, claimed);\n\n   return descriptor;\n}\n\n/**\n * This auxiliary test is used for two purposes:\n * (a) it checks the creation of withdrawal claims,\n * (b) it is used as a precursor for tests that evaluate withdrawal claims.\n *\n * NOTE: This test verifies proper withdrawal claim behavior.\n */\nBOOST_AUTO_TEST_CASE( withdraw_permission_create )\n{ try {\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   auto dan_private_key = generate_private_key(\"dan\");\n   account_id_type nathan_id = create_account(\"nathan\", nathan_private_key.get_public_key()).get_id();\n   account_id_type dan_id = create_account(\"dan\", dan_private_key.get_public_key()).get_id();\n\n   transfer(account_id_type(), nathan_id, asset(1000));\n   generate_block();\n   set_expiration( db, trx );\n\n   {\n      withdraw_permission_create_operation op;\n      op.authorized_account = dan_id;\n      op.withdraw_from_account = nathan_id;\n      op.withdrawal_limit = asset(5);\n      op.withdrawal_period_sec = fc::hours(1).to_seconds();\n      op.periods_until_expiration = 5;\n      op.period_start_time = db.head_block_time() + db.get_global_properties().parameters.block_interval*5; // 5 blocks after fork time\n      trx.operations.push_back(op);\n      REQUIRE_OP_VALIDATION_FAILURE(op, withdrawal_limit, asset());\n      REQUIRE_OP_VALIDATION_FAILURE(op, periods_until_expiration, 0);\n      REQUIRE_OP_VALIDATION_FAILURE(op, withdraw_from_account, dan_id);\n      REQUIRE_OP_VALIDATION_FAILURE(op, withdrawal_period_sec, 0);\n      REQUIRE_THROW_WITH_VALUE(op, withdrawal_limit, asset(10, asset_id_type(10)));\n      REQUIRE_THROW_WITH_VALUE(op, authorized_account, account_id_type(1000));\n      REQUIRE_THROW_WITH_VALUE(op, period_start_time, fc::time_point_sec(10000));\n      REQUIRE_THROW_WITH_VALUE(op, withdrawal_period_sec, 1);\n      trx.operations.back() = op;\n   }\n   sign( trx, nathan_private_key );\n   PUSH_TX( db, trx );\n   trx.clear();\n} FC_LOG_AND_RETHROW() }\n\n/**\n * Test the claims of withdrawals both before and during\n * authorized withdrawal periods.\n * NOTE: The simulated elapse of blockchain time through the use of\n * generate_blocks() must be carefully used in order to simulate\n * this test.\n * NOTE: This test verifies proper withdrawal claim behavior.\n */\nBOOST_AUTO_TEST_CASE( withdraw_permission_test )\n{ try {\n   INVOKE(withdraw_permission_create);\n\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   auto dan_private_key = generate_private_key(\"dan\");\n   account_id_type nathan_id = get_account(\"nathan\").get_id();\n   account_id_type dan_id = get_account(\"dan\").get_id();\n   withdraw_permission_id_type permit;\n   set_expiration( db, trx );\n\n   fc::time_point_sec first_start_time;\n   {\n      const withdraw_permission_object& permit_object = permit(db);\n      BOOST_CHECK(permit_object.authorized_account == dan_id);\n      BOOST_CHECK(permit_object.withdraw_from_account == nathan_id);\n      BOOST_CHECK(permit_object.period_start_time > db.head_block_time());\n      first_start_time = permit_object.period_start_time;\n      BOOST_CHECK(permit_object.withdrawal_limit == asset(5));\n      BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds());\n      BOOST_CHECK(permit_object.expiration == first_start_time + permit_object.withdrawal_period_sec*5 );\n   }\n\n   {\n      withdraw_permission_claim_operation op;\n      op.withdraw_permission = permit;\n      op.withdraw_from_account = nathan_id;\n      op.withdraw_to_account = dan_id;\n      op.amount_to_withdraw = asset(1);\n      set_expiration( db, trx );\n\n      trx.operations.push_back(op);\n      sign( trx, dan_private_key ); // Transaction should be signed to be valid\n      //Throws because we haven't entered the first withdrawal period yet.\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx ), fc::exception);\n      //Get to the actual withdrawal period\n      bool miss_intermediate_blocks = false; // Required to have generate_blocks() elapse flush to the time of interest\n      generate_blocks(first_start_time, miss_intermediate_blocks);\n          set_expiration( db, trx );\n\n      REQUIRE_THROW_WITH_VALUE(op, withdraw_permission, withdraw_permission_id_type(5));\n      REQUIRE_THROW_WITH_VALUE(op, withdraw_from_account, dan_id);\n      REQUIRE_THROW_WITH_VALUE(op, withdraw_from_account, account_id_type());\n      REQUIRE_THROW_WITH_VALUE(op, withdraw_to_account, nathan_id);\n      REQUIRE_THROW_WITH_VALUE(op, withdraw_to_account, account_id_type());\n      REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(10));\n      REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(6));\n      set_expiration( db, trx );\n      trx.clear();\n      trx.operations.push_back(op);\n      sign( trx, dan_private_key );\n      PUSH_TX( db, trx ); // <-- Claim #1\n\n      // would be legal on its own, but doesn't work because trx already withdrew\n      REQUIRE_THROW_WITH_VALUE(op, amount_to_withdraw, asset(5));\n\n      // Make sure we can withdraw again this period, as long as we're not exceeding the periodic limit\n      trx.clear();\n      // withdraw 1\n      trx.operations = {op};\n      // make it different from previous trx so it's non-duplicate\n      trx.expiration += fc::seconds(1);\n      sign( trx, dan_private_key );\n      PUSH_TX( db, trx ); // <-- Claim #2\n      trx.clear();\n   }\n\n   // Account for two (2) claims of one (1) unit\n   BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 998);\n   BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 2);\n\n   {\n      const withdraw_permission_object& permit_object = permit(db);\n      BOOST_CHECK(permit_object.authorized_account == dan_id);\n      BOOST_CHECK(permit_object.withdraw_from_account == nathan_id);\n      BOOST_CHECK(permit_object.period_start_time == first_start_time);\n      BOOST_CHECK(permit_object.withdrawal_limit == asset(5));\n      BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds());\n      BOOST_CHECK_EQUAL(permit_object.claimed_this_period.value, 2 ); // <-- Account for two (2) claims of one (1) unit\n      BOOST_CHECK(permit_object.expiration == first_start_time + 5*permit_object.withdrawal_period_sec);\n      generate_blocks(first_start_time + permit_object.withdrawal_period_sec);\n      // lazy update:  verify period_start_time isn't updated until new trx occurs\n      BOOST_CHECK(permit_object.period_start_time == first_start_time);\n   }\n\n   {\n      // Leave Nathan with one unit\n      transfer(nathan_id, dan_id, asset(997));\n\n      // Attempt a withdrawal claim for units than available\n      withdraw_permission_claim_operation op;\n      op.withdraw_permission = permit;\n      op.withdraw_from_account = nathan_id;\n      op.withdraw_to_account = dan_id;\n      op.amount_to_withdraw = asset(5);\n      trx.operations.push_back(op);\n      set_expiration( db, trx );\n      sign( trx, dan_private_key );\n      //Throws because nathan doesn't have the money\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception);\n\n      // Attempt a withdrawal claim for which nathan does have sufficient units\n      op.amount_to_withdraw = asset(1);\n      trx.clear();\n      trx.operations = {op};\n      set_expiration( db, trx );\n      sign( trx, dan_private_key );\n      PUSH_TX( db, trx );\n   }\n\n   BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 0);\n   BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 1000);\n   trx.clear();\n   transfer(dan_id, nathan_id, asset(1000));\n\n   {\n      const withdraw_permission_object& permit_object = permit(db);\n      BOOST_CHECK(permit_object.authorized_account == dan_id);\n      BOOST_CHECK(permit_object.withdraw_from_account == nathan_id);\n      BOOST_CHECK(permit_object.period_start_time == first_start_time + permit_object.withdrawal_period_sec);\n      BOOST_CHECK(permit_object.expiration == first_start_time + 5*permit_object.withdrawal_period_sec);\n      BOOST_CHECK(permit_object.withdrawal_limit == asset(5));\n      BOOST_CHECK(permit_object.withdrawal_period_sec == fc::hours(1).to_seconds());\n      generate_blocks(permit_object.expiration);\n   }\n   // Ensure the permit object has been garbage collected\n   BOOST_CHECK(db.find(permit) == nullptr);\n\n   {\n      withdraw_permission_claim_operation op;\n      op.withdraw_permission = permit;\n      op.withdraw_from_account = nathan_id;\n      op.withdraw_to_account = dan_id;\n      op.amount_to_withdraw = asset(5);\n      trx.operations.push_back(op);\n      set_expiration( db, trx );\n      sign( trx, dan_private_key );\n      //Throws because the permission has expired\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( withdraw_permission_nominal_case )\n{ try {\n   INVOKE(withdraw_permission_create);\n\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   auto dan_private_key = generate_private_key(\"dan\");\n   account_id_type nathan_id = get_account(\"nathan\").get_id();\n   account_id_type dan_id = get_account(\"dan\").get_id();\n   withdraw_permission_id_type permit;\n\n   // Wait until the permission period's start time\n   const withdraw_permission_object& first_permit_object = permit(db);\n   generate_blocks(\n           first_permit_object.period_start_time);\n\n   // Loop through the withdrawal periods and claim a withdrawal\n   while(true)\n   {\n      const withdraw_permission_object& permit_object = permit(db);\n      //wdump( (permit_object) );\n      withdraw_permission_claim_operation op;\n      op.withdraw_permission = permit;\n      op.withdraw_from_account = nathan_id;\n      op.withdraw_to_account = dan_id;\n      op.amount_to_withdraw = asset(5);\n      trx.operations.push_back(op);\n      set_expiration( db, trx );\n      sign( trx, dan_private_key );\n      PUSH_TX( db, trx );\n      // tx's involving withdraw_permissions can't delete it even\n      // if no further withdrawals are possible\n      BOOST_CHECK(db.find(permit) != nullptr);\n      BOOST_CHECK( permit_object.claimed_this_period == 5 );\n      BOOST_CHECK_EQUAL( permit_object.available_this_period(db.head_block_time()).amount.value, 0 );\n      BOOST_CHECK_EQUAL( current_period(permit_object, db.head_block_time()).available_this_period.amount.value, 0 );\n      trx.clear();\n      generate_blocks(\n           permit_object.period_start_time\n         + permit_object.withdrawal_period_sec );\n      if( db.find(permit) == nullptr )\n         break;\n   }\n\n   BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 975);\n   BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 25);\n} FC_LOG_AND_RETHROW() }\n\n/**\n * Test asset whitelisting feature for withdrawals.\n * Reproduces https://github.com/bitshares/bitshares-core/issues/942 and tests the fix for it.\n */\nBOOST_AUTO_TEST_CASE( withdraw_permission_whitelist_asset_test )\n{ try {\n\n   uint32_t skip = database::skip_witness_signature\n                 | database::skip_transaction_signatures\n                 | database::skip_transaction_dupe_check\n                 | database::skip_block_size_check\n                 | database::skip_tapos_check\n                 | database::skip_merkle_check\n                 ;\n\n   generate_block( skip );\n\n   for( int i=0; i<2; i++ )\n   {\n      int blocks = 0;\n      set_expiration( db, trx );\n\n      ACTORS( (nathan)(dan)(izzy) );\n\n      const asset_id_type uia_id = create_user_issued_asset( \"ADVANCED\", izzy_id(db), white_list ).get_id();\n\n      issue_uia( nathan_id, asset(1000, uia_id) );\n\n      // Make a whitelist authority\n      {\n         BOOST_TEST_MESSAGE( \"Changing the whitelist authority\" );\n         asset_update_operation uop;\n         uop.issuer = izzy_id;\n         uop.asset_to_update = uia_id;\n         uop.new_options = uia_id(db).options;\n         uop.new_options.whitelist_authorities.insert(izzy_id);\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n         trx.operations.clear();\n      }\n\n      // Add dan to whitelist\n      {\n         upgrade_to_lifetime_member( izzy_id );\n\n         account_whitelist_operation wop;\n         wop.authorizing_account = izzy_id;\n         wop.account_to_list = dan_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.push_back( wop );\n         PUSH_TX( db, trx, ~0 );\n         trx.operations.clear();\n      }\n\n      // create withdraw permission\n      {\n         withdraw_permission_create_operation op;\n         op.authorized_account = dan_id;\n         op.withdraw_from_account = nathan_id;\n         op.withdrawal_limit = asset(5, uia_id);\n         op.withdrawal_period_sec = fc::hours(1).to_seconds();\n         op.periods_until_expiration = 5;\n         op.period_start_time = db.head_block_time() + 1;\n         trx.operations.push_back(op);\n         PUSH_TX( db, trx, ~0 );\n         trx.operations.clear();\n      }\n\n      withdraw_permission_id_type first_permit_id; // first object must have id 0\n\n      generate_block( skip ); // get to the time point that able to withdraw\n      ++blocks;\n      set_expiration( db, trx );\n\n      // try claim a withdrawal\n      {\n         withdraw_permission_claim_operation op;\n         op.withdraw_permission = first_permit_id;\n         op.withdraw_from_account = nathan_id;\n         op.withdraw_to_account = dan_id;\n         op.amount_to_withdraw = asset(5, uia_id);\n         trx.operations.push_back(op);\n         GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception );\n         trx.operations.clear();\n      }\n\n      // TODO add test cases for other white-listing features\n\n      // undo above tx's and reset\n      generate_block( skip );\n      ++blocks;\n      while( blocks > 0 )\n      {\n         db.pop_block();\n         --blocks;\n      }\n   }\n\n} FC_LOG_AND_RETHROW() }\n\n\n/**\n * This case checks to see whether the amount claimed within any particular withdrawal period\n * is properly reflected within the permission object.\n * The maximum withdrawal per period will be limited to 5 units.\n * There are a total of 5 withdrawal periods that are permitted.\n * The test will evaluate the following withdrawal pattern:\n * (1) during Period 1, a withdrawal of 4 units,\n * (2) during Period 2, a withdrawal of 1 units,\n * (3) during Period 3, a withdrawal of 0 units,\n * (4) during Period 4, a withdrawal of 5 units,\n * (5) during Period 5, a withdrawal of 3 units.\n *\n * Total withdrawal will be 13 units.\n */\nBOOST_AUTO_TEST_CASE( withdraw_permission_incremental_case )\n{ try {\n    INVOKE(withdraw_permission_create);\n    time_point_sec expected_first_period_start_time = db.head_block_time() + db.get_global_properties().parameters.block_interval*5; // Hard-coded to synchronize with withdraw_permission_create()\n    uint64_t expected_period_duration_seconds = fc::hours(1).to_seconds(); // Hard-coded to synchronize with withdraw_permission_create()\n\n    auto nathan_private_key = generate_private_key(\"nathan\");\n    auto dan_private_key = generate_private_key(\"dan\");\n    account_id_type nathan_id = get_account(\"nathan\").get_id();\n    account_id_type dan_id = get_account(\"dan\").get_id();\n    withdraw_permission_id_type permit;\n\n    // Wait until the permission period's start time\n    {\n        const withdraw_permission_object &before_first_permit_object = permit(db);\n        BOOST_CHECK_EQUAL(before_first_permit_object.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch());\n        generate_blocks(\n                before_first_permit_object.period_start_time);\n    }\n    // Before withdrawing, check the period description\n    const withdraw_permission_object &first_permit_object = permit(db);\n    const withdrawal_period_descriptor first_period = current_period(first_permit_object, db.head_block_time());\n    BOOST_CHECK_EQUAL(first_period.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch());\n    BOOST_CHECK_EQUAL(first_period.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + expected_period_duration_seconds);\n    BOOST_CHECK_EQUAL(first_period.available_this_period.amount.value, 5);\n\n    // Period 1: Withdraw 4 units\n    {\n        // Before claiming, check the period description\n        const withdraw_permission_object& permit_object = permit(db);\n        BOOST_CHECK(db.find(permit) != nullptr);\n        withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 0));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1));\n\n        // Claim\n        withdraw_permission_claim_operation op;\n        op.withdraw_permission = permit;\n        op.withdraw_from_account = nathan_id;\n        op.withdraw_to_account = dan_id;\n        op.amount_to_withdraw = asset(4);\n        trx.operations.push_back(op);\n        set_expiration( db, trx );\n        sign( trx, dan_private_key );\n        PUSH_TX( db, trx );\n\n        // After claiming, check the period description\n        BOOST_CHECK(db.find(permit) != nullptr);\n        BOOST_CHECK( permit_object.claimed_this_period == 4 );\n        BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 4 );\n        period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 1);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 0));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1));\n\n        // Advance to next period\n        trx.clear();\n        generate_blocks(\n                permit_object.period_start_time\n                + permit_object.withdrawal_period_sec );\n    }\n\n    // Period 2: Withdraw 1 units\n    {\n        // Before claiming, check the period description\n        const withdraw_permission_object& permit_object = permit(db);\n        BOOST_CHECK(db.find(permit) != nullptr);\n        withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2));\n\n        // Claim\n        withdraw_permission_claim_operation op;\n        op.withdraw_permission = permit;\n        op.withdraw_from_account = nathan_id;\n        op.withdraw_to_account = dan_id;\n        op.amount_to_withdraw = asset(1);\n        trx.operations.push_back(op);\n        set_expiration( db, trx );\n        sign( trx, dan_private_key );\n        PUSH_TX( db, trx );\n\n        // After claiming, check the period description\n        BOOST_CHECK(db.find(permit) != nullptr);\n        BOOST_CHECK( permit_object.claimed_this_period == 1 );\n        BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 1 );\n        period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 4);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 1));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2));\n\n        // Advance to next period\n        trx.clear();\n        generate_blocks(\n                permit_object.period_start_time\n                + permit_object.withdrawal_period_sec );\n    }\n\n    // Period 3: Withdraw 0 units\n    {\n        // Before claiming, check the period description\n        const withdraw_permission_object& permit_object = permit(db);\n        BOOST_CHECK(db.find(permit) != nullptr);\n        withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3));\n\n        // No claim\n\n        // After doing nothing, check the period description\n        period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 2));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3));\n\n        // Advance to end of Period 3\n        time_point_sec period_end_time = period_descriptor.period_end_time;\n        generate_blocks(period_end_time);\n    }\n\n    // Period 4: Withdraw 5 units\n    {\n        // Before claiming, check the period description\n        const withdraw_permission_object& permit_object = permit(db);\n        BOOST_CHECK(db.find(permit) != nullptr);\n        withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4));\n\n        // Claim\n        withdraw_permission_claim_operation op;\n        op.withdraw_permission = permit;\n        op.withdraw_from_account = nathan_id;\n        op.withdraw_to_account = dan_id;\n        op.amount_to_withdraw = asset(5);\n        trx.operations.push_back(op);\n        set_expiration( db, trx );\n        sign( trx, dan_private_key );\n        PUSH_TX( db, trx );\n\n        // After claiming, check the period description\n        BOOST_CHECK(db.find(permit) != nullptr);\n        BOOST_CHECK( permit_object.claimed_this_period == 5 );\n        BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 5 );\n        period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 0);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 3));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4));\n\n        // Advance to next period\n        trx.clear();\n        generate_blocks(\n                permit_object.period_start_time\n                + permit_object.withdrawal_period_sec );\n    }\n\n    // Period 5: Withdraw 3 units\n    {\n        // Before claiming, check the period description\n        const withdraw_permission_object& permit_object = permit(db);\n        BOOST_CHECK(db.find(permit) != nullptr);\n        withdrawal_period_descriptor period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 5);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 5));\n\n        // Claim\n        withdraw_permission_claim_operation op;\n        op.withdraw_permission = permit;\n        op.withdraw_from_account = nathan_id;\n        op.withdraw_to_account = dan_id;\n        op.amount_to_withdraw = asset(3);\n        trx.operations.push_back(op);\n        set_expiration( db, trx );\n        sign( trx, dan_private_key );\n        PUSH_TX( db, trx );\n\n        // After claiming, check the period description\n        BOOST_CHECK(db.find(permit) != nullptr);\n        BOOST_CHECK( permit_object.claimed_this_period == 3 );\n        BOOST_CHECK_EQUAL( permit_object.claimed_this_period.value, 3 );\n        period_descriptor = current_period(permit_object, db.head_block_time());\n        BOOST_CHECK_EQUAL(period_descriptor.available_this_period.amount.value, 2);\n        BOOST_CHECK_EQUAL(period_descriptor.period_start_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 4));\n        BOOST_CHECK_EQUAL(period_descriptor.period_end_time.sec_since_epoch(), expected_first_period_start_time.sec_since_epoch() + (expected_period_duration_seconds * 5));\n\n        // Advance to next period\n        trx.clear();\n        generate_blocks(\n                permit_object.period_start_time\n                + permit_object.withdrawal_period_sec );\n    }\n\n    // Withdrawal periods completed\n    BOOST_CHECK(db.find(permit) == nullptr);\n\n    BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 987);\n    BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 13);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( withdraw_permission_update )\n{ try {\n   INVOKE(withdraw_permission_create);\n\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   account_id_type nathan_id = get_account(\"nathan\").get_id();\n   account_id_type dan_id = get_account(\"dan\").get_id();\n   withdraw_permission_id_type permit;\n   set_expiration( db, trx );\n\n   {\n      withdraw_permission_update_operation op;\n      op.permission_to_update = permit;\n      op.authorized_account = dan_id;\n      op.withdraw_from_account = nathan_id;\n      op.periods_until_expiration = 2;\n      op.period_start_time = db.head_block_time() + 10;\n      op.withdrawal_period_sec = 10;\n      op.withdrawal_limit = asset(12);\n      trx.operations.push_back(op);\n      REQUIRE_THROW_WITH_VALUE(op, periods_until_expiration, 0);\n      REQUIRE_THROW_WITH_VALUE(op, withdrawal_period_sec, 0);\n      REQUIRE_THROW_WITH_VALUE(op, withdrawal_limit, asset(1, asset_id_type(12)));\n      REQUIRE_THROW_WITH_VALUE(op, withdrawal_limit, asset(0));\n      REQUIRE_THROW_WITH_VALUE(op, withdraw_from_account, account_id_type(0));\n      REQUIRE_THROW_WITH_VALUE(op, authorized_account, account_id_type(0));\n      REQUIRE_THROW_WITH_VALUE(op, period_start_time, db.head_block_time() - 50);\n      trx.operations.back() = op;\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx );\n   }\n\n   {\n      const withdraw_permission_object& permit_object = db.get(permit);\n      BOOST_CHECK(permit_object.authorized_account == dan_id);\n      BOOST_CHECK(permit_object.withdraw_from_account == nathan_id);\n      BOOST_CHECK(permit_object.period_start_time == db.head_block_time() + 10);\n      BOOST_CHECK(permit_object.withdrawal_limit == asset(12));\n      BOOST_CHECK(permit_object.withdrawal_period_sec == 10);\n      // BOOST_CHECK(permit_object.remaining_periods == 2);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( withdraw_permission_delete )\n{ try {\n   INVOKE(withdraw_permission_update);\n\n   withdraw_permission_delete_operation op;\n   op.authorized_account = get_account(\"dan\").id;\n   op.withdraw_from_account = get_account(\"nathan\").id;\n   set_expiration( db, trx );\n   trx.operations.push_back(op);\n   sign( trx, generate_private_key(\"nathan\" ));\n   PUSH_TX( db, trx );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( mia_feeds )\n{ try {\n   ACTORS((nathan)(dan)(ben)(vikram));\n   asset_id_type bit_usd_id = create_bitasset(\"USDBIT\").get_id();\n\n   {\n      asset_update_operation op;\n      const asset_object& obj = bit_usd_id(db);\n      op.asset_to_update = bit_usd_id;\n      op.issuer = obj.issuer;\n      op.new_issuer = nathan_id;\n      op.new_options = obj.options;\n      op.new_options.flags &= ~witness_fed_asset;\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      generate_block();\n      trx.clear();\n   }\n   {\n      asset_update_feed_producers_operation op;\n      op.asset_to_update = bit_usd_id;\n      op.issuer = nathan_id;\n      op.new_feed_producers = {dan_id, ben_id, vikram_id};\n      trx.operations.push_back(op);\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx );\n      generate_block(database::skip_nothing);\n   }\n   {\n      const asset_bitasset_data_object& obj = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(obj.feeds.size(), 3u);\n      BOOST_CHECK( obj.current_feed.margin_call_params_equal( price_feed() ) );\n   }\n   {\n      const asset_object& bit_usd = bit_usd_id(db);\n      asset_publish_feed_operation op;\n      op.publisher = vikram_id;\n      op.asset_id = bit_usd_id;\n      op.feed.settlement_price = op.feed.core_exchange_rate = ~price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(30));\n\n      // We'll expire margins after a month\n      // Accept defaults for required collateral\n      trx.operations.emplace_back(op);\n      PUSH_TX( db, trx, ~0 );\n\n      const asset_bitasset_data_object& bitasset = bit_usd.bitasset_data(db);\n      BOOST_CHECK(bitasset.current_feed.settlement_price.to_real() == 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      op.publisher = ben_id;\n      op.feed.settlement_price = op.feed.core_exchange_rate = ~price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(25));\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      op.publisher = dan_id;\n      op.feed.settlement_price = op.feed.core_exchange_rate = ~price(asset(GRAPHENE_BLOCKCHAIN_PRECISION),bit_usd.amount(40));\n      op.feed.maximum_short_squeeze_ratio = 1001;\n      op.feed.maintenance_collateral_ratio = 1001;\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION);\n      BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO);\n\n      op.publisher = nathan_id;\n      trx.operations.back() = op;\n      GRAPHENE_CHECK_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n   }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( feed_limit_test )\n{ try {\n   INVOKE( mia_feeds );\n   const asset_object& bit_usd = get_asset(\"USDBIT\");\n   const asset_bitasset_data_object& bitasset = bit_usd.bitasset_data(db);\n   GET_ACTOR(nathan);\n\n   BOOST_CHECK(!bitasset.current_feed.settlement_price.is_null());\n\n   BOOST_TEST_MESSAGE(\"Setting minimum feeds to 4\");\n   asset_update_bitasset_operation op;\n   op.new_options.minimum_feeds = 4;\n   op.asset_to_update = bit_usd.get_id();\n   op.issuer = bit_usd.issuer;\n   trx.operations = {op};\n   sign( trx, nathan_private_key );\n   PUSH_TX(db, trx);\n\n   BOOST_TEST_MESSAGE(\"Checking current_feed is null\");\n   BOOST_CHECK(bitasset.current_feed.settlement_price.is_null());\n\n   BOOST_TEST_MESSAGE(\"Setting minimum feeds to 3\");\n   op.new_options.minimum_feeds = 3;\n   trx.clear();\n   trx.operations = {op};\n   sign( trx, nathan_private_key );\n   PUSH_TX(db, trx);\n\n   BOOST_TEST_MESSAGE(\"Checking current_feed is not null\");\n   BOOST_CHECK(!bitasset.current_feed.settlement_price.is_null());\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( witness_create )\n{ try {\n\n   uint32_t skip = database::skip_witness_signature\n                 | database::skip_transaction_signatures\n                 | database::skip_transaction_dupe_check\n                 | database::skip_block_size_check\n                 | database::skip_tapos_check\n                 | database::skip_merkle_check\n                 ;\n   generate_block(skip);\n\n   auto wtplugin = app.register_plugin<graphene::witness_plugin::witness_plugin>();\n   boost::program_options::variables_map options;\n\n   // init witness key cahce\n   std::set< witness_id_type > caching_witnesses;\n   std::vector< std::string > witness_ids;\n   for( uint64_t i = 1; ; ++i )\n   {\n      witness_id_type wid(i);\n      caching_witnesses.insert( wid );\n      string wid_str = \"\\\"\" + std::string(object_id_type(wid)) + \"\\\"\";\n      witness_ids.push_back( wid_str );\n      if( !db.find(wid) )\n         break;\n   }\n   fc::set_option( options, \"witness-id\", witness_ids );\n   wtplugin->plugin_initialize(options);\n   wtplugin->plugin_startup();\n\n   const auto& wit_key_cache = wtplugin->get_witness_key_cache();\n\n   // setup test account\n   ACTOR(nathan);\n   upgrade_to_lifetime_member(nathan_id);\n   trx.clear();\n\n   // create witness\n   witness_id_type nathan_witness_id = create_witness(nathan_id, nathan_private_key, skip).get_id();\n\n   // nathan should be in the cache\n   BOOST_CHECK_EQUAL( caching_witnesses.count(nathan_witness_id), 1u );\n\n   // nathan's key in the cache should still be null before a new block is generated\n   auto nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && !nathan_itr->second.valid() );\n\n   // Give nathan some voting stake\n   transfer(committee_account, nathan_id, asset(10000000));\n   generate_block(skip);\n\n   // nathan should be a witness now\n   BOOST_REQUIRE( db.find( nathan_witness_id ) );\n   // nathan's key in the cache should have been stored now\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == nathan_private_key.get_public_key() );\n\n   // undo the block\n   db.pop_block();\n\n   // nathan should not be a witness now\n   BOOST_REQUIRE( !db.find( nathan_witness_id ) );\n   // nathan's key in the cache should still be valid, since witness plugin doesn't get notified on popped block\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == nathan_private_key.get_public_key() );\n\n   // copy popped transactions\n   auto popped_tx = db._popped_tx;\n\n   // generate another block\n   generate_block(skip);\n\n   // nathan should not be a witness now\n   BOOST_REQUIRE( !db.find( nathan_witness_id ) );\n   // nathan's key in the cache should be null now\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && !nathan_itr->second.valid() );\n\n   // push the popped tx\n   for( const auto& tx : popped_tx )\n   {\n      PUSH_TX( db, tx, skip );\n   }\n   // generate another block\n   generate_block(skip);\n   set_expiration( db, trx );\n\n   // nathan should be a witness now\n   BOOST_REQUIRE( db.find( nathan_witness_id ) );\n   // nathan's key in the cache should have been stored now\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == nathan_private_key.get_public_key() );\n\n   // generate a new key\n   fc::ecc::private_key new_signing_key = fc::ecc::private_key::regenerate(fc::digest(\"nathan_new\"));\n\n   // update nathan's block signing key\n   {\n      witness_update_operation wuop;\n      wuop.witness_account = nathan_id;\n      wuop.witness = nathan_witness_id;\n      wuop.new_signing_key = new_signing_key.get_public_key();\n      signed_transaction wu_trx;\n      wu_trx.operations.push_back( wuop );\n      set_expiration( db, wu_trx );\n      PUSH_TX( db, wu_trx, skip );\n   }\n\n   // nathan's key in the cache should still be old key\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == nathan_private_key.get_public_key() );\n\n   // generate another block\n   generate_block(skip);\n\n   // nathan's key in the cache should have changed to new key\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == new_signing_key.get_public_key() );\n\n   // undo the block\n   db.pop_block();\n\n   // nathan's key in the cache should still be new key, since witness plugin doesn't get notified on popped block\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == new_signing_key.get_public_key() );\n\n   // generate another block\n   generate_block(skip);\n\n   // nathan's key in the cache should be old key now\n   nathan_itr = wit_key_cache.find( nathan_witness_id );\n   BOOST_CHECK( nathan_itr != wit_key_cache.end() && nathan_itr->second.valid()\n                && *nathan_itr->second == nathan_private_key.get_public_key() );\n\n   // voting\n   {\n      account_update_operation op;\n      op.account = nathan_id;\n      op.new_options = nathan_id(db).options;\n      op.new_options->votes.insert(nathan_witness_id(db).vote_id);\n      op.new_options->num_witness = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),\n                                                  [](vote_id_type id) { return id.type() == vote_id_type::witness; });\n      op.new_options->num_committee = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),\n                                                    [](vote_id_type id) { return id.type() == vote_id_type::committee; });\n      trx.operations.push_back(op);\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   const auto& witnesses = db.get_global_properties().active_witnesses;\n\n   // make sure we're in active_witnesses\n   auto itr = std::find(witnesses.begin(), witnesses.end(), nathan_witness_id);\n   BOOST_CHECK(itr != witnesses.end());\n\n   // generate blocks until we are at the beginning of a round\n   while( ((db.get_dynamic_global_properties().current_aslot + 1) % witnesses.size()) != 0 )\n      generate_block();\n\n   int produced = 0;\n   // Make sure we get scheduled at least once in witnesses.size()*2 blocks\n   // may take this many unless we measure where in the scheduling round we are\n   // TODO:  intense_test that repeats this loop many times\n   for( size_t i=0, n=witnesses.size()*2; i<n; i++ )\n   {\n      signed_block block = generate_block();\n      if( block.witness == nathan_witness_id )\n         produced++;\n   }\n   BOOST_CHECK_GE( produced, 1 );\n\n   wtplugin->plugin_shutdown();\n} FC_LOG_AND_RETHROW() }\n\n/**\n *  This test should verify that the asset_global_settle operation works as expected,\n *  make sure that global settling cannot be performed by anyone other than the\n *  issuer and only if the global settle bit is set.\n */\nBOOST_AUTO_TEST_CASE( global_settle_test )\n{ try {\n   uint32_t skip = database::skip_witness_signature\n                 | database::skip_transaction_signatures\n                 | database::skip_transaction_dupe_check\n                 | database::skip_block_size_check\n                 | database::skip_tapos_check\n                 | database::skip_merkle_check\n                 ;\n\n   generate_block( skip );\n\n  for( int i=0; i<2; i++ )\n  {\n   if( i == 1 )\n   {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_342_TIME - mi, true, skip);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n   }\n   set_expiration( db, trx );\n\n   ACTORS((nathan)(ben)(valentine)(dan));\n   asset_id_type bit_usd_id = create_bitasset(\"USDBIT\", nathan_id, 100, global_settle | charge_market_fee).get_id();\n\n   update_feed_producers( bit_usd_id(db), { nathan_id } );\n\n   price_feed feed;\n   feed.settlement_price = price( asset( 1000, bit_usd_id ), asset( 500 ) );\n   feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n   feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n   publish_feed( bit_usd_id(db), nathan, feed );\n\n   transfer(committee_account, ben_id, asset(10000));\n   transfer(committee_account, valentine_id, asset(10000));\n   transfer(committee_account, dan_id, asset(10000));\n   borrow(ben, asset(1000, bit_usd_id), asset(1000));\n   BOOST_CHECK_EQUAL(get_balance(ben_id, bit_usd_id), 1000);\n   BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 9000);\n\n   create_sell_order(ben_id, asset(1000, bit_usd_id), asset(1000));\n   BOOST_CHECK_EQUAL(get_balance(ben_id, bit_usd_id), 0);\n   BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 9000);\n\n   create_sell_order(valentine_id, asset(1000), asset(1000, bit_usd_id));\n   BOOST_CHECK_EQUAL(get_balance(ben_id, bit_usd_id), 0);\n   BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10000);\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, bit_usd_id), 990);\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, asset_id_type()), 9000);\n\n   borrow(valentine, asset(500, bit_usd_id), asset(600));\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, bit_usd_id), 1490);\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, asset_id_type()), 8400);\n\n   create_sell_order(valentine_id, asset(500, bit_usd_id), asset(600));\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, bit_usd_id), 990);\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, asset_id_type()), 8400);\n\n   create_sell_order(dan_id, asset(600), asset(500, bit_usd_id));\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, bit_usd_id), 990);\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, asset_id_type()), 9000);\n   BOOST_CHECK_EQUAL(get_balance(ben_id, bit_usd_id), 0);\n   BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10000);\n   BOOST_CHECK_EQUAL(get_balance(dan_id, bit_usd_id), 495);\n   BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 9400);\n\n   // add some collateral\n   borrow(ben, asset(0, bit_usd_id), asset(1000));\n   BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 9000);\n\n   {\n      asset_global_settle_operation op;\n      op.asset_to_settle = bit_usd_id;\n      op.issuer = nathan_id;\n      op.settle_price = ~price(asset(10), asset(11, bit_usd_id));\n      trx.clear();\n      trx.operations.push_back(op);\n      REQUIRE_THROW_WITH_VALUE(op, settle_price, ~price(asset(2001), asset(1000, bit_usd_id)));\n      REQUIRE_THROW_WITH_VALUE(op, asset_to_settle, asset_id_type());\n      REQUIRE_THROW_WITH_VALUE(op, asset_to_settle, asset_id_type(100));\n      REQUIRE_THROW_WITH_VALUE(op, issuer, account_id_type(2));\n      trx.operations.back() = op;\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx );\n   }\n\n   force_settle(valentine_id(db), asset(990, bit_usd_id));\n   force_settle(dan_id(db), asset(495, bit_usd_id));\n\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, bit_usd_id), 0);\n   BOOST_CHECK_EQUAL(get_balance(valentine_id, asset_id_type()), 10045);\n   BOOST_CHECK_EQUAL(get_balance(ben_id, bit_usd_id), 0);\n   if( i == 1 ) // BSIP35: better rounding\n   {\n      BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10090);\n      BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 9850);\n   }\n   else\n   {\n      BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10091);\n      BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 9849);\n   }\n   BOOST_CHECK_EQUAL(get_balance(dan_id, bit_usd_id), 0);\n\n   // undo above tx's and reset\n   generate_block( skip );\n   db.pop_block();\n  }\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( worker_create_test )\n{ try {\n   ACTOR(nathan);\n   upgrade_to_lifetime_member(nathan_id);\n   generate_block();\n\n   {\n      worker_create_operation op;\n      op.owner = nathan_id;\n      op.daily_pay = 1000;\n      op.initializer = vesting_balance_worker_initializer(1);\n      op.work_begin_date = db.head_block_time() + 10;\n      op.work_end_date = op.work_begin_date + fc::days(2);\n      trx.clear();\n      trx.operations.push_back(op);\n      REQUIRE_THROW_WITH_VALUE(op, daily_pay, -1);\n      REQUIRE_THROW_WITH_VALUE(op, daily_pay, 0);\n      REQUIRE_THROW_WITH_VALUE(op, owner, account_id_type(1000));\n      REQUIRE_THROW_WITH_VALUE(op, work_begin_date, db.head_block_time() - 10);\n      REQUIRE_THROW_WITH_VALUE(op, work_end_date, op.work_begin_date);\n      trx.operations.back() = op;\n      sign( trx, nathan_private_key );\n      PUSH_TX( db, trx );\n   }\n\n   const worker_object& worker = worker_id_type()(db);\n   BOOST_CHECK(worker.worker_account == nathan_id);\n   BOOST_CHECK(worker.daily_pay == 1000);\n   BOOST_CHECK(worker.work_begin_date == db.head_block_time() + 10);\n   BOOST_CHECK(worker.work_end_date == db.head_block_time() + 10 + fc::days(2));\n   BOOST_CHECK(worker.vote_for.type() == vote_id_type::worker);\n   BOOST_CHECK(worker.vote_against.type() == vote_id_type::worker);\n\n   const vesting_balance_object& balance = worker.worker.get<vesting_balance_worker_type>().balance(db);\n   BOOST_CHECK(balance.owner == nathan_id);\n   BOOST_CHECK(balance.balance == asset(0));\n   BOOST_CHECK(balance.policy.get<cdd_vesting_policy>().vesting_seconds == fc::days(1).to_seconds());\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( worker_pay_test )\n{ try {\n   INVOKE(worker_create_test);\n   GET_ACTOR(nathan);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   transfer(committee_account, nathan_id, asset(100000));\n\n   {\n      account_update_operation op;\n      op.account = nathan_id;\n      op.new_options = nathan_id(db).options;\n      op.new_options->votes.insert(worker_id_type()(db).vote_for);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n   {\n      asset_reserve_operation op;\n      op.payer = account_id_type();\n      op.amount_to_reserve = asset(GRAPHENE_MAX_SHARE_SUPPLY/2);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 1000);\n   generate_blocks(db.head_block_time() + fc::hours(12));\n\n   {\n      vesting_balance_withdraw_operation op;\n      op.vesting_balance = worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance;\n      op.amount = asset(500);\n      op.owner = nathan_id;\n      set_expiration( db, trx );\n      trx.operations.push_back(op);\n      sign( trx,  nathan_private_key );\n      PUSH_TX( db, trx );\n      trx.clear_signatures();\n      REQUIRE_THROW_WITH_VALUE(op, amount, asset(1));\n      trx.clear();\n   }\n\n   BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 100500);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 500);\n\n   {\n      account_update_operation op;\n      op.account = nathan_id;\n      op.new_options = nathan_id(db).options;\n      op.new_options->votes.erase(worker_id_type()(db).vote_for);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n\n   generate_blocks(db.head_block_time() + fc::hours(12));\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 500);\n\n   {\n      vesting_balance_withdraw_operation op;\n      op.vesting_balance = worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance;\n      op.amount = asset(500);\n      op.owner = nathan_id;\n      set_expiration( db, trx );\n      trx.operations.push_back(op);\n      REQUIRE_THROW_WITH_VALUE(op, amount, asset(500));\n      generate_blocks(db.head_block_time() + fc::hours(12));\n      set_expiration( db, trx );\n      REQUIRE_THROW_WITH_VALUE(op, amount, asset(501));\n      trx.operations.back() = op;\n      sign( trx,  nathan_private_key );\n      PUSH_TX( db, trx );\n      trx.clear_signatures();\n      trx.clear();\n   }\n\n   BOOST_CHECK_EQUAL(get_balance(nathan_id, asset_id_type()), 101000);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( refund_worker_test )\n{try{\n   ACTOR(nathan);\n   upgrade_to_lifetime_member(nathan_id);\n   generate_block();\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   {\n      worker_create_operation op;\n      op.owner = nathan_id;\n      op.daily_pay = 1000;\n      op.initializer = refund_worker_initializer();\n      op.work_begin_date = db.head_block_time() + 10;\n      op.work_end_date = op.work_begin_date + fc::days(2);\n      trx.clear();\n      trx.operations.push_back(op);\n      REQUIRE_THROW_WITH_VALUE(op, daily_pay, -1);\n      REQUIRE_THROW_WITH_VALUE(op, daily_pay, 0);\n      REQUIRE_THROW_WITH_VALUE(op, owner, account_id_type(1000));\n      REQUIRE_THROW_WITH_VALUE(op, work_begin_date, db.head_block_time() - 10);\n      REQUIRE_THROW_WITH_VALUE(op, work_end_date, op.work_begin_date);\n      trx.operations.back() = op;\n      sign( trx,  nathan_private_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   const worker_object& worker = worker_id_type()(db);\n   BOOST_CHECK(worker.worker_account == nathan_id);\n   BOOST_CHECK(worker.daily_pay == 1000);\n   BOOST_CHECK(worker.work_begin_date == db.head_block_time() + 10);\n   BOOST_CHECK(worker.work_end_date == db.head_block_time() + 10 + fc::days(2));\n   BOOST_CHECK(worker.vote_for.type() == vote_id_type::worker);\n   BOOST_CHECK(worker.vote_against.type() == vote_id_type::worker);\n\n   transfer(committee_account, nathan_id, asset(100000));\n\n   {\n      account_update_operation op;\n      op.account = nathan_id;\n      op.new_options = nathan_id(db).options;\n      op.new_options->votes.insert(worker_id_type()(db).vote_for);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n   {\n      asset_reserve_operation op;\n      op.payer = account_id_type();\n      op.amount_to_reserve = asset(GRAPHENE_MAX_SHARE_SUPPLY/2);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n\n   // auto supply = asset_id_type()(db).dynamic_data(db).current_supply;\n   verify_asset_supplies(db);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   verify_asset_supplies(db);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<refund_worker_type>().total_burned.value, 1000);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   verify_asset_supplies(db);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<refund_worker_type>().total_burned.value, 2000);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   BOOST_CHECK(!db.get(worker_id_type()).is_active(db.head_block_time()));\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<refund_worker_type>().total_burned.value, 2000);\n}FC_LOG_AND_RETHROW()}\n\n/**\n * Create a burn worker, vote it in, make sure funds are destroyed.\n */\n\nBOOST_AUTO_TEST_CASE( burn_worker_test )\n{try{\n   ACTOR(nathan);\n   upgrade_to_lifetime_member(nathan_id);\n   generate_block();\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   {\n      worker_create_operation op;\n      op.owner = nathan_id;\n      op.daily_pay = 1000;\n      op.initializer = burn_worker_initializer();\n      op.work_begin_date = db.head_block_time() + 10;\n      op.work_end_date = op.work_begin_date + fc::days(2);\n      trx.clear();\n      trx.operations.push_back(op);\n      REQUIRE_THROW_WITH_VALUE(op, daily_pay, -1);\n      REQUIRE_THROW_WITH_VALUE(op, daily_pay, 0);\n      REQUIRE_THROW_WITH_VALUE(op, owner, account_id_type(1000));\n      REQUIRE_THROW_WITH_VALUE(op, work_begin_date, db.head_block_time() - 10);\n      REQUIRE_THROW_WITH_VALUE(op, work_end_date, op.work_begin_date);\n      trx.operations.back() = op;\n      sign( trx,  nathan_private_key );\n      PUSH_TX( db, trx );\n      trx.clear();\n   }\n\n   const worker_object& worker = worker_id_type()(db);\n   BOOST_CHECK(worker.worker_account == nathan_id);\n   BOOST_CHECK(worker.daily_pay == 1000);\n   BOOST_CHECK(worker.work_begin_date == db.head_block_time() + 10);\n   BOOST_CHECK(worker.work_end_date == db.head_block_time() + 10 + fc::days(2));\n   BOOST_CHECK(worker.vote_for.type() == vote_id_type::worker);\n   BOOST_CHECK(worker.vote_against.type() == vote_id_type::worker);\n\n   transfer(committee_account, nathan_id, asset(100000));\n\n   {\n      account_update_operation op;\n      op.account = nathan_id;\n      op.new_options = nathan_id(db).options;\n      op.new_options->votes.insert(worker_id_type()(db).vote_for);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n   {\n      // refund some asset to fill up the pool\n      asset_reserve_operation op;\n      op.payer = account_id_type();\n      op.amount_to_reserve = asset(GRAPHENE_MAX_SHARE_SUPPLY/2);\n      trx.operations.push_back(op);\n      PUSH_TX( db, trx, ~0 );\n      trx.clear();\n   }\n\n   BOOST_CHECK_EQUAL( get_balance(GRAPHENE_NULL_ACCOUNT, asset_id_type()), 0 );\n   verify_asset_supplies(db);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   verify_asset_supplies(db);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<burn_worker_type>().total_burned.value, 1000);\n   BOOST_CHECK_EQUAL( get_balance(GRAPHENE_NULL_ACCOUNT, asset_id_type()), 1000 );\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   verify_asset_supplies(db);\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<burn_worker_type>().total_burned.value, 2000);\n   BOOST_CHECK_EQUAL( get_balance(GRAPHENE_NULL_ACCOUNT, asset_id_type()), 2000 );\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   BOOST_CHECK(!db.get(worker_id_type()).is_active(db.head_block_time()));\n   BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get<burn_worker_type>().total_burned.value, 2000);\n   BOOST_CHECK_EQUAL( get_balance(GRAPHENE_NULL_ACCOUNT, asset_id_type()), 2000 );\n}FC_LOG_AND_RETHROW()}\n\nBOOST_AUTO_TEST_CASE( force_settle_test )\n{\n   uint32_t skip = database::skip_witness_signature\n                 | database::skip_transaction_signatures\n                 | database::skip_transaction_dupe_check\n                 | database::skip_block_size_check\n                 | database::skip_tapos_check\n                 | database::skip_merkle_check\n                 ;\n\n   generate_block( skip );\n\n  for( int i=0; i<2; i++ )\n  {\n   if( i == 1 )\n   {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_342_TIME - mi, true, skip);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip);\n   }\n   set_expiration( db, trx );\n\n   int blocks = 0;\n   try\n   {\n      ACTORS( (nathan)(shorter1)(shorter2)(shorter3)(shorter4)(shorter5) );\n\n      int64_t initial_balance = 100000000;\n\n      transfer(account_id_type()(db), shorter1_id(db), asset(initial_balance));\n      transfer(account_id_type()(db), shorter2_id(db), asset(initial_balance));\n      transfer(account_id_type()(db), shorter3_id(db), asset(initial_balance));\n      transfer(account_id_type()(db), shorter4_id(db), asset(initial_balance));\n      transfer(account_id_type()(db), shorter5_id(db), asset(initial_balance));\n\n      asset_id_type bitusd_id = create_bitasset(\n         \"USDBIT\",\n         nathan_id,\n         100,\n         disable_force_settle\n         ).get_id();\n\n      asset_id_type core_id = asset_id_type();\n\n      auto update_bitasset_options = [&]( asset_id_type asset_id,\n         std::function< void(bitasset_options&) > update_function )\n      {\n         const asset_object& _asset = asset_id(db);\n         asset_update_bitasset_operation op;\n         op.asset_to_update = asset_id;\n         op.issuer = _asset.issuer;\n         op.new_options = (*_asset.bitasset_data_id)(db).options;\n         update_function( op.new_options );\n         signed_transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, ~0 );\n      } ;\n\n      auto update_asset_options = [&]( asset_id_type asset_id,\n         std::function< void(asset_options&) > update_function )\n      {\n         const asset_object& _asset = asset_id(db);\n         asset_update_operation op;\n         op.asset_to_update = asset_id;\n         op.issuer = _asset.issuer;\n         op.new_options = _asset.options;\n         update_function( op.new_options );\n         signed_transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, ~0 );\n      } ;\n\n      BOOST_TEST_MESSAGE( \"Update maximum_force_settlement_volume = 9000\" );\n\n      BOOST_CHECK( bitusd_id(db).is_market_issued() );\n      update_bitasset_options( bitusd_id, [&]( bitasset_options& new_options )\n      { new_options.maximum_force_settlement_volume = 9000; } );\n\n      BOOST_TEST_MESSAGE( \"Publish price feed\" );\n\n      update_feed_producers( bitusd_id, { nathan_id } );\n      {\n         price_feed feed;\n         feed.settlement_price = price( asset( 1, bitusd_id ), asset( 1, core_id ) );\n         publish_feed( bitusd_id, nathan_id, feed );\n      }\n\n      BOOST_TEST_MESSAGE( \"First short batch\" );\n\n      call_order_id_type call1_id = borrow( shorter1_id, asset(1000, bitusd_id), asset(2*1000, core_id) )->get_id();   // 2.0000\n      call_order_id_type call2_id = borrow( shorter2_id, asset(2000, bitusd_id), asset(2*1999, core_id) )->get_id();   // 1.9990\n      call_order_id_type call3_id = borrow( shorter3_id, asset(3000, bitusd_id), asset(2*2890, core_id) )->get_id();   // 1.9267\n      call_order_id_type call4_id = borrow( shorter4_id, asset(4000, bitusd_id), asset(2*3950, core_id) )->get_id();   // 1.9750\n      call_order_id_type call5_id = borrow( shorter5_id, asset(5000, bitusd_id), asset(2*4900, core_id) )->get_id();   // 1.9600\n\n      transfer( shorter1_id, nathan_id, asset(1000, bitusd_id) );\n      transfer( shorter2_id, nathan_id, asset(2000, bitusd_id) );\n      transfer( shorter3_id, nathan_id, asset(3000, bitusd_id) );\n      transfer( shorter4_id, nathan_id, asset(4000, bitusd_id) );\n      transfer( shorter5_id, nathan_id, asset(5000, bitusd_id) );\n\n      BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 15000);\n      BOOST_CHECK_EQUAL( get_balance(nathan_id, core_id), 0);\n      BOOST_CHECK_EQUAL( get_balance(shorter1_id, core_id), initial_balance-2000 );\n      BOOST_CHECK_EQUAL( get_balance(shorter2_id, core_id), initial_balance-3998 );\n      BOOST_CHECK_EQUAL( get_balance(shorter3_id, core_id), initial_balance-5780 );\n      BOOST_CHECK_EQUAL( get_balance(shorter4_id, core_id), initial_balance-7900 );\n      BOOST_CHECK_EQUAL( get_balance(shorter5_id, core_id), initial_balance-9800 );\n\n      BOOST_TEST_MESSAGE( \"Update force_settlement_delay_sec = 100, force_settlement_offset_percent = 1%\" );\n\n      update_bitasset_options( bitusd_id, [&]( bitasset_options& new_options )\n      { new_options.force_settlement_delay_sec = 100;\n        new_options.force_settlement_offset_percent = GRAPHENE_1_PERCENT; } );\n\n      // Force settlement is disabled; check that it fails\n      GRAPHENE_REQUIRE_THROW( force_settle( nathan_id, asset( 50, bitusd_id ) ), fc::exception );\n\n      update_asset_options( bitusd_id, [&]( asset_options& new_options )\n      { new_options.flags &= ~disable_force_settle; } );\n\n      // Can't settle more BitUSD than you own\n      GRAPHENE_REQUIRE_THROW( force_settle( nathan_id, asset( 999999, bitusd_id ) ), fc::exception );\n\n      // settle3 should be least collateralized order according to index\n      BOOST_CHECK( db.get_index_type<call_order_index>().indices().get<by_collateral>().begin()->id == call3_id );\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 3000 );\n\n      BOOST_TEST_MESSAGE( \"Verify partial settlement of call\" );\n      // Partially settle a call\n      force_settlement_id_type settle_id { *force_settle( nathan_id, asset( 50, bitusd_id ) )\n                                               .get< extendable_operation_result >().value.new_objects->begin() };\n\n      // Call does not take effect immediately\n      BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 14950);\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 50);\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 3000 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 5780 );\n      BOOST_CHECK( settle_id(db).owner == nathan_id );\n\n      // Wait for settlement to take effect\n      generate_blocks( settle_id(db).settlement_date, true, skip );\n      blocks += 2;\n\n      BOOST_CHECK(db.find(settle_id) == nullptr);\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 50 );\n      BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 14950);\n      BOOST_CHECK_EQUAL( get_balance(nathan_id, core_id), 49 );   // 1% force_settlement_offset_percent (rounded unfavorably)\n      BOOST_CHECK_EQUAL( call3_id(db).debt.value, 2950 );\n      BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 5731 );  // 5731 == 5780-49\n\n      BOOST_CHECK( db.get_index_type<call_order_index>().indices().get<by_collateral>().begin()->id == call3_id );\n\n      BOOST_TEST_MESSAGE( \"Verify pending settlement is cancelled when asset's force_settle is disabled\" );\n      // Ensure pending settlement is cancelled when force settle is disabled\n      settle_id = *force_settle( nathan_id, asset( 50, bitusd_id ) )\n                                               .get< extendable_operation_result >().value.new_objects->begin();\n\n      BOOST_CHECK( !db.get_index_type<force_settlement_index>().indices().empty() );\n      update_asset_options( bitusd_id, [&]( asset_options& new_options )\n      { new_options.flags |= disable_force_settle; } );\n      BOOST_CHECK(  db.get_index_type<force_settlement_index>().indices().empty() );\n      update_asset_options( bitusd_id, [&]( asset_options& new_options )\n      { new_options.flags &= ~disable_force_settle; } );\n\n      BOOST_TEST_MESSAGE( \"Perform iterative settlement\" );\n      settle_id = *force_settle( nathan_id, asset( 12500, bitusd_id ) )\n                                               .get< extendable_operation_result >().value.new_objects->begin();\n\n      // c3 2950 : 5731   1.9427   fully settled\n      // c5 5000 : 9800   1.9600   fully settled\n      // c4 4000 : 7900   1.9750   fully settled\n      // c2 2000 : 3998   1.9990   550 settled\n      // c1 1000 : 2000   2.0000\n\n      generate_blocks( settle_id(db).settlement_date, true, skip );\n      blocks += 2;\n\n      int64_t call1_payout =                0;\n      int64_t call2_payout =       550*99/100;\n      int64_t call3_payout = 49 + 2950*99/100;\n      int64_t call4_payout =      4000*99/100;\n      int64_t call5_payout =      5000*99/100;\n\n      if( i == 1 ) // BSIP35: better rounding\n      {\n         call3_payout = 49 + (2950*99+100-1)/100; // round up\n         call4_payout =      (4000*99+100-1)/100; // round up\n         call5_payout =      (5000*99+100-1)/100; // round up\n      }\n\n      BOOST_CHECK_EQUAL( get_balance(shorter1_id, core_id), initial_balance-2*1000 );  // full collat still tied up\n      BOOST_CHECK_EQUAL( get_balance(shorter2_id, core_id), initial_balance-2*1999 );  // full collat still tied up\n      BOOST_CHECK_EQUAL( get_balance(shorter3_id, core_id), initial_balance-call3_payout );  // initial balance minus transfer to Nathan (as BitUSD)\n      BOOST_CHECK_EQUAL( get_balance(shorter4_id, core_id), initial_balance-call4_payout );  // initial balance minus transfer to Nathan (as BitUSD)\n      BOOST_CHECK_EQUAL( get_balance(shorter5_id, core_id), initial_balance-call5_payout );  // initial balance minus transfer to Nathan (as BitUSD)\n\n      BOOST_CHECK_EQUAL( get_balance(nathan_id, core_id),\n           call1_payout + call2_payout + call3_payout + call4_payout + call5_payout );\n\n      BOOST_CHECK( db.find(call3_id) == nullptr );\n      BOOST_CHECK( db.find(call4_id) == nullptr );\n      BOOST_CHECK( db.find(call5_id) == nullptr );\n\n      BOOST_REQUIRE( db.find(call1_id) != nullptr );\n      BOOST_REQUIRE( db.find(call2_id) != nullptr );\n\n      BOOST_CHECK_EQUAL( call1_id(db).debt.value, 1000 );\n      BOOST_CHECK_EQUAL( call1_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( call2_id(db).debt.value, 2000-550 );\n      BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 3998-call2_payout );\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n\n   // undo above tx's and reset\n   generate_block( skip );\n   ++blocks;\n   while( blocks > 0 )\n   {\n      db.pop_block();\n      --blocks;\n   }\n  }\n}\n\nBOOST_AUTO_TEST_CASE( assert_op_test )\n{\n   try {\n   // create some objects\n   auto nathan_private_key = generate_private_key(\"nathan\");\n   public_key_type nathan_public_key = nathan_private_key.get_public_key();\n   account_id_type nathan_id = create_account(\"nathan\", nathan_public_key).get_id();\n\n   assert_operation op;\n\n   // nathan checks that his public key is equal to the given value.\n   op.fee_paying_account = nathan_id;\n   op.predicates.emplace_back(account_name_eq_lit_predicate{ nathan_id, \"nathan\" });\n   trx.operations.push_back(op);\n   sign( trx, nathan_private_key );\n   PUSH_TX( db, trx );\n\n   // nathan checks that his public key is not equal to the given value (fail)\n   trx.clear();\n   op.predicates.emplace_back(account_name_eq_lit_predicate{ nathan_id, \"dan\" });\n   trx.operations.push_back(op);\n   sign( trx, nathan_private_key );\n   GRAPHENE_CHECK_THROW( PUSH_TX( db, trx ), fc::exception );\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( balance_object_test )\n{ try {\n   // Intentionally overriding the fixture's db; I need to control genesis on this one.\n   database db;\n   const uint32_t skip_flags = database::skip_undo_history_check;\n   fc::temp_directory td( graphene::utilities::temp_directory_path() );\n   genesis_state.initial_balances.push_back({address(generate_private_key(\"n\").get_public_key()), GRAPHENE_SYMBOL, 1});\n   genesis_state.initial_balances.push_back({address(generate_private_key(\"x\").get_public_key()), GRAPHENE_SYMBOL, 1});\n   fc::time_point_sec starting_time = genesis_state.initial_timestamp + 3000;\n\n   auto n_key = generate_private_key(\"n\");\n   auto x_key = generate_private_key(\"x\");\n   auto v1_key = generate_private_key(\"v1\");\n   auto v2_key = generate_private_key(\"v2\");\n\n   genesis_state_type::initial_vesting_balance_type vest;\n   vest.owner = address(v1_key.get_public_key());\n   vest.asset_symbol = GRAPHENE_SYMBOL;\n   vest.amount = 500;\n   vest.begin_balance = vest.amount;\n   vest.begin_timestamp = starting_time;\n   vest.vesting_duration_seconds = 60;\n   genesis_state.initial_vesting_balances.push_back(vest);\n   vest.owner = address(v2_key.get_public_key());\n   vest.begin_timestamp -= fc::seconds(30);\n   vest.amount = 400;\n   genesis_state.initial_vesting_balances.push_back(vest);\n\n   genesis_state.initial_accounts.emplace_back(\"n\", n_key.get_public_key());\n\n   auto _sign = [&]( signed_transaction& tx, const private_key_type& key )\n   {  tx.sign( key, db.get_chain_id() );   };\n\n   db.open(td.path(), [this]{return genesis_state;}, \"TEST\");\n   const balance_object& balance = balance_id_type()(db);\n   BOOST_CHECK_EQUAL(balance.balance.amount.value, 1);\n   BOOST_CHECK_EQUAL(balance_id_type(1)(db).balance.amount.value, 1);\n\n   balance_claim_operation op;\n   op.deposit_to_account = db.get_index_type<account_index>().indices().get<by_name>().find(\"n\")->get_id();\n   op.total_claimed = asset(1);\n   op.balance_to_claim = balance_id_type(1);\n   op.balance_owner_key = x_key.get_public_key();\n   trx.operations = {op};\n   _sign( trx, n_key );\n   // Fail because I'm claiming from an address which hasn't signed\n   GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), tx_missing_other_auth);\n   trx.clear();\n   op.balance_to_claim = balance_id_type();\n   op.balance_owner_key = n_key.get_public_key();\n   trx.operations = {op};\n   _sign( trx, n_key );\n   PUSH_TX(db, trx);\n\n   // Not using fixture's get_balance() here because it uses fixture's db, not my override\n   BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 1);\n   BOOST_CHECK(db.find(balance_id_type()) == nullptr);\n   BOOST_CHECK(db.find(balance_id_type(1)) != nullptr);\n\n   auto slot = db.get_slot_at_time(starting_time);\n   db.generate_block(starting_time, db.get_scheduled_witness(slot), init_account_priv_key, skip_flags);\n   set_expiration( db, trx );\n\n   const balance_object& vesting_balance_1 = balance_id_type(2)(db);\n   const balance_object& vesting_balance_2 = balance_id_type(3)(db);\n   BOOST_CHECK(vesting_balance_1.is_vesting_balance());\n   BOOST_CHECK_EQUAL(vesting_balance_1.balance.amount.value, 500);\n   BOOST_CHECK_EQUAL(vesting_balance_1.available(db.head_block_time()).amount.value, 0);\n   BOOST_CHECK(vesting_balance_2.is_vesting_balance());\n   BOOST_CHECK_EQUAL(vesting_balance_2.balance.amount.value, 400);\n   BOOST_CHECK_EQUAL(vesting_balance_2.available(db.head_block_time()).amount.value, 150);\n\n   op.balance_to_claim = vesting_balance_1.id;\n   op.total_claimed = asset(1);\n   op.balance_owner_key = v1_key.get_public_key();\n   trx.clear();\n   trx.operations = {op};\n   _sign( trx, n_key );\n   _sign( trx, v1_key );\n   // Attempting to claim 1 from a balance with 0 available\n   GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_invalid_claim_amount);\n\n   op.balance_to_claim = vesting_balance_2.id;\n   op.total_claimed.amount = 151;\n   op.balance_owner_key = v2_key.get_public_key();\n   trx.operations = {op};\n   trx.clear_signatures();\n   _sign( trx, n_key );\n   _sign( trx, v2_key );\n   // Attempting to claim 151 from a balance with 150 available\n   GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_invalid_claim_amount);\n\n   op.balance_to_claim = vesting_balance_2.id;\n   op.total_claimed.amount = 100;\n   op.balance_owner_key = v2_key.get_public_key();\n   trx.operations = {op};\n   trx.clear_signatures();\n   _sign( trx, n_key );\n   _sign( trx, v2_key );\n   PUSH_TX(db, trx);\n   BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 101);\n   BOOST_CHECK_EQUAL(vesting_balance_2.balance.amount.value, 300);\n\n   op.total_claimed.amount = 10;\n   trx.operations = {op};\n   trx.clear_signatures();\n   _sign( trx, n_key );\n   _sign( trx, v2_key );\n   // Attempting to claim twice within a day\n   GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_claimed_too_often);\n\n   db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, skip_flags);\n   slot = db.get_slot_at_time(vesting_balance_1.vesting_policy->begin_timestamp + 60);\n   db.generate_block(db.get_slot_time(slot), db.get_scheduled_witness(slot), init_account_priv_key, skip_flags);\n   set_expiration( db, trx );\n\n   op.balance_to_claim = vesting_balance_1.id;\n   op.total_claimed.amount = 500;\n   op.balance_owner_key = v1_key.get_public_key();\n   trx.operations = {op};\n   trx.clear_signatures();\n   _sign( trx, n_key );\n   _sign( trx, v1_key );\n   PUSH_TX(db, trx);\n   BOOST_CHECK(db.find(op.balance_to_claim) == nullptr);\n   BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 601);\n\n   op.balance_to_claim = vesting_balance_2.id;\n   op.balance_owner_key = v2_key.get_public_key();\n   op.total_claimed.amount = 10;\n   trx.operations = {op};\n   trx.clear_signatures();\n   _sign( trx, n_key );\n   _sign( trx, v2_key );\n   // Attempting to claim twice within a day\n   GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), balance_claim_claimed_too_often);\n\n   db.generate_block(db.get_slot_time(1), db.get_scheduled_witness(1), init_account_priv_key, skip_flags);\n   slot = db.get_slot_at_time(db.head_block_time() + fc::days(1));\n   db.generate_block(db.get_slot_time(slot), db.get_scheduled_witness(slot), init_account_priv_key, skip_flags);\n   set_expiration( db, trx );\n\n   op.total_claimed = vesting_balance_2.balance;\n   trx.operations = {op};\n   trx.clear_signatures();\n   _sign( trx, n_key );\n   _sign( trx, v2_key );\n   PUSH_TX(db, trx);\n   BOOST_CHECK(db.find(op.balance_to_claim) == nullptr);\n   BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 901);\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(transfer_with_memo) {\n   try {\n      ACTOR(alice);\n      ACTOR(bob);\n      transfer(account_id_type(), alice_id, asset(1000));\n      BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 1000);\n\n      transfer_operation op;\n      op.from = alice_id;\n      op.to = bob_id;\n      op.amount = asset(500);\n      op.memo = memo_data();\n      op.memo->set_message(alice_private_key, bob_public_key, \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n      trx.operations = {op};\n      trx.sign(alice_private_key, db.get_chain_id());\n      PUSH_TX(db, trx);\n\n      BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 500);\n      BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 500);\n\n      auto memo = db.get_recent_transaction(trx.id()).operations.front().get<transfer_operation>().memo;\n      BOOST_CHECK(memo);\n      BOOST_CHECK_EQUAL(memo->get_message(bob_private_key, alice_public_key), \"Dear Bob,\\n\\nMoney!\\n\\nLove, Alice\");\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(zero_second_vbo)\n{\n   try\n   {\n      ACTOR(alice);\n      // don't pay witnesses so we have some worker budget to work with\n\n      transfer(account_id_type(), alice_id, asset(int64_t(100000) * 1100 * 1000 * 1000));\n      {\n         asset_reserve_operation op;\n         op.payer = alice_id;\n         op.amount_to_reserve = asset(int64_t(100000) * 1000 * 1000 * 1000);\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      }\n      enable_fees();\n      upgrade_to_lifetime_member(alice_id);\n      generate_block();\n\n      // Wait for a maintenance interval to ensure we have a full day's budget to work with.\n      // Otherwise we may not have enough to feed the witnesses and the worker will end up starved if we start late in the day.\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      auto check_vesting_1b = [&](vesting_balance_id_type vbid)\n      {\n         // this function checks that Alice can't draw any right now,\n         // but one block later, she can withdraw it all.\n\n         vesting_balance_withdraw_operation withdraw_op;\n         withdraw_op.vesting_balance = vbid;\n         withdraw_op.owner = alice_id;\n         withdraw_op.amount = asset(1);\n\n         signed_transaction withdraw_tx;\n         withdraw_tx.operations.push_back( withdraw_op );\n         sign(withdraw_tx, alice_private_key);\n         GRAPHENE_REQUIRE_THROW( PUSH_TX( db, withdraw_tx ), fc::exception );\n\n         generate_block();\n         withdraw_tx = signed_transaction();\n         withdraw_op.amount = asset(500);\n         withdraw_tx.operations.push_back( withdraw_op );\n         set_expiration( db, withdraw_tx );\n         sign(withdraw_tx, alice_private_key);\n         PUSH_TX( db, withdraw_tx );\n      };\n\n      // This block creates a zero-second VBO with a vesting_balance_create_operation.\n      {\n         cdd_vesting_policy_initializer pinit;\n         pinit.vesting_seconds = 0;\n\n         vesting_balance_create_operation create_op;\n         create_op.creator = alice_id;\n         create_op.owner = alice_id;\n         create_op.amount = asset(500);\n         create_op.policy = pinit;\n\n         signed_transaction create_tx;\n         create_tx.operations.push_back( create_op );\n         set_expiration( db, create_tx );\n         sign(create_tx, alice_private_key);\n\n         processed_transaction ptx = PUSH_TX( db, create_tx );\n         vesting_balance_id_type vbid { ptx.operation_results[0].get<object_id_type>() };\n         check_vesting_1b( vbid );\n      }\n\n      // This block creates a zero-second VBO with a worker_create_operation.\n      {\n         worker_create_operation create_op;\n         create_op.owner = alice_id;\n         create_op.work_begin_date = db.head_block_time();\n         create_op.work_end_date = db.head_block_time() + fc::days(1000);\n         create_op.daily_pay = share_type( 10000 );\n         create_op.name = \"alice\";\n         create_op.url = \"\";\n         create_op.initializer = vesting_balance_worker_initializer(0);\n         signed_transaction create_tx;\n         create_tx.operations.push_back(create_op);\n         set_expiration( db, create_tx );\n         sign(create_tx, alice_private_key);\n         processed_transaction ptx = PUSH_TX( db, create_tx );\n         worker_id_type wid { ptx.operation_results[0].get<object_id_type>() };\n\n         // vote it in\n         account_update_operation vote_op;\n         vote_op.account = alice_id;\n         vote_op.new_options = alice_id(db).options;\n         vote_op.new_options->votes.insert(wid(db).vote_for);\n         signed_transaction vote_tx;\n         vote_tx.operations.push_back(vote_op);\n         set_expiration( db, vote_tx );\n         sign( vote_tx, alice_private_key );\n         PUSH_TX( db, vote_tx );\n\n         // vote it in, wait for one maint. for vote to take effect\n         vesting_balance_id_type vbid = wid(db).worker.get<vesting_balance_worker_type>().balance;\n         // wait for another maint. for worker to be paid\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         BOOST_CHECK( vbid(db).get_allowed_withdraw(db.head_block_time()) == asset(0) );\n         generate_block();\n         BOOST_CHECK( vbid(db).get_allowed_withdraw(db.head_block_time()) == asset(10000) );\n\n         /*\n         db.get_index_type< simple_index<budget_record_object> >().inspect_all_objects(\n            [&](const object& o)\n            {\n               ilog( \"budget: ${brec}\", (\"brec\", static_cast<const budget_record_object&>(o)) );\n            });\n         */\n      }\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( vbo_withdraw_different )\n{\n   try\n   {\n      ACTORS((alice)(izzy));\n      // don't pay witnesses so we have some worker budget to work with\n\n      // transfer(account_id_type(), alice_id, asset(1000));\n\n      asset_id_type stuff_id = create_user_issued_asset( \"STUFF\", izzy_id(db), 0 ).get_id();\n      issue_uia( alice_id, asset( 1000, stuff_id ) );\n\n      // deposit STUFF with linear vesting policy\n      vesting_balance_id_type vbid;\n      {\n         linear_vesting_policy_initializer pinit;\n         pinit.begin_timestamp = db.head_block_time();\n         pinit.vesting_cliff_seconds    = 30;\n         pinit.vesting_duration_seconds = 30;\n\n         vesting_balance_create_operation create_op;\n         create_op.creator = alice_id;\n         create_op.owner = alice_id;\n         create_op.amount = asset(100, stuff_id);\n         create_op.policy = pinit;\n\n         signed_transaction create_tx;\n         create_tx.operations.push_back( create_op );\n         set_expiration( db, create_tx );\n         sign(create_tx, alice_private_key);\n\n         processed_transaction ptx = PUSH_TX( db, create_tx );\n         vbid = ptx.operation_results[0].get<object_id_type>();\n      }\n\n      // wait for VB to mature\n      generate_blocks( 30 );\n\n      BOOST_CHECK( vbid(db).get_allowed_withdraw( db.head_block_time() ) == asset(100, stuff_id) );\n\n      // bad withdrawal op (wrong asset)\n      {\n         vesting_balance_withdraw_operation op;\n\n         op.vesting_balance = vbid;\n         op.amount = asset(100);\n         op.owner = alice_id;\n\n         signed_transaction withdraw_tx;\n         withdraw_tx.operations.push_back(op);\n         set_expiration( db, withdraw_tx );\n         sign( withdraw_tx, alice_private_key );\n         GRAPHENE_CHECK_THROW( PUSH_TX( db, withdraw_tx ), fc::exception );\n      }\n\n      // good withdrawal op\n      {\n         vesting_balance_withdraw_operation op;\n\n         op.vesting_balance = vbid;\n         op.amount = asset(100, stuff_id);\n         op.owner = alice_id;\n\n         signed_transaction withdraw_tx;\n         withdraw_tx.operations.push_back(op);\n         set_expiration( db, withdraw_tx );\n         sign( withdraw_tx, alice_private_key );\n         PUSH_TX( db, withdraw_tx );\n      }\n   }\n   FC_LOG_AND_RETHROW()\n}\n\n// TODO:  Write linear VBO tests\n\nBOOST_AUTO_TEST_CASE( top_n_special )\n{\n   ACTORS( (alice)(bob)(chloe)(dan)(izzy)(stan) );\n\n   try\n   {\n      {\n         //\n         // Izzy (issuer)\n         // Stan (special authority)\n         // Alice, Bob, Chloe, Dan (ABCD)\n         //\n\n         asset_id_type topn_id = create_user_issued_asset( \"TOPN\", izzy_id(db), 0 ).get_id();\n         authority stan_owner_auth = stan_id(db).owner;\n         authority stan_active_auth = stan_id(db).active;\n\n         // set SA, wait for maint interval\n         // TODO:  account_create_operation\n         // TODO:  multiple accounts with different n for same asset\n\n         {\n            top_holders_special_authority top2, top3;\n\n            top2.num_top_holders = 2;\n            top2.asset = topn_id;\n\n            top3.num_top_holders = 3;\n            top3.asset = topn_id;\n\n            account_update_operation op;\n            op.account = stan_id;\n            op.extensions.value.active_special_authority = top3;\n            op.extensions.value.owner_special_authority = top2;\n\n            signed_transaction tx;\n            tx.operations.push_back( op );\n\n            set_expiration( db, tx );\n            sign( tx, stan_private_key );\n\n            PUSH_TX( db, tx );\n\n            // TODO:  Check special_authority is properly set\n            // TODO:  Do it in steps\n         }\n\n         // wait for maint interval\n         // make sure we don't have any authority as account hasn't gotten distributed yet\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         BOOST_CHECK( stan_id(db).owner  == stan_owner_auth );\n         BOOST_CHECK( stan_id(db).active == stan_active_auth );\n\n         // issue some to Alice, make sure she gets control of Stan\n\n         // we need to set_expiration() before issue_uia() because the latter doens't call it #11\n         set_expiration( db, trx );  // #11\n         issue_uia( alice_id, asset( 1000, topn_id ) );\n\n         BOOST_CHECK( stan_id(db).owner  == stan_owner_auth );\n         BOOST_CHECK( stan_id(db).active == stan_active_auth );\n\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         /*  NOTE - this was an old check from an earlier implementation that only allowed SA for LTM's\n         // no boost yet, we need to upgrade to LTM before mechanics apply to Stan\n         BOOST_CHECK( stan_id(db).owner  == stan_owner_auth );\n         BOOST_CHECK( stan_id(db).active == stan_active_auth );\n\n         set_expiration( db, trx );  // #11\n         upgrade_to_lifetime_member(stan_id);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         */\n\n         BOOST_CHECK( stan_id(db).owner  == authority(  501, alice_id, 1000 ) );\n         BOOST_CHECK( stan_id(db).active == authority(  501, alice_id, 1000 ) );\n\n         // give asset to Stan, make sure owner doesn't change at all\n         set_expiration( db, trx );  // #11\n         transfer( alice_id, stan_id, asset( 1000, topn_id ) );\n\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         BOOST_CHECK( stan_id(db).owner  == authority(  501, alice_id, 1000 ) );\n         BOOST_CHECK( stan_id(db).active == authority(  501, alice_id, 1000 ) );\n\n         set_expiration( db, trx );  // #11\n         issue_uia( chloe_id, asset( 131000, topn_id ) );\n\n         // now Chloe has 131,000 and Stan has 1k.  Make sure change occurs at next maintenance interval.\n         // NB, 131072 is a power of 2; the number 131000 was chosen so that we need a bitshift, but\n         // if we put the 1000 from Stan's balance back into play, we need a different bitshift.\n\n         // we use Chloe so she can be displaced by Bob later (showing the tiebreaking logic).\n\n         // Check Alice is still in control, because we're deferred to next maintenance interval\n         BOOST_CHECK( stan_id(db).owner  == authority(  501, alice_id, 1000 ) );\n         BOOST_CHECK( stan_id(db).active == authority(  501, alice_id, 1000 ) );\n\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         BOOST_CHECK( stan_id(db).owner  == authority( 32751, chloe_id, 65500 ) );\n         BOOST_CHECK( stan_id(db).active == authority( 32751, chloe_id, 65500 ) );\n\n         // put Alice's stake back in play\n         set_expiration( db, trx );  // #11\n         transfer( stan_id, alice_id, asset( 1000, topn_id ) );\n\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         BOOST_CHECK( stan_id(db).owner  == authority( 33001, alice_id, 500, chloe_id, 65500 ) );\n         BOOST_CHECK( stan_id(db).active == authority( 33001, alice_id, 500, chloe_id, 65500 ) );\n\n         // issue 200,000 to Dan to cause another bitshift.\n         set_expiration( db, trx );  // #11\n         issue_uia( dan_id, asset( 200000, topn_id ) );\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         // 200000 Dan\n         // 131000 Chloe\n         // 1000 Alice\n\n         BOOST_CHECK( stan_id(db).owner  == authority( 41376,                chloe_id, 32750, dan_id, 50000 ) );\n         BOOST_CHECK( stan_id(db).active == authority( 41501, alice_id, 250, chloe_id, 32750, dan_id, 50000 ) );\n\n         // have Alice send all but 1 back to Stan, verify that we clamp Alice at one vote\n         set_expiration( db, trx );  // #11\n         transfer( alice_id, stan_id, asset( 999, topn_id ) );\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         BOOST_CHECK( stan_id(db).owner  == authority( 41376,                chloe_id, 32750, dan_id, 50000 ) );\n         BOOST_CHECK( stan_id(db).active == authority( 41376, alice_id,   1, chloe_id, 32750, dan_id, 50000 ) );\n\n         // send 131k to Bob so he's tied with Chloe, verify he displaces Chloe in top2\n         set_expiration( db, trx );  // #11\n         issue_uia( bob_id, asset( 131000, topn_id ) );\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         BOOST_CHECK( stan_id(db).owner  == authority( 41376, bob_id, 32750,                  dan_id, 50000 ) );\n         BOOST_CHECK( stan_id(db).active == authority( 57751, bob_id, 32750, chloe_id, 32750, dan_id, 50000 ) );\n\n         // TODO more rounding checks\n      }\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( buyback )\n{\n   ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin) );\n   upgrade_to_lifetime_member(philbin_id);\n\n   generate_blocks( HARDFORK_555_TIME );\n\n   try\n   {\n      {\n         //\n         // Izzy (issuer)\n         // Alice, Bob, Chloe, Dan (ABCD)\n         // Rex (recycler -- buyback account)\n         // Philbin (registrar)\n         //\n\n         asset_id_type nono_id = create_user_issued_asset( \"NONO\", izzy_id(db), 0 ).get_id();\n         asset_id_type buyme_id = create_user_issued_asset( \"BUYME\", izzy_id(db), 0 ).get_id();\n\n         // Create a buyback account\n         account_id_type rex_id;\n         {\n            buyback_account_options bbo;\n            bbo.asset_to_buy = buyme_id;\n            bbo.asset_to_buy_issuer = izzy_id;\n            bbo.markets.emplace( asset_id_type() );\n            account_create_operation create_op = make_account( \"rex\" );\n            create_op.registrar = philbin_id;\n            create_op.extensions.value.buyback_options = bbo;\n            create_op.owner = authority::null_authority();\n            create_op.active = authority::null_authority();\n\n            // Let's break it...\n\n            signed_transaction tx;\n            tx.operations.push_back( create_op );\n            set_expiration( db, tx );\n\n            tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = alice_id;\n            sign( tx, alice_private_key );\n            sign( tx, philbin_private_key );\n\n            // Alice and Philbin signed, but asset issuer is invalid\n            GRAPHENE_CHECK_THROW( PUSH_TX(db, tx), account_create_buyback_incorrect_issuer );\n\n            tx.clear_signatures();\n            tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = izzy_id;\n            sign( tx, philbin_private_key );\n\n            // Izzy didn't sign\n            GRAPHENE_CHECK_THROW( PUSH_TX(db, tx), tx_missing_active_auth );\n            sign( tx, izzy_private_key );\n\n            // OK\n            processed_transaction ptx = PUSH_TX( db, tx );\n            rex_id = ptx.operation_results.back().get< object_id_type >();\n\n            // Try to create another account rex2 which is bbo on same asset\n            tx.clear_signatures();\n            tx.operations.back().get< account_create_operation >().name = \"rex2\";\n            sign( tx, izzy_private_key );\n            sign( tx, philbin_private_key );\n            GRAPHENE_CHECK_THROW( PUSH_TX(db, tx), account_create_buyback_already_exists );\n         }\n\n         // issue some BUYME to Alice\n         // we need to set_expiration() before issue_uia() because the latter doens't call it #11\n         set_expiration( db, trx );  // #11\n         issue_uia( alice_id, asset( 1000, buyme_id ) );\n         issue_uia( alice_id, asset( 1000, nono_id ) );\n\n         // Alice wants to sell 100 BUYME for 1000 BTS, a middle price.\n         limit_order_id_type order_id_mid = create_sell_order( alice_id, asset( 100, buyme_id ), asset( 1000, asset_id_type() ) )->get_id();\n\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         generate_block();\n\n         // no success because buyback has none for sale\n         BOOST_CHECK( order_id_mid(db).for_sale == 100 );\n\n         // but we can send some to buyback\n         fund( rex_id(db), asset( 100, asset_id_type() ) );\n         // no action until next maint\n         BOOST_CHECK( order_id_mid(db).for_sale == 100 );\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         generate_block();\n\n         // partial fill, Alice now sells 90 BUYME for 900 BTS.\n         BOOST_CHECK( order_id_mid(db).for_sale == 90 );\n\n         // TODO check burn amount\n\n         // aagh more state in trx\n         set_expiration( db, trx );  // #11\n\n         // Selling 10 BUYME for 50 BTS, a low price.\n         limit_order_id_type order_id_low  = create_sell_order( alice_id, asset( 10, buyme_id ), asset(  50, asset_id_type() ) )->get_id();\n         // Selling 10 BUYME for 150 BTS, a high price.\n         limit_order_id_type order_id_high = create_sell_order( alice_id, asset( 10, buyme_id ), asset( 150, asset_id_type() ) )->get_id();\n\n         fund( rex_id(db), asset( 250, asset_id_type() ) );\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n         generate_block();\n\n         BOOST_CHECK( db.find( order_id_low  ) == nullptr );\n         BOOST_CHECK( db.find( order_id_mid  ) != nullptr );\n         BOOST_CHECK( db.find( order_id_high ) != nullptr );\n\n         // 250 CORE in rex                 90 BUYME in mid order    10 BUYME in low order\n         //  50 CORE goes to low order, buy 10 for 50 CORE\n         // 200 CORE goes to mid order, buy 20 for 200 CORE\n         //                                 70 BUYME in mid order     0 BUYME in low order\n\n         idump( (order_id_mid(db)) );\n         BOOST_CHECK( order_id_mid(db).for_sale == 70 );\n         BOOST_CHECK( order_id_high(db).for_sale == 10 );\n\n         BOOST_CHECK( get_balance( rex_id, asset_id_type() ) == 0 );\n\n         // clear out the books -- 700 left on mid order, 150 left on high order, so 2000 BTS should result in 1150 left over\n\n         fund( rex_id(db), asset( 2000, asset_id_type() ) );\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n         idump( (get_balance( rex_id, asset_id_type() )) );\n\n         BOOST_CHECK( get_balance( rex_id, asset_id_type() ) == 1150 );\n\n         GRAPHENE_CHECK_THROW( transfer( alice_id, rex_id, asset( 1, nono_id ) ), fc::exception );\n         // TODO: Check cancellation works for account which is BTS-restricted\n      }\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/oso_take_profit_order_tests.cpp",
    "content": "/*\n * Copyright (c) 2023 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/market_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( oso_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_hardfork_time_test )\n{ try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_2362_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n\n      // Before the hard fork, unable to create a limit order with the \"on_fill\" extension\n      // or create with proposals,\n      // but can create without on_fill\n      create_take_profit_order_action tpa1 { asset_id_type(), 5, GRAPHENE_100_PERCENT, 3600, false };\n      vector<limit_order_auto_action> on_fill { tpa1 };\n\n      // With on_fill\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Without on_fill\n      create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), {} );\n\n      // Proposal with on_fill\n      limit_order_create_operation cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      BOOST_CHECK_THROW( propose( cop1 ), fc::exception );\n      // Proposal without on_fill\n      limit_order_create_operation cop2 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), {} );\n      propose( cop2 );\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests setting up oso with limit_order_create_operation\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_setup_test )\n{ try {\n\n      // Proceeds to the hard fork\n      generate_blocks( HARDFORK_CORE_2535_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n\n      // Spread percentage should be positive\n      create_take_profit_order_action tpa1 { asset_id_type(), 0, GRAPHENE_100_PERCENT, 3600, false };\n      vector<limit_order_auto_action> on_fill { tpa1 };\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Cannot propose either\n      limit_order_create_operation cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      BOOST_CHECK_THROW( propose( cop1 ), fc::exception );\n\n      // Size percentage should be positive\n      tpa1 = { asset_id_type(), 1, 0, 3600, false };\n      on_fill = { tpa1 };\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Cannot propose either\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      BOOST_CHECK_THROW( propose( cop1 ), fc::exception );\n\n      // Size percentage should not exceed 100%\n      tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT + 1, 3600, false };\n      on_fill = { tpa1 };\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Cannot propose either\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      BOOST_CHECK_THROW( propose( cop1 ), fc::exception );\n\n      // Expiration should be positive\n      tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT, 0, false };\n      on_fill = { tpa1 };\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Cannot propose either\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      BOOST_CHECK_THROW( propose( cop1 ), fc::exception );\n\n      // Fee asset should exist\n      tpa1 = { usd_id + 1, 1, GRAPHENE_100_PERCENT, 3600, false };\n      on_fill = { tpa1 };\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Can propose\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      propose( cop1 );\n\n      // on_fill must contain only 1 action\n      tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT, 3600, false };\n      // size == 0\n      on_fill = {};\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Can propose\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      propose( cop1 );\n      // size > 1\n      on_fill = { tpa1, tpa1 };\n      BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill ),\n                         fc::exception );\n      // Can propose\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      propose( cop1 );\n\n      // A valid operation with on_fill\n      tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT, 3600, false };\n      on_fill = { tpa1 };\n      const limit_order_object* order1 = create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill );\n      // Can propose\n      cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), on_fill );\n      propose( cop1 );\n\n      BOOST_REQUIRE( order1 );\n      limit_order_id_type order1_id = order1->get_id();\n\n      // Another order without on_fill\n      const limit_order_object* order2 = create_sell_order( sam_id, asset(1), asset(1, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), {} );\n      BOOST_REQUIRE( order2 );\n      limit_order_id_type order2_id = order2->get_id();\n\n      // Final check\n      const auto& check_result = [&]()\n      {\n         BOOST_CHECK( order2_id(db).on_fill.empty() );\n\n         BOOST_REQUIRE_EQUAL( order1_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action = order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action.fee_asset_id == tpa1.fee_asset_id );\n         BOOST_CHECK( action.spread_percent == tpa1.spread_percent );\n         BOOST_CHECK( action.size_percent == tpa1.size_percent );\n         BOOST_CHECK( action.expiration_seconds == tpa1.expiration_seconds );\n         BOOST_CHECK( action.repeat == tpa1.repeat );\n      };\n\n      check_result();\n\n      generate_block();\n\n      check_result();\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests order-sends-take-profit-order and related order cancellation\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_trigger_and_cancel_test )\n{ try {\n\n      // Proceeds to the hard fork\n      generate_blocks( HARDFORK_CORE_2535_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      additional_asset_options_t usd_options;\n      usd_options.value.taker_fee_percent = 80; // 0.8% taker fee\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, charge_market_fee | white_list,\n                                                 price(asset(1, asset_id_type(1)), asset(1)),\n                                                 4, 30, usd_options ); // 0.3% maker fee\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type core_id;\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n      issue_uia( ted, asset(init_amount, usd_id) );\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_sam_usd = 0;\n      int64_t expected_balance_ted_usd = init_amount;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n      };\n\n      check_balances();\n\n      // Sam sells CORE for USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa1 { core_id,    100, 10000,         3600, false };\n      vector<limit_order_auto_action> on_fill_1 { tpa1 };\n\n      const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill_1 );\n      BOOST_REQUIRE( sell_order1 );\n      limit_order_id_type sell_order1_id = sell_order1->get_id();\n\n      limit_order_id_type last_order_id = sell_order1_id;\n\n      BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n      BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n      BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n      const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n      BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n      BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n      BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n      BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n      BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // Ted buys CORE with USD without on_fill, partially fills Sam's order\n      const limit_order_object* buy_order1 = create_sell_order( ted_id, asset(1235, usd_id), asset(1000) );\n      last_order_id = last_order_id + 1;\n\n      // The buy order is smaller, it gets fully filled\n      BOOST_CHECK( !buy_order1 );\n      expected_balance_ted_core += 1000;\n      expected_balance_ted_usd -= 1235;\n\n      // The newly created take profit order is a buy order\n      last_order_id = last_order_id + 1;\n      limit_order_id_type buy_order2_id = last_order_id;\n\n      auto buy_order2_expiration = db.head_block_time() + 3600;\n\n      const auto& check_result_1 = [&]()\n      {\n         // The sell order is partially filled\n         BOOST_REQUIRE( db.find(sell_order1_id) );\n         // The take profit order\n         BOOST_REQUIRE( db.find(buy_order2_id) );\n         BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n         // The sell order gets 1235, market fee = round_down(1235 * 30 / 10000) = 3\n         BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); // 1235 - 3\n         // price = (12345 / 10000) / 101% = 12345 / 10100\n         // min to receive = round_up( 1232 * 10100 / 12345 ) = 1008\n         // updated price = 1232 / 1008\n         BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n         BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n         BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n         BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n\n         // The sell order is partially filled, pays 1000\n         BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); // 10000 - 1000\n         BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n         check_balances();\n      };\n\n      check_result_1();\n\n      generate_block();\n\n      check_result_1();\n\n      // Sam sells more CORE for USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa2 {  usd_id,     70,  9700, uint32_t(-1), true };\n      vector<limit_order_auto_action> on_fill_2 { tpa2 };\n\n      const limit_order_object* sell_order2 = create_sell_order( sam_id, asset(10000), asset(13000, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill_2 );\n      last_order_id = last_order_id + 1;\n\n      BOOST_REQUIRE( sell_order2 );\n      limit_order_id_type sell_order2_id = sell_order2->get_id();\n\n      BOOST_REQUIRE_EQUAL( sell_order2_id(db).on_fill.size(), 1U );\n      BOOST_REQUIRE( sell_order2_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n      const auto& action_s2 = sell_order2_id(db).on_fill.front().get<create_take_profit_order_action>();\n      BOOST_CHECK( action_s2.fee_asset_id == tpa2.fee_asset_id );\n      BOOST_CHECK( action_s2.spread_percent == tpa2.spread_percent );\n      BOOST_CHECK( action_s2.size_percent == tpa2.size_percent );\n      BOOST_CHECK( action_s2.expiration_seconds == tpa2.expiration_seconds );\n      BOOST_CHECK( action_s2.repeat == tpa2.repeat );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // Sam sells yet more CORE for USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa3 {  usd_id,     70,  9970,         3600, true };\n      vector<limit_order_auto_action> on_fill_3 { tpa3 };\n\n      const limit_order_object* sell_order3 = create_sell_order( sam_id, asset(10000), asset(34000, usd_id),\n                                            db.head_block_time() + 7200, price::unit_price(), on_fill_3 );\n      last_order_id = last_order_id + 1;\n\n      BOOST_REQUIRE( sell_order3 );\n      limit_order_id_type sell_order3_id = sell_order3->get_id();\n\n      // Data backup\n      auto sell_order3_expiration = sell_order3->expiration;\n\n      BOOST_REQUIRE_EQUAL( sell_order3_id(db).on_fill.size(), 1U );\n      BOOST_REQUIRE( sell_order3_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n      const auto& action_s3 = sell_order3_id(db).on_fill.front().get<create_take_profit_order_action>();\n      BOOST_CHECK( action_s3.fee_asset_id == tpa3.fee_asset_id );\n      BOOST_CHECK( action_s3.spread_percent == tpa3.spread_percent );\n      BOOST_CHECK( action_s3.size_percent == tpa3.size_percent );\n      BOOST_CHECK( action_s3.expiration_seconds == tpa3.expiration_seconds );\n      BOOST_CHECK( action_s3.repeat == tpa3.repeat );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // Ted buys CORE with USD with on_fill, fills Sam's orders\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa4 { core_id,      1,  9999, uint32_t(-1), true };\n      vector<limit_order_auto_action> on_fill_4 { tpa4 };\n\n      const limit_order_object* buy_order3 = create_sell_order( ted_id, asset(30000, usd_id), asset(7000),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill_4 );\n      last_order_id = last_order_id + 1;\n\n      // buy_order3 is fully filled\n      BOOST_CHECK( !buy_order3 );\n\n      // The take profit order created by sell_order1 is updated\n      auto buy_order2_expiration_new = db.head_block_time() + 3600;\n\n      // The take profit order created by buy_order3 is a sell order\n      last_order_id = last_order_id + 1;\n      limit_order_id_type sell_order4_id = last_order_id;\n      auto sell_order4_expiration = time_point_sec::maximum();\n\n      // The take profit order created by sell_order2 is a buy order\n      last_order_id = last_order_id + 1;\n      limit_order_id_type buy_order4_id = last_order_id;\n      auto buy_order4_expiration = time_point_sec::maximum();\n\n      // The take profit order created by sell_order3 is a buy order\n      last_order_id = last_order_id + 1;\n      limit_order_id_type buy_order5_id = last_order_id;\n      auto buy_order5_expiration = db.head_block_time() + 3600;\n\n      expected_balance_ted_core += 1; // see calculation below\n      expected_balance_ted_usd -= (30000 - 1); // buy_order3 refund 1, see calculation below\n      expected_balance_sam_usd += (388 + 17); // sell_order2 and sell_order3, see calculation below\n\n\n      const auto& check_result_2 = [&]()\n      {\n         // sell_order1 gets fully filled\n         BOOST_CHECK( !db.find(sell_order1_id) );\n\n         // The take profit order linked to sell_order1 (buy_order2) is updated\n         BOOST_REQUIRE( db.find(buy_order2_id) );\n         BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n         // sell_order1 pays 9000, gets round_down(9000 * 12345 / 10000) = 11110, market fee = 11110 * 30 / 10000 = 33\n         BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 12309 ); // 1232 + 11110 - 33\n         // price = (12345 / 10000) / 101% = 12345 / 10100\n         // min to receive = round_up( 12309 * 10100 / 12345 ) = 10071\n         // updated price = 12309 / 10071\n         BOOST_CHECK( buy_order2_id(db).sell_price == asset(12309,usd_id) / asset(10071) );\n         BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration_new );\n         BOOST_CHECK( buy_order2_id(db).expiration != buy_order2_expiration );\n         BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n         BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n\n         // buy_order3 pays 11110, gets 9000, remaining for sale = 30000 - 11110 = 18890\n\n         // sell_order2 gets fully filled\n         BOOST_CHECK( !db.find(sell_order2_id) );\n\n         // The take profit order created by sell_order2\n         BOOST_REQUIRE( db.find(buy_order4_id) );\n         BOOST_CHECK( buy_order4_id(db).seller == sam_id );\n         // sell_order2 gets 13000, market fee = round_down(13000 * 30 / 10000) = 39\n         // gets = 13000 - 39 = 12961\n         // take profit order size = round_up(12961 * 9700 / 10000) = 12573\n         // Sam USD balance change = 12961 - 12573 = 388\n         BOOST_CHECK_EQUAL( buy_order4_id(db).for_sale.value, 12573 );\n         // price = (13000 / 10000) / 100.7% = 13000 / 10070\n         // min to receive = round_up( 12573 * 10070 / 13000 ) = 9740\n         // updated price = 12573 / 9740\n         BOOST_CHECK( buy_order4_id(db).sell_price == asset(12573,usd_id) / asset(9740) );\n         BOOST_CHECK( buy_order4_id(db).expiration == buy_order4_expiration );\n         BOOST_CHECK( !buy_order4_id(db).take_profit_order_id );\n\n         BOOST_REQUIRE_EQUAL( buy_order4_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( buy_order4_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_b4 = buy_order4_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_b4.fee_asset_id == tpa2.fee_asset_id );\n         BOOST_CHECK( action_b4.spread_percent == tpa2.spread_percent );\n         BOOST_CHECK( action_b4.size_percent == tpa2.size_percent );\n         BOOST_CHECK( action_b4.expiration_seconds == tpa2.expiration_seconds );\n         BOOST_CHECK( action_b4.repeat == tpa2.repeat );\n\n         // buy_order3 pays 13000, gets 10000, remaining for sale = 18890 - 13000 = 5890\n\n         // sell_order3 gets partially filled\n         BOOST_REQUIRE( db.find(sell_order3_id) );\n         // The take profit order created by sell_order3\n         BOOST_REQUIRE( db.find(buy_order5_id) );\n         BOOST_CHECK( buy_order5_id(db).seller == sam_id );\n         // sell_order3 gets 5890, pays round_down(5890 * 10000 / 34000) = 1732\n         // updated gets = round_up(1732 * 34000 / 10000) = 5889, refund = 5890 - 5889 = 1\n         // market fee = round_down(5889 * 30 / 10000) = 17\n         // gets = 5889 - 17 = 5872\n         // take profit order size = round_up(5872 * 9970 / 10000) = 5855\n         // Sam USD balance change = 5872 - 5855 = 17\n         BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5855 );\n         // price = (34000 / 10000) / 100.7% = 34000 / 10070\n         // min to receive = round_up( 5855 * 10070 / 34000 ) = 1735\n         // updated price = 5855 / 1735 = 3.374639769\n         BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) );\n         BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration );\n         BOOST_CHECK( buy_order5_id(db).take_profit_order_id == sell_order3_id );\n\n         BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_b5 = buy_order5_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id );\n         BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent );\n         BOOST_CHECK( action_b5.size_percent == tpa3.size_percent );\n         BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds );\n         BOOST_CHECK( action_b5.repeat == tpa3.repeat );\n\n         // sell_order3 gets partially filled, pays 1732\n         BOOST_CHECK_EQUAL( sell_order3_id(db).for_sale.value, 8268 ); // 10000 - 1732\n         BOOST_CHECK( sell_order3_id(db).take_profit_order_id == buy_order5_id );\n\n         // buy_order3 gets 1732, pays 5889, refund 1\n\n         // The take profit order created by buy_order3\n         BOOST_REQUIRE( db.find(sell_order4_id) );\n         BOOST_CHECK( sell_order4_id(db).seller == ted_id );\n         // buy_order3 got in total 9000 + 10000 + 1732 = 20732, market fee = 0\n         // take profit order size =\n         //   round_up(9000 * 9999 / 10000) + round_up(10000 * 9999 / 10000) + round_up(1732 * 9999 / 10000) = 20731\n         // Ted CORE balance change = 20732 - 20731 = 1\n         BOOST_CHECK_EQUAL( sell_order4_id(db).for_sale.value, 20731 );\n         // price = (7000 / 30000) / 100.01% = 7000 / 30003\n         // min to receive = round_up( 20731 * 30003 / 7000 ) = 88857\n         // updated price = 20731 / 88857\n         BOOST_CHECK( sell_order4_id(db).sell_price == asset(20731) / asset(88857,usd_id) );\n         BOOST_CHECK( sell_order4_id(db).expiration == sell_order4_expiration );\n         BOOST_CHECK( !sell_order4_id(db).take_profit_order_id );\n\n         BOOST_REQUIRE_EQUAL( sell_order4_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( sell_order4_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_s4 = sell_order4_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s4.fee_asset_id == tpa4.fee_asset_id );\n         BOOST_CHECK( action_s4.spread_percent == tpa4.spread_percent );\n         BOOST_CHECK( action_s4.size_percent == tpa4.size_percent );\n         BOOST_CHECK( action_s4.expiration_seconds == tpa4.expiration_seconds );\n         BOOST_CHECK( action_s4.repeat == tpa4.repeat );\n\n         check_balances();\n      };\n\n      check_result_2();\n\n      generate_block();\n\n      check_result_2();\n\n      // Ted sells CORE for USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa5 {  usd_id,  65535,     1,         8800, true };\n      vector<limit_order_auto_action> on_fill_5 { tpa5 };\n\n      const limit_order_object* sell_order5 = create_sell_order( ted_id, asset(1), asset(1, usd_id),\n                                            db.head_block_time() + 9900, price::unit_price(), on_fill_5 );\n      last_order_id = last_order_id + 1;\n\n      // sell_order5 is fully filled\n      BOOST_CHECK( !sell_order5 );\n\n      // buy_order5 is partially filled\n      // The take profit order linked to buy_order5 (sell_order3) is updated\n      auto sell_order3_expiration_new = db.head_block_time() + 3600;\n\n      // The take profit order created by sell_order5 is a buy order\n      last_order_id = last_order_id + 1;\n      limit_order_id_type buy_order6_id = last_order_id;\n      auto buy_order6_expiration = db.head_block_time() + 8800;\n\n      expected_balance_ted_core -= 1; // see calculation below\n      expected_balance_ted_usd += 2; // see calculation below\n\n      const auto check_result_3 = [&]()\n      {\n         // buy_order5 is partially filled\n         BOOST_REQUIRE( db.find(buy_order5_id) );\n         BOOST_CHECK( buy_order5_id(db).seller == sam_id );\n         // buy_order5 gets 1, pays round_down(1 * 5855 / 1735) = 3\n         BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5852 ); // 5855 - 3\n         BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); // unchanged\n         BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); // unchanged\n         BOOST_CHECK( buy_order5_id(db).take_profit_order_id == sell_order3_id ); // unchanged\n\n         // All unchanged\n         BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_b5 = buy_order5_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id );\n         BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent );\n         BOOST_CHECK( action_b5.size_percent == tpa3.size_percent );\n         BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds );\n         BOOST_CHECK( action_b5.repeat == tpa3.repeat );\n\n         // The take profit order linked to buy_order5 (sell_order3) is updated\n         BOOST_REQUIRE( db.find(sell_order3_id) );\n         BOOST_CHECK( sell_order3_id(db).seller == sam_id );\n         // new amount for sale = round_up(1 * 99.7%) = 1, account balances unchanged\n         BOOST_CHECK_EQUAL( sell_order3_id(db).for_sale.value, 8269 ); // 8268 + 1\n         BOOST_CHECK( sell_order3_id(db).sell_price == asset(10000) / asset(34000,usd_id) ); // unchanged\n         BOOST_CHECK( sell_order3_id(db).expiration == sell_order3_expiration_new );\n         BOOST_CHECK( sell_order3_id(db).expiration != sell_order3_expiration );\n         BOOST_CHECK( sell_order3_id(db).take_profit_order_id == buy_order5_id ); // unchanged\n\n         // All unchanged\n         BOOST_REQUIRE_EQUAL( sell_order3_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( sell_order3_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_s3 = sell_order3_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s3.fee_asset_id == tpa3.fee_asset_id );\n         BOOST_CHECK( action_s3.spread_percent == tpa3.spread_percent );\n         BOOST_CHECK( action_s3.size_percent == tpa3.size_percent );\n         BOOST_CHECK( action_s3.expiration_seconds == tpa3.expiration_seconds );\n         BOOST_CHECK( action_s3.repeat == tpa3.repeat );\n\n         // The take profit order created by sell_order5\n         BOOST_REQUIRE( db.find(buy_order6_id) );\n         BOOST_CHECK( buy_order6_id(db).seller == ted_id );\n         // sell_order5 gets 3, market fee = round_down(3 * 30 / 10000) = 0, still gets 3\n         // take profit order size = round_up(3 * 1 / 10000) = 1\n         // Ted USD balance change = 3 - 1 = 2\n         BOOST_CHECK_EQUAL( buy_order6_id(db).for_sale.value, 1 );\n         // price = (1 / 1) / (1 + 655.35%) = 10000 / 75535\n         // min to receive = round_up( 1 * 75535 / 10000 ) = 8\n         // updated price = 1 / 8\n         BOOST_CHECK( buy_order6_id(db).sell_price == asset(1,usd_id) / asset(8) );\n         BOOST_CHECK( buy_order6_id(db).expiration == buy_order6_expiration );\n         BOOST_CHECK( !buy_order6_id(db).take_profit_order_id );\n\n         BOOST_REQUIRE_EQUAL( buy_order6_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( buy_order6_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_b6 = buy_order6_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_b6.fee_asset_id == tpa5.fee_asset_id );\n         BOOST_CHECK( action_b6.spread_percent == tpa5.spread_percent );\n         BOOST_CHECK( action_b6.size_percent == tpa5.size_percent );\n         BOOST_CHECK( action_b6.expiration_seconds == tpa5.expiration_seconds );\n         BOOST_CHECK( action_b6.repeat == tpa5.repeat );\n\n         check_balances();\n      };\n\n      check_result_3();\n\n      generate_block();\n\n      check_result_3();\n\n      // Sam places an order to buy CORE with USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa6 { core_id,     10, 10000, uint32_t(-1), true };\n      vector<limit_order_auto_action> on_fill_6 { tpa6 };\n\n      const limit_order_object* buy_order7 = create_sell_order( sam_id, asset(338, usd_id), asset(100),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill_6 );\n      last_order_id = last_order_id + 1;\n\n      BOOST_REQUIRE( buy_order7 );\n      limit_order_id_type buy_order7_id = buy_order7->get_id();\n\n      BOOST_CHECK( buy_order7_id(db).seller == sam_id );\n      BOOST_CHECK_EQUAL( buy_order7_id(db).for_sale.value, 338 );\n      BOOST_CHECK( buy_order7_id(db).sell_price == asset(338,usd_id) / asset(100) );\n      BOOST_CHECK( buy_order7_id(db).expiration == time_point_sec::maximum() );\n      BOOST_CHECK( !buy_order7_id(db).take_profit_order_id );\n\n      BOOST_REQUIRE_EQUAL( buy_order7_id(db).on_fill.size(), 1U );\n      BOOST_REQUIRE( buy_order7_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n      const auto& action_b7 = buy_order7_id(db).on_fill.front().get<create_take_profit_order_action>();\n      BOOST_CHECK( action_b7.fee_asset_id == tpa6.fee_asset_id );\n      BOOST_CHECK( action_b7.spread_percent == tpa6.spread_percent );\n      BOOST_CHECK( action_b7.size_percent == tpa6.size_percent );\n      BOOST_CHECK( action_b7.expiration_seconds == tpa6.expiration_seconds );\n      BOOST_CHECK( action_b7.repeat == tpa6.repeat );\n\n      expected_balance_sam_usd -= 338;\n\n      check_balances();\n\n      // Make a whitelist, Sam is not in\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = usd_id;\n         uop.issuer = usd_id(db).issuer;\n         uop.new_options = usd_id(db).options;\n         // The whitelist is managed by Ted\n         uop.new_options.whitelist_authorities.insert(ted_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Ted so that he can manage the whitelist\n         upgrade_to_lifetime_member( ted_id );\n\n         // Add Ted to the whitelist, but do not add others\n         account_whitelist_operation wop;\n         wop.authorizing_account = ted_id;\n         wop.account_to_list = ted_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Ted sells CORE for USD, fully fills buy_order7, partially fills buy_order5\n      const limit_order_object* sell_order7 = create_sell_order( ted_id, asset(200), asset(200, usd_id) );\n      last_order_id = last_order_id + 1;\n\n      // sell_order7 is fully filled\n      BOOST_CHECK( !sell_order7 );\n\n      expected_balance_sam_core += 200; // See calculation below\n      expected_balance_ted_core -= 200; // See calculation below\n      expected_balance_ted_usd += 671; // 336 + 335, See calculation below\n\n      const auto check_result_4 = [&]()\n      {\n         // buy_order7 is fully filled\n         BOOST_CHECK( !db.find(buy_order7_id) );\n         // buy_order7 gets 100, pays = round_down(100 * 3380 / 1000) = 338,\n         // updated gets = round_up( 338 * 1000 / 3380 ) = 100\n\n         // fails to create a take profit order due to whitelisting\n         BOOST_CHECK( !db.find(last_order_id+1) );\n\n         // Ted gets 338 USD, market fee = round_down(338 * 0.8%) = 2,\n         // updated gets = 338 - 2 = 336\n\n         // buy_order5 is partially filled\n         BOOST_REQUIRE( db.find(buy_order5_id) );\n         BOOST_CHECK( buy_order5_id(db).seller == sam_id );\n         // buy_order5 gets 100, pays round_down(100 * 5855 / 1735) = 337\n         // updated gets = round_up(337 * 1735 / 5855) = 100\n         BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5515 ); // 5852 - 337\n         BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); // unchanged\n         BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); // unchanged\n         BOOST_CHECK( buy_order5_id(db).take_profit_order_id == sell_order3_id ); // unchanged\n\n         // All unchanged\n         BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_b5 = buy_order5_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id );\n         BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent );\n         BOOST_CHECK( action_b5.size_percent == tpa3.size_percent );\n         BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds );\n         BOOST_CHECK( action_b5.repeat == tpa3.repeat );\n\n         // Due to whitelisting, the take profit order linked to buy_order5 (sell_order3) is unchanged\n         BOOST_REQUIRE( db.find(sell_order3_id) );\n         BOOST_CHECK( sell_order3_id(db).seller == sam_id );\n         BOOST_CHECK_EQUAL( sell_order3_id(db).for_sale.value, 8269 ); // unchanged\n         BOOST_CHECK( sell_order3_id(db).sell_price == asset(10000) / asset(34000,usd_id) ); // unchanged\n         BOOST_CHECK( sell_order3_id(db).expiration == sell_order3_expiration_new );\n         BOOST_CHECK( sell_order3_id(db).take_profit_order_id == buy_order5_id ); // unchanged\n\n         // All unchanged\n         BOOST_REQUIRE_EQUAL( sell_order3_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( sell_order3_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_s3 = sell_order3_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s3.fee_asset_id == tpa3.fee_asset_id );\n         BOOST_CHECK( action_s3.spread_percent == tpa3.spread_percent );\n         BOOST_CHECK( action_s3.size_percent == tpa3.size_percent );\n         BOOST_CHECK( action_s3.expiration_seconds == tpa3.expiration_seconds );\n         BOOST_CHECK( action_s3.repeat == tpa3.repeat );\n\n         // Ted gets 337 USD, market fee = round_down(337 * 0.8%) = 2,\n         // updated gets = 337 - 2 = 335\n\n         check_balances();\n      };\n\n      check_result_4();\n\n      generate_block();\n\n      check_result_4();\n\n      const asset_object& eur = create_user_issued_asset(\"MYEUR\");\n      asset_id_type eur_id = eur.get_id();\n\n      // Ted buys EUR with USD\n      const limit_order_object* buy_eur = create_sell_order( ted_id, asset(200, usd_id), asset(200, eur_id) );\n      last_order_id = last_order_id + 1;\n\n      limit_order_id_type buy_eur_id = buy_eur->get_id();\n\n      expected_balance_ted_usd -= 200;\n\n      const auto check_result_5 = [&]()\n      {\n         // Check that the failed OSO operation does not increase the internal next value of limit_order_id\n         BOOST_CHECK( last_order_id == buy_eur_id );\n\n         check_balances();\n      };\n\n      check_result_5();\n\n      generate_block();\n\n      check_result_5();\n\n      // Sam cancels an order\n      cancel_limit_order( sell_order3_id(db) );\n\n      expected_balance_sam_core += 8269;\n\n      const auto check_result_6 = [&]()\n      {\n         // buy_order5 is canceled\n         BOOST_CHECK( !db.find(sell_order3_id) );\n\n         // The take profit order linked to sell_order3 (buy_order5) is updated\n         BOOST_REQUIRE( db.find(buy_order5_id) );\n         BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5515 ); // unchanged\n         BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); // unchanged\n         BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); // unchanged\n         BOOST_CHECK( !buy_order5_id(db).take_profit_order_id ); // cleared\n\n         // Others all unchanged\n         BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_b5 = buy_order5_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id );\n         BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent );\n         BOOST_CHECK( action_b5.size_percent == tpa3.size_percent );\n         BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds );\n         BOOST_CHECK( action_b5.repeat == tpa3.repeat );\n\n         check_balances();\n      };\n\n      check_result_6();\n\n      generate_block();\n\n      check_result_6();\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests a scenario where a take profit order fails to be sent due to extreme order price\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_fail_test_1 )\n{ try {\n\n      // Proceeds to the hard fork\n      generate_blocks( HARDFORK_CORE_2535_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type core_id;\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      issue_uia( ted, asset(GRAPHENE_MAX_SHARE_SUPPLY, usd_id) );\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_sam_usd = 0;\n      int64_t expected_balance_ted_usd = GRAPHENE_MAX_SHARE_SUPPLY;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n      };\n\n      check_balances();\n\n      // Ted buys CORE with USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa1 { core_id,    500, 10000,         3600, false };\n      vector<limit_order_auto_action> on_fill_1 { tpa1 };\n\n      const limit_order_object* sell_order1 = create_sell_order( ted_id, asset(GRAPHENE_MAX_SHARE_SUPPLY, usd_id),\n                                                                 asset(100), time_point_sec::maximum(),\n                                                                 price::unit_price(), on_fill_1 );\n      BOOST_REQUIRE( sell_order1 );\n      limit_order_id_type sell_order1_id = sell_order1->get_id();\n\n      limit_order_id_type last_order_id = sell_order1_id;\n\n      BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n      BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n      BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n      const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n      BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n      BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n      BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n      BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n      BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n      expected_balance_ted_usd -= GRAPHENE_MAX_SHARE_SUPPLY;\n      check_balances();\n\n      // Sam sells CORE for USD without on_fill, fully fills Ted's order\n      const limit_order_object* buy_order1 = create_sell_order( sam_id, asset(100),\n                                                                asset(GRAPHENE_MAX_SHARE_SUPPLY, usd_id) );\n      last_order_id = last_order_id + 1;\n\n      // The buy order gets fully filled\n      BOOST_CHECK( !buy_order1 );\n\n      expected_balance_sam_core -= 100;\n      expected_balance_sam_usd += GRAPHENE_MAX_SHARE_SUPPLY;\n\n      expected_balance_ted_core += 100;\n\n      const auto& check_result_1 = [&]()\n      {\n         // The sell order is fully filled\n         BOOST_CHECK( !db.find(sell_order1_id) );\n\n         // The take profit order is not created due to an exception\n         BOOST_CHECK( !db.find(last_order_id+1) );\n\n         check_balances();\n      };\n\n      check_result_1();\n\n      generate_block();\n\n      check_result_1();\n\n      // Sam sells more CORE for USD without on_fill\n      const limit_order_object* sell_order2 = create_sell_order( sam_id, asset(10000), asset(13000, usd_id) );\n      last_order_id = last_order_id + 1;\n\n      BOOST_REQUIRE( sell_order2 );\n      limit_order_id_type sell_order2_id = sell_order2->get_id();\n\n      expected_balance_sam_core -= 10000;\n\n      const auto check_result_2 = [&]()\n      {\n         // Check that the failed OSO operation does not increase the internal next value of limit_order_id\n         BOOST_CHECK( last_order_id == sell_order2_id );\n\n         check_balances();\n      };\n\n      check_result_2();\n\n      generate_block();\n\n      check_result_2();\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests OSO-related order updates: basic operation validation and evaluation\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_update_basic_test )\n{ try {\n\n      // Proceeds to the hard fork\n      generate_blocks( HARDFORK_CORE_2535_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type core_id;\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // Sam sells CORE for USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa1 { core_id,    100, 10000,         3600, false };\n      vector<limit_order_auto_action> on_fill_1 { tpa1 };\n\n      const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill_1 );\n      BOOST_REQUIRE( sell_order1 );\n      limit_order_id_type sell_order1_id = sell_order1->get_id();\n\n      // Sam tries to update a limit order\n\n      // Spread percentage should be positive\n      //     fee_asset, spread,  size,   expiration, repeat\n      tpa1 = { core_id,      0, 10000,         3600, false };\n      on_fill_1 = { tpa1 };\n      BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ),\n                         fc::exception );\n      // Cannot propose either\n      limit_order_update_operation uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 );\n      BOOST_CHECK_THROW( propose( uop1 ), fc::exception );\n\n      // Size percentage should be positive\n      //     fee_asset, spread,  size,   expiration, repeat\n      tpa1 = { core_id,      1,     0,         3600, false };\n      on_fill_1 = { tpa1 };\n      BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ),\n                         fc::exception );\n      // Cannot propose either\n      uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 );\n      BOOST_CHECK_THROW( propose( uop1 ), fc::exception );\n\n      // Size percentage should not exceed 100%\n      //     fee_asset, spread,  size,   expiration, repeat\n      tpa1 = { core_id,      1, 10001,         3600, false };\n      on_fill_1 = { tpa1 };\n      BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ),\n                         fc::exception );\n      // Cannot propose either\n      uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 );\n      BOOST_CHECK_THROW( propose( uop1 ), fc::exception );\n\n      // Expiration should be positive\n      //     fee_asset, spread,  size,   expiration, repeat\n      tpa1 = { core_id,      1, 10000,            0, false };\n      on_fill_1 = { tpa1 };\n      BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ),\n                         fc::exception );\n      // Cannot propose either\n      uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 );\n      BOOST_CHECK_THROW( propose( uop1 ), fc::exception );\n\n      // Fee asset should exist\n      tpa1 = { usd_id + 1, 1, GRAPHENE_100_PERCENT, 3600, false };\n      on_fill_1 = { tpa1 };\n      BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ),\n                         fc::exception );\n      // Can propose\n      uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 );\n      propose( uop1 );\n\n      // on_fill must contain 0 or 1 action\n      tpa1 = { core_id, 1, GRAPHENE_100_PERCENT, 3600, false };\n      on_fill_1 = { tpa1, tpa1 };\n      BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ),\n                         fc::exception );\n      // Can propose\n      uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 );\n      propose( uop1 );\n\n      generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests OSO-related order updates, scenarios:\n/// * update an order which is not linked to another order and has no on_fill\n///   * add on_fill\n/// * update an order which is not linked to another order and has on_fill\n///   * update on_fill\n///   * remove on_fill\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_update_test_1 )\n{ try {\n\n      // Proceeds to the hard fork\n      generate_blocks( HARDFORK_CORE_2535_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type core_id;\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_sam_usd = 0;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n      };\n\n      // Sam sells CORE for USD without on_fill\n      const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id) );\n      BOOST_REQUIRE( sell_order1 );\n      limit_order_id_type sell_order1_id = sell_order1->get_id();\n\n      BOOST_CHECK( sell_order1_id(db).on_fill.empty() );\n      BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // Sam updates order with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa1 { core_id,    100, 10000,         3600, false };\n      vector<limit_order_auto_action> on_fill_1 { tpa1 };\n      update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 );\n\n      const auto& check_result_1 = [&]()\n      {\n         BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n         BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n         BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n         BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n         BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n         BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n         check_balances();\n      };\n\n      check_result_1();\n\n      generate_block();\n\n      check_result_1();\n\n      // Sam updates order with new on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa2 { usd_id,      10,  1000,         3800, true };\n      vector<limit_order_auto_action> on_fill_2 { tpa2 };\n      update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 );\n\n      const auto& check_result_2 = [&]()\n      {\n         BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n         BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id );\n         BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent );\n         BOOST_CHECK( action_s1.size_percent == tpa2.size_percent );\n         BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds );\n         BOOST_CHECK( action_s1.repeat == tpa2.repeat );\n\n         check_balances();\n      };\n\n      check_result_2();\n\n      generate_block();\n\n      check_result_2();\n\n      // Sam updates order without on_fill\n      update_limit_order( sell_order1_id, {}, asset(1) );\n      expected_balance_sam_core -= 1;\n\n      const auto& check_result_3 = [&]()\n      {\n         BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n         BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n         BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n         const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id );\n         BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent );\n         BOOST_CHECK( action_s1.size_percent == tpa2.size_percent );\n         BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds );\n         BOOST_CHECK( action_s1.repeat == tpa2.repeat );\n\n         check_balances();\n      };\n\n      check_result_3();\n\n      generate_block();\n\n      check_result_3();\n\n      // Sam updates order with an empty on_fill\n      vector<limit_order_auto_action> on_fill_3;\n      update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_3 );\n\n      const auto& check_result_4 = [&]()\n      {\n         BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n         BOOST_CHECK( sell_order1_id(db).on_fill.empty() );\n\n         check_balances();\n      };\n\n      check_result_4();\n\n      generate_block();\n\n      check_result_4();\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests OSO-related order updates, scenarios:\n/// * update an order which is linked to another order but has no on_fill\n///   * do not add on_fill, do not specify a new price\n///   * do not add on_fill, specify a new price but no change\n///   * do not add on_fill, update price\n///   * add on_fill\n/// * update an order which is linked to another order and has on_fill\n///   * do not specify new on_fill, do not specify a new price\n///   * do not specify new on_fill, specify a new price but no change\n///   * do not specify new on_fill, update price\n///   * remove on_fill\n///   * update on_fill\n///     * do not update spread_percent or repeat\n///     * update spread_percent\n///     * update repeat\nBOOST_AUTO_TEST_CASE( oso_take_profit_order_update_test_2 )\n{ try {\n\n      // Proceeds to the hard fork\n      generate_blocks( HARDFORK_CORE_2535_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      additional_asset_options_t usd_options;\n      usd_options.value.taker_fee_percent = 80; // 0.8% taker fee\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\", ted, charge_market_fee | white_list,\n                                                 price(asset(1, asset_id_type(1)), asset(1)),\n                                                 4, 30, usd_options ); // 0.3% maker fee\n      asset_id_type usd_id = usd.get_id();\n      asset_id_type core_id;\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n      issue_uia( ted, asset(init_amount, usd_id) );\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_sam_usd = 0;\n      int64_t expected_balance_ted_usd = init_amount;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n      };\n\n      check_balances();\n\n      // Sam sells CORE for USD with on_fill\n      //                                   fee_asset, spread,  size,   expiration, repeat\n      create_take_profit_order_action tpa1 { core_id,    100, 10000,         3600, false };\n      vector<limit_order_auto_action> on_fill_1 { tpa1 };\n\n      const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id),\n                                            time_point_sec::maximum(), price::unit_price(), on_fill_1 );\n      BOOST_REQUIRE( sell_order1 );\n      limit_order_id_type sell_order1_id = sell_order1->get_id();\n\n      limit_order_id_type last_order_id = sell_order1_id;\n\n      BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 10000 );\n      BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n      BOOST_CHECK( !sell_order1_id(db).take_profit_order_id );\n\n      BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n      BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n      {\n         const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n         BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n         BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n         BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n         BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n         BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n      }\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      // Ted buys CORE with USD without on_fill, partially fills Sam's order\n      const limit_order_object* buy_order1 = create_sell_order( ted_id, asset(1235, usd_id), asset(1000) );\n      last_order_id = last_order_id + 1;\n\n      // The buy order is smaller, it gets fully filled\n      BOOST_CHECK( !buy_order1 );\n      expected_balance_ted_core += 1000;\n      expected_balance_ted_usd -= 1235;\n\n      // The newly created take profit order is a buy order\n      last_order_id = last_order_id + 1;\n      limit_order_id_type buy_order2_id = last_order_id;\n\n      auto buy_order2_expiration = db.head_block_time() + 3600;\n\n      const auto& check_result_1 = [&]()\n      {\n         // The sell order is partially filled\n         BOOST_REQUIRE( db.find(sell_order1_id) );\n         // The take profit order\n         BOOST_REQUIRE( db.find(buy_order2_id) );\n         BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n         // The sell order gets 1235, market fee = round_down(1235 * 30 / 10000) = 3\n         BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); // 1235 - 3\n         // price = (12345 / 10000) / 101% = 12345 / 10100\n         // min to receive = round_up( 1232 * 10100 / 12345 ) = 1008\n         // updated price = 1232 / 1008\n         BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n         BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n         BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n         BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n\n         // The sell order is partially filled, pays 1000\n         BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); // 10000 - 1000\n         BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n         BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n         check_balances();\n      };\n\n      check_result_1();\n\n      generate_block();\n\n      check_result_1();\n\n      // Several passes to test different scenarios\n      auto bak_balance_sam_core = expected_balance_sam_core;\n      auto bak_balance_sam_usd = expected_balance_sam_usd;\n      for( size_t i = 0; i <= 10; ++i )\n      {\n         // Sam updates order\n         create_take_profit_order_action tpa2 = tpa1;\n         if( 0 == i )\n         {\n            // no on_fill, do not add on_fill, do not specify a new price\n            update_limit_order( buy_order2_id, {}, asset(-1, usd_id) );\n            expected_balance_sam_usd += 1;\n         }\n         else if( 1 == i )\n         {\n            // no on_fill, do not add on_fill, specify a new price but no change\n            update_limit_order( buy_order2_id, buy_order2_id(db).sell_price, {}, time_point_sec::maximum() );\n         }\n         else if( 2 == i )\n         {\n            // no on_fill, do not add on_fill, update price\n            auto new_price = buy_order2_id(db).sell_price;\n            new_price.quote.amount += 1;\n            update_limit_order( buy_order2_id, new_price );\n         }\n         else if( 3 == i )\n         {\n            // no on_fill, add on_fill\n            update_limit_order( buy_order2_id, {}, {}, {}, price::unit_price(), on_fill_1 );\n         }\n         else if( 4 == i )\n         {\n            // has on_fill, do not specify new on_fill, do not specify a new price\n            update_limit_order( sell_order1_id, {}, asset(1) );\n            expected_balance_sam_core -= 1;\n         }\n         else if( 5 == i )\n         {\n            // has on_fill, do not specify new on_fill, specify a new price but no change\n            update_limit_order( sell_order1_id, sell_order1_id(db).sell_price, asset(1) );\n            expected_balance_sam_core -= 1;\n         }\n         else if( 6 == i )\n         {\n            // has on_fill, do not specify new on_fill, update price\n            auto new_price = sell_order1_id(db).sell_price;\n            new_price.quote.amount += 1;\n            update_limit_order( sell_order1_id, new_price );\n         }\n         else if( 7 == i )\n         {\n            // has on_fill, specify an empty new on_fill (to remove it)\n            vector<limit_order_auto_action> on_fill_2;\n            update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 );\n         }\n         else if( 8 == i )\n         {\n            // has on_fill, specify a new on_fill, but no update to spread_percent or repeat\n            //     fee_asset, spread,  size,   expiration, repeat\n            tpa2 = {  usd_id,    100,  9000,         7200, false };\n            vector<limit_order_auto_action> on_fill_2 { tpa2 };\n            update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 );\n         }\n         else if( 9 == i )\n         {\n            // has on_fill, specify a new on_fill, update spread_percent\n            //     fee_asset, spread,  size,   expiration, repeat\n            tpa2 = { core_id,    101, 10000,         3600, false };\n            vector<limit_order_auto_action> on_fill_2 { tpa2 };\n            update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 );\n         }\n         else if( 10 == i )\n         {\n            // has on_fill, specify a new on_fill, update repeat\n            //     fee_asset, spread,  size,   expiration, repeat\n            tpa2 = { core_id,    100, 10000,         3600, true  };\n            vector<limit_order_auto_action> on_fill_2 { tpa2 };\n            update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 );\n         }\n\n         const auto& check_result_2 = [&]()\n         {\n            if( 0 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1231 ); // updated: 1232 - 1\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 1 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == time_point_sec::maximum() ); // updated\n               BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 2 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1009) ); // updated\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 3 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n\n               BOOST_REQUIRE_EQUAL( buy_order2_id(db).on_fill.size(), 1U ); // updated\n               BOOST_REQUIRE( buy_order2_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_b2 = buy_order2_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_b2.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_b2.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_b2.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_b2.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_b2.repeat == tpa1.repeat );\n            }\n            else if( 4 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9001 ); // updated: 9000 + 1\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 5 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9001 ); // updated: 9000 + 1\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 6 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12346, usd_id) ); // updated\n               BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa1.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa1.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 7 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared\n\n               BOOST_CHECK( sell_order1_id(db).on_fill.empty() ); // removed\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 8 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id );\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id ); // updated\n               BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa2.size_percent ); // updated\n               BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds ); // updated\n               BOOST_CHECK( action_s1.repeat == tpa2.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id );\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 9 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent ); // updated\n               BOOST_CHECK( action_s1.size_percent == tpa2.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa2.repeat );\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n            else if( 10 == i )\n            {\n               // The sell order\n               BOOST_REQUIRE( db.find(sell_order1_id) );\n               BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 );\n               BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) );\n               BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared\n\n               BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U );\n               BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type<create_take_profit_order_action>() );\n               const auto& action_s1 = sell_order1_id(db).on_fill.front().get<create_take_profit_order_action>();\n               BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id );\n               BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent );\n               BOOST_CHECK( action_s1.size_percent == tpa2.size_percent );\n               BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds );\n               BOOST_CHECK( action_s1.repeat == tpa2.repeat ); // updated\n\n               // The take profit order\n               BOOST_REQUIRE( db.find(buy_order2_id) );\n               BOOST_CHECK( buy_order2_id(db).seller == sam_id );\n               BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 );\n               BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) );\n               BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration );\n               BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared\n               BOOST_CHECK( buy_order2_id(db).on_fill.empty() );\n            }\n\n            check_balances();\n\n         };\n\n         check_result_2();\n\n         generate_block();\n\n         check_result_2();\n\n         // reset\n         db.pop_block();\n         expected_balance_sam_core = bak_balance_sam_core;\n         expected_balance_sam_usd = bak_balance_sam_usd;\n      }\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/p2p_node_tests.cpp",
    "content": "/*\n * Copyright (c) 2019 Bitshares Foundation, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include <memory>\n#include <thread>\n#include <iostream>\n#include <boost/test/unit_test.hpp>\n#include <boost/assign/list_of.hpp>\n\n#include <fc/thread/thread.hpp>\n#include <fc/asio.hpp>\n#include <fc/filesystem.hpp>\n#include <fc/time.hpp>\n\n#include <graphene/net/node.hpp>\n#include <graphene/net/peer_connection.hpp>\n#include <graphene/utilities/tempdir.hpp>\n\n#include <fc/io/raw.hpp>\n\n#include <fc/log/appender.hpp>\n#include <fc/log/console_appender.hpp>\n#include <fc/log/logger.hpp>\n#include <fc/log/logger_config.hpp>\n\n#include <graphene/protocol/fee_schedule.hpp>\n\n#include \"../../libraries/net/node_impl.hxx\"\n\n#include \"../common/genesis_file_util.hpp\"\n#include \"../common/utils.hpp\"\n\n/***\n * A peer connection delegate\n */\nclass test_delegate : public graphene::net::peer_connection_delegate\n{\npublic:\n   test_delegate()\n   {\n   }\n   void on_message( graphene::net::peer_connection* originating_peer,\n         const graphene::net::message& received_message ) override\n   {\n      ilog( \"on_message was called with ${msg}\", (\"msg\",received_message) );\n      try {\n         graphene::net::address_request_message m = received_message.as< graphene::net::address_request_message >();\n         std::shared_ptr<graphene::net::message> m_ptr = std::make_shared< graphene::net::message >( m );\n         last_message = m_ptr;\n      } catch (...)\n      {\n      }\n   }\n   void on_connection_closed( graphene::net::peer_connection* originating_peer ) override {}\n   graphene::net::message get_message_for_item( const graphene::net::item_id& item ) override\n   {\n      return graphene::net::message();\n   }\n   std::shared_ptr< graphene::net::message > last_message = nullptr;\n};\n\nclass test_peer : public graphene::net::peer_connection\n{\nprivate:\n   fc::ecc::private_key generated_private_key = fc::ecc::private_key::generate();\n   fc::optional<graphene::net::node_id_t> configured_node_id; // used to create hello_message\npublic:\n   std::vector<graphene::net::message> messages_received;\n\n   test_peer(graphene::net::peer_connection_delegate* del) : graphene::net::peer_connection(del)\n   {\n   }\n\n   void send_message( const graphene::net::message& message_to_send,\n         size_t message_send_time_field_offset = (size_t)-1 ) override\n   {\n      messages_received.push_back( message_to_send );\n   }\n\n   graphene::net::node_id_t get_public_key() const\n   {\n      return generated_private_key.get_public_key();\n   }\n\n   void set_configured_node_id( const graphene::net::node_id_t& id )\n   {\n      configured_node_id = id;\n   }\n\n   graphene::net::node_id_t get_configured_node_id() const\n   {\n      return configured_node_id ? *configured_node_id : get_public_key();\n   }\n\n   graphene::net::hello_message create_hello_message( const graphene::net::chain_id_type& chain_id )\n   {\n      graphene::net::hello_message hello;\n\n      hello.user_agent = \"Test peer\";\n\n      hello.inbound_address = get_remote_endpoint()->get_address();\n      hello.inbound_port = get_remote_endpoint()->port();\n      hello.outbound_port = hello.inbound_port;\n\n      hello.node_public_key = generated_private_key.get_public_key();\n\n      fc::sha256::encoder shared_secret_encoder;\n      fc::sha512 shared_secret = get_shared_secret(); // note: the shared secret is now just a zero-initialized array\n      shared_secret_encoder.write(shared_secret.data(), sizeof(shared_secret));\n      hello.signed_shared_secret = generated_private_key.sign_compact(shared_secret_encoder.result());\n\n      hello.chain_id = chain_id;\n\n      if( configured_node_id )\n      {\n         fc::mutable_variant_object user_data;\n         user_data[\"node_id\"] = fc::variant( *configured_node_id, 1 );\n         hello.user_data = user_data;\n      }\n\n      return hello;\n   }\n};\n\nstatic void test_closing_connection_message( const graphene::net::message& msg )\n{\n   BOOST_REQUIRE( msg.msg_type.value() == graphene::net::closing_connection_message::type );\n}\n\nstatic void test_connection_accepted_message( const graphene::net::message& msg )\n{\n   BOOST_REQUIRE( msg.msg_type.value() == graphene::net::connection_accepted_message::type );\n}\n\nstatic void test_connection_rejected_message( const graphene::net::message& msg )\n{\n   BOOST_REQUIRE( msg.msg_type.value() == graphene::net::connection_rejected_message::type );\n}\n\nstatic void test_address_message( const graphene::net::message& msg, std::size_t num_elements )\n{\n   BOOST_REQUIRE( msg.msg_type.value() == graphene::net::address_message::type );\n   const auto& addr_msg = msg.as<graphene::net::address_message>();\n   BOOST_CHECK_EQUAL( addr_msg.addresses.size(), num_elements );\n}\n\nclass test_node_delegate : public graphene::net::node_delegate\n{\nprivate:\n   graphene::net::chain_id_type chain_id = fc::sha256::hash(std::string(\"p2p_test_chain\"));\n   std::string node_name;\npublic:\n   explicit test_node_delegate( const std::string& name )\n   : node_name( name )\n   {\n   }\n   /****\n    * Implementation methods of node_delegate\n    */\n   bool has_item( const graphene::net::item_id& id ) { return false; }\n   bool handle_block( const graphene::net::block_message& blk_msg, bool sync_mode,\n         std::vector<fc::uint160_t>& contained_transaction_message_ids )\n      { return false; }\n   void handle_transaction( const graphene::net::trx_message& trx_msg )\n   {\n      ilog( \"${name} was asked to handle a transaction\", (\"name\", node_name) );\n   }\n   void handle_message( const graphene::net::message& message_to_process )\n   {\n      ilog( \"${name} received a message\", (\"name\",node_name) );\n   }\n   std::vector<graphene::net::item_hash_t> get_block_ids(\n         const std::vector<graphene::net::item_hash_t>& blockchain_synopsis,\n         uint32_t& remaining_item_count, uint32_t limit = 2000 )\n   {\n      return std::vector<graphene::net::item_hash_t>();\n   }\n   graphene::net::message get_item( const graphene::net::item_id& id )\n   {\n      ilog(\"${name} get_item was called\", (\"name\",node_name));\n      return graphene::net::message();\n   }\n   graphene::net::chain_id_type get_chain_id() const\n   {\n      ilog(\"${name} get_chain_id was called\", (\"name\",node_name));\n      return chain_id;\n   }\n   std::vector<graphene::net::item_hash_t> get_blockchain_synopsis(\n         const graphene::net::item_hash_t& reference_point,\n         uint32_t number_of_blocks_after_reference_point)\n   {\n      return std::vector<graphene::net::item_hash_t>();\n   }\n   void sync_status( uint32_t item_type, uint32_t item_count ) {}\n   void connection_count_changed( uint32_t c )\n   {\n      ilog( \"${name} connection_count_change was called\", (\"name\",node_name) );\n   }\n   uint32_t get_block_number( const graphene::net::item_hash_t& block_id )\n   {\n      ilog( \"${name} get_block_number was called\", (\"name\",node_name) );\n      return 0;\n   }\n   fc::time_point_sec get_block_time( const graphene::net::item_hash_t& block_id )\n   {\n      ilog( \"${name} get_block_time was called\", (\"name\",node_name) );\n      return fc::time_point_sec();\n   }\n   graphene::net::item_hash_t get_head_block_id() const\n   {\n      ilog( \"${name} get_head_block_id was called\", (\"name\",node_name) );\n      return graphene::net::item_hash_t();\n   }\n   uint32_t estimate_last_known_fork_from_git_revision_timestamp( uint32_t unix_timestamp ) const\n   {\n      return 0;\n   }\n   void error_encountered( const std::string& message, const fc::oexception& error )\n   {\n      ilog( \"${name} error_encountered was called. Message: ${msg}\", (\"name\",node_name)(\"msg\", message) );\n   }\n   uint8_t get_current_block_interval_in_seconds() const\n   {\n      ilog( \"${name} get_current_block_interval_in_seconds was called\", (\"name\",node_name) );\n      return 0;\n   }\n};\n\nclass test_node : public graphene::net::node\n{\npublic:\n   std::vector<std::shared_ptr<test_peer>> test_peers;\n\n   test_node( const std::string& name, const fc::path& config_dir, int port, int seed_port = -1 )\n         : node( name )\n   {\n      std::cout << \"test_node::test_node(): current thread=\" << uint64_t(&fc::thread::current()) << std::endl;\n      node_name = name;\n      load_configuration( config_dir );\n      set_node_delegate( std::make_shared<test_node_delegate>( name ) );\n   }\n   ~test_node()\n   {\n      my->get_thread()->async( [&]() {\n         this->test_peers.clear();\n      }).wait();\n   }\n\n   const graphene::net::peer_database& get_peer_db()\n   {\n      return my->_potential_peer_db;\n   }\n\n   const graphene::net::chain_id_type& get_chain_id() const\n   {\n      return my->_chain_id;\n   }\n\n   const graphene::net::node_id_t& get_node_id() const\n   {\n      return my->_node_id;\n   }\n\n   void start_fake_network_connect_loop()\n   {\n      this->my->get_thread()->async( [&]() {\n            my->_p2p_network_connect_loop_done\n                  = fc::schedule( [&]{}, fc::time_point::now() + fc::minutes(5), \"dummy_task\" );\n         }).wait();\n   }\n\n   void stop_fake_network_connect_loop()\n   {\n      try { my->_p2p_network_connect_loop_done.cancel(); } catch( ... ) { }\n   }\n\n   void on_message( graphene::net::peer_connection_ptr originating_peer,\n                    const graphene::net::message& received_message )\n   {\n      my->get_thread()->async( [&]() {\n         my->on_message( originating_peer.get(), received_message );\n      }).wait();\n   }\n\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> create_test_peer( std::string url )\n   {\n      return this->my->get_thread()->async( [&, &url = url](){\n            std::shared_ptr<test_delegate> d{};\n            auto peer = std::make_shared<test_peer>( d.get() );\n            peer->set_remote_endpoint( fc::optional<fc::ip::endpoint>( fc::ip::endpoint::from_string( url )) );\n            this->test_peers.push_back( peer );\n            return std::make_pair( d, peer );\n         }).wait();\n   }\n\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr>\n         create_peer_connection( std::string url )\n   {\n      return this->my->get_thread()->async( [&, &url = url](){\n            std::shared_ptr<test_delegate> d{};\n            graphene::net::peer_connection_ptr peer = graphene::net::peer_connection::make_shared( d.get() );\n            peer->set_remote_endpoint( fc::optional<fc::ip::endpoint>( fc::ip::endpoint::from_string( url )) );\n            peer->remote_inbound_endpoint = peer->get_remote_endpoint();\n            my->move_peer_to_active_list( peer );\n            return std::make_pair( d, peer );\n         }).wait();\n   }\n\n   graphene::net::hello_message create_hello_message_from_peer( std::shared_ptr<test_peer> peer_ptr,\n                                                                const graphene::net::chain_id_type& chain_id )\n   {\n      return this->my->get_thread()->async( [&](){\n            return peer_ptr->create_hello_message( chain_id );\n         }).wait();\n   }\n\nprivate:\n   std::string node_name;\n};\n\n// this class is to simulate that a test_node started to connect to the network and accepting connections\nclass fake_network_connect_guard\n{\nprivate:\n   test_node& _node;\npublic:\n   explicit fake_network_connect_guard( test_node& n) : _node(n) { _node.start_fake_network_connect_loop(); }\n   ~fake_network_connect_guard() { _node.stop_fake_network_connect_loop(); }\n};\n\nstruct p2p_fixture\n{\n   p2p_fixture()\n   {\n      // Configure logging : log p2p messages to console\n      fc::logging_config logging_config = fc::logging_config::default_config();\n\n      auto logger = logging_config.loggers.back(); // get a copy of the default logger\n      logger.name = \"p2p\";                         // update the name to p2p\n      logging_config.loggers.push_back( logger );  // add it to logging_config\n\n      fc::configure_logging(logging_config);\n   }\n   ~p2p_fixture()\n   {\n      // Restore default logging config\n      fc::configure_logging( fc::logging_config::default_config() );\n   }\n};\n\nBOOST_FIXTURE_TEST_SUITE( p2p_node_tests, p2p_fixture )\n\n/****\n * Testing normal hello message processing\n */\nBOOST_AUTO_TEST_CASE( hello_test )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that peer3 initialized the connection to node1\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::inbound;\n   // simulate that peer3 has a node_id that is different than its public key\n   graphene::net::node_id_t peer3_public_key = peer3_ptr->get_public_key();\n   graphene::net::node_id_t peer3_node_id = fc::ecc::private_key::generate().get_public_key();\n   peer3_ptr->set_configured_node_id( peer3_node_id );\n   BOOST_CHECK( peer3_node_id == peer3_ptr->get_configured_node_id() );\n   BOOST_CHECK( peer3_node_id != peer3_ptr->node_id );\n   BOOST_CHECK( peer3_public_key != peer3_ptr->node_public_key );\n   BOOST_CHECK( peer3_public_key != peer3_node_id );\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   req.inbound_address = fc::ip::address( \"9.9.9.9\" );\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // peer3 is accepted\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg = peer3_ptr->messages_received.front();\n   test_connection_accepted_message( msg );\n   BOOST_CHECK( test_peer::their_connection_state::connection_accepted == peer3_ptr->their_state );\n\n   // check data\n   BOOST_CHECK( peer3_node_id == peer3_ptr->node_id );\n   BOOST_CHECK( peer3_public_key == peer3_ptr->node_public_key );\n\n   // check that peer3 is added to the peer database because it is an inbound connection\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( peer_record.valid() );\n   }\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"9.9.9.9:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( peer_record.valid() );\n   }\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * Testing normal hello message processing when the peer is not accepting connections\n */\nBOOST_AUTO_TEST_CASE( hello_firewalled_peer_test )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that peer3 initialized the connection to node1\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::inbound;\n   // peer3 does not have a node_id that is different than its public key\n   graphene::net::node_id_t peer3_public_key = peer3_ptr->get_public_key();\n   graphene::net::node_id_t peer3_node_id = peer3_ptr->get_configured_node_id();\n   BOOST_CHECK( peer3_node_id != peer3_ptr->node_id );\n   BOOST_CHECK( peer3_public_key != peer3_ptr->node_public_key );\n   BOOST_CHECK( peer3_public_key == peer3_node_id );\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   req.inbound_address = fc::ip::address( \"9.9.9.9\" );\n   req.inbound_port = 0;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // peer3 is accepted\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg = peer3_ptr->messages_received.front();\n   test_connection_accepted_message( msg );\n   BOOST_CHECK( test_peer::their_connection_state::connection_accepted == peer3_ptr->their_state );\n   // we think peer3 is firewalled\n   BOOST_CHECK( graphene::net::firewalled_state::firewalled == peer3_ptr->is_firewalled );\n\n   // check data\n   BOOST_CHECK( peer3_node_id == peer3_ptr->node_id );\n   BOOST_CHECK( peer3_public_key == peer3_ptr->node_public_key );\n   BOOST_CHECK( peer3_public_key == peer3_node_id );\n\n   // check that peer3 is not added to the peer database because it is not accepting connections\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( !peer_record.valid() );\n   }\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"9.9.9.9:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( !peer_record.valid() );\n   }\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:0\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( !peer_record.valid() );\n   }\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"9.9.9.9:0\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( !peer_record.valid() );\n   }\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * If a peer sent us a hello message when we aren't accepting connections, the peer will be rejected.\n */\nBOOST_AUTO_TEST_CASE( hello_not_accepting_connections )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // Note: no fake_network_connect_guard here, by default the node is not accepting connections\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 initialized the connection to peer3\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::outbound;\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // peer3 is rejected\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg = peer3_ptr->messages_received.front();\n   test_connection_rejected_message( msg );\n   BOOST_CHECK( test_peer::their_connection_state::connection_rejected == peer3_ptr->their_state );\n\n   // check that peer3 is not added to the peer database because it is an outbound connection\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_CHECK( !peer_record.valid() );\n   }\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * If a peer sent us a hello message when we aren't expecting it, the peer will be disconnected.\n */\nBOOST_AUTO_TEST_CASE( hello_unexpected )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 got its hello request and accepted the connection\n   peer3_ptr->their_state = test_peer::their_connection_state::connection_accepted;\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // peer3 should not send hello so the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg = peer3_ptr->messages_received.front();\n   test_closing_connection_message( msg );\n\n   // peer3 request again\n   peer3_ptr->messages_received.clear();\n   node1.on_message( peer3_ptr, req );\n\n   // the request is ignored\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 0 );\n}\n\n/****\n * If we receive a hello from a node which is on a different chain,\n * disconnected and greatly delay the next attempt to reconnect\n */\nBOOST_AUTO_TEST_CASE( hello_from_different_chain )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 initialized the connection to peer3\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::outbound;\n   // simulate that peer3 is in node1's peer database\n   node1.add_seed_node( \"1.2.3.4:5678\" );\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_REQUIRE( peer_record.valid() );\n   }\n\n   const auto now = fc::time_point::now();\n\n   // peer3 send hello\n   graphene::net::chain_id_type chain_id = fc::sha256::hash(std::string(\"dummy_chain\"));\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, chain_id );\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_connection_rejected_message( msg1 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n   BOOST_CHECK( test_peer::their_connection_state::connection_rejected == peer3_ptr->their_state );\n   // check peer db\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_REQUIRE( peer_record.valid() );\n      BOOST_CHECK( peer_record->last_connection_disposition == graphene::net::last_connection_rejected );\n      BOOST_CHECK( peer_record->last_connection_attempt_time >= now );\n      BOOST_CHECK_GE( peer_record->number_of_failed_connection_attempts, 10 );\n   }\n}\n\n/****\n * If a peer sends us a hello message with an invalid signature, we reject and disconnect it.\n */\nBOOST_AUTO_TEST_CASE( hello_invalid_signature )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that peer3 initialized the connection to node1\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::inbound;\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   req.signed_shared_secret = fc::ecc::compact_signature();\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_connection_rejected_message( msg1 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n   BOOST_CHECK( test_peer::their_connection_state::connection_rejected == peer3_ptr->their_state );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * If a peer sends us a hello message with an empty node_id, we disconnect it.\n */\nBOOST_AUTO_TEST_CASE( hello_null_node_id )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that peer3 initialized the connection to node1\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::inbound;\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   fc::mutable_variant_object user_data = req.user_data;\n   user_data[\"node_id\"] = fc::variant( graphene::net::node_id_t(), 1 );\n   req.user_data = user_data;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg1 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg1 );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * If a peer sends us a hello message with a node_id identical to ours, we reject and disconnect it,\n * and greatly delay the next attempt to reconnect\n */\nBOOST_AUTO_TEST_CASE( hello_from_self )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 initialized the connection to peer3\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::outbound;\n   // simulate that peer3 is in node1's peer database\n   node1.add_seed_node( \"1.2.3.4:5678\" );\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_REQUIRE( peer_record.valid() );\n   }\n\n   const auto now = fc::time_point::now();\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   fc::mutable_variant_object user_data = req.user_data;\n   user_data[\"node_id\"] = fc::variant( node1.get_node_id(), 1 );\n   req.user_data = user_data;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_connection_rejected_message( msg1 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n   BOOST_CHECK( test_peer::their_connection_state::connection_rejected == peer3_ptr->their_state );\n   // check peer db\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      const auto peer_record = node1.get_peer_db().lookup_entry_for_endpoint( peer3_ep );\n      BOOST_REQUIRE( peer_record.valid() );\n      BOOST_CHECK( peer_record->last_connection_disposition == graphene::net::last_connection_rejected );\n      BOOST_CHECK( peer_record->last_connection_attempt_time >= now );\n      BOOST_CHECK_GE( peer_record->number_of_failed_connection_attempts, 10 );\n   }\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * If a peer sends us a hello message and we find it is already connected to us, we reject and disconnect it.\n */\nBOOST_AUTO_TEST_CASE( hello_already_connected )\n{ try {\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // get something in the list of connections\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_rslts\n         = node1.create_peer_connection( \"127.0.0.1:8090\" );\n   auto node2_ptr = node2_rslts.second;\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 initialized the connection to peer3\n   peer3_ptr->their_state = test_peer::their_connection_state::just_connected;\n   peer3_ptr->direction = graphene::net::peer_connection_direction::outbound;\n   // simulate that node2 and peer3 has the same public_key and node_id\n   node2_ptr->node_public_key = peer3_ptr->get_public_key();\n   node2_ptr->node_id = node2_ptr->node_public_key;\n\n   // peer3 send hello\n   graphene::net::hello_message req = node1.create_hello_message_from_peer( peer3_ptr, node1.get_chain_id() );\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_connection_rejected_message( msg1 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n   BOOST_CHECK( test_peer::their_connection_state::connection_rejected == peer3_ptr->their_state );\n\n   // check node2's data\n   {\n      fc::ip::endpoint peer3_ep = fc::ip::endpoint::from_string( std::string(\"1.2.3.4:5678\") );\n      BOOST_CHECK( *node2_ptr->remote_inbound_endpoint == peer3_ep );\n      BOOST_CHECK( graphene::net::firewalled_state::not_firewalled == node2_ptr->is_firewalled );\n      BOOST_REQUIRE_EQUAL( node2_ptr->additional_inbound_endpoints.size(), 1u );\n      BOOST_CHECK( *node2_ptr->additional_inbound_endpoints.begin() == peer3_ep );\n   }\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\n/****\n * If a node requests addresses without sending hello_message first, it will be disconnected.\n */\nBOOST_AUTO_TEST_CASE( address_request_without_hello )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // get something in the list of connections\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_rslts\n         = node1.create_peer_connection( \"127.0.0.1:8090\" );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n\n   // peer3 request addresses\n   graphene::net::address_request_message req;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // peer3 didn't send hello so the connection should be closed\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg = peer3_ptr->messages_received.front();\n   test_closing_connection_message( msg );\n\n   // peer3 request again\n   peer3_ptr->messages_received.clear();\n   node1.on_message( peer3_ptr, req );\n\n   // the request is ignored\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 0 );\n}\n\nBOOST_AUTO_TEST_CASE( set_nothing_advertise_algorithm )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // set advertise algorithm to \"nothing\"\n   node1.set_advertise_algorithm( \"nothing\" );\n\n   // get something in the list of connections\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_rslts\n         = node1.create_peer_connection( \"127.0.0.1:8090\" );\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 got its hello request and accepted the connection\n   peer3_ptr->their_state = test_peer::their_connection_state::connection_accepted;\n\n   // peer3 request addresses\n   graphene::net::address_request_message req;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // Node1 does not share the peer list with others\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 1U );\n   const auto& msg = peer3_ptr->messages_received.front();\n   test_address_message( msg, 0 );\n}\n\nBOOST_AUTO_TEST_CASE( advertise_list_test )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // set advertise algorithm to \"list\"\n   std::vector<std::string> advert_list = { \"127.0.0.1:8090\", \"2.3.4.55:1234\", \"bad_one\" };\n   node1.set_advertise_algorithm( \"list\", advert_list );\n\n   // add some connections, 1 of which appears on the advertise_list\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node1_rslts\n         = node1.create_peer_connection(\"127.0.0.1:8089\");\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_rslts\n         = node1.create_peer_connection(\"127.0.0.1:8090\");\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_reslts\n         = node1.create_peer_connection(\"127.0.0.1:8091\");\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 got its hello request and rejected the connection\n   peer3_ptr->their_state = test_peer::their_connection_state::connection_rejected;\n\n   // peer3 request addresses\n   graphene::net::address_request_message req;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // node1 replies with 1 address, then closes the connection\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_address_message( msg1, 1 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n}\n\nBOOST_AUTO_TEST_CASE( exclude_list )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // set advertise algorithm to \"exclude_list\"\n   std::vector<std::string> ex_list = { \"127.0.0.1:8090\", \"2.3.4.55:1234\" };\n   node1.set_advertise_algorithm( \"exclude_list\", ex_list );\n\n   // add some connections, 1 of which appears on the exclude_list\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node1_rslts\n         = node1.create_peer_connection(\"127.0.0.1:8089\");\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_rslts\n         = node1.create_peer_connection(\"127.0.0.1:8090\");\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_reslts\n         = node1.create_peer_connection(\"127.0.0.1:8091\");\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 got its hello request and rejected the connection\n   peer3_ptr->their_state = test_peer::their_connection_state::connection_rejected;\n\n   // peer3 request addresses\n   graphene::net::address_request_message req;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // node1 replies with 2 addresses, then closes the connection\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_address_message( msg1, 2 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n}\n\nBOOST_AUTO_TEST_CASE( advertising_all_test )\n{\n   // create a node (node1)\n   int node1_port = fc::network::get_available_port();\n   fc::temp_directory node1_dir( graphene::utilities::temp_directory_path() );\n   test_node node1( \"Node1\", node1_dir.path(), node1_port );\n   // simulate that node1 started to connect to the network and accepting connections\n   fake_network_connect_guard guard( node1 );\n\n   // set advertise algorithm to \"all\"\n   node1.set_advertise_algorithm( \"all\" );\n\n   // add some connections\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node1_rslts\n         = node1.create_peer_connection(\"127.0.0.1:8089\");\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_rslts\n         = node1.create_peer_connection(\"127.0.0.1:8090\");\n   std::pair<std::shared_ptr<test_delegate>, graphene::net::peer_connection_ptr> node2_reslts\n         = node1.create_peer_connection(\"127.0.0.1:8091\");\n\n   // a new peer (peer3)\n   std::pair<std::shared_ptr<test_delegate>, std::shared_ptr<test_peer>> peer3\n         = node1.create_test_peer( \"1.2.3.4:5678\" );\n   std::shared_ptr<test_peer> peer3_ptr = peer3.second;\n   // simulate that node1 got its hello request and rejected the connection\n   peer3_ptr->their_state = test_peer::their_connection_state::connection_rejected;\n\n   // peer3 request addresses\n   graphene::net::address_request_message req;\n   node1.on_message( peer3_ptr, req );\n\n   // check the results\n   // node1 replies with 3 addresses, then closes the connection\n   BOOST_REQUIRE_EQUAL( peer3_ptr->messages_received.size(), 2U );\n   const auto& msg1 = peer3_ptr->messages_received.front();\n   test_address_message( msg1, 3 );\n   const auto& msg2 = peer3_ptr->messages_received.back();\n   test_closing_connection_message( msg2 );\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/pob_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/ticket_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( pob_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( hardfork_time_test )\n{ try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_1270_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // Before the hard fork, unable to create a ticket or update a ticket, or do any of them with proposals\n      BOOST_CHECK_THROW( create_ticket( sam_id, lock_180_days, asset(1) ), fc::exception );\n      ticket_object tmp_ticket;\n      tmp_ticket.account = sam_id;\n      BOOST_CHECK_THROW( update_ticket( tmp_ticket, lock_360_days, asset(1) ), fc::exception );\n\n      ticket_create_operation cop = make_ticket_create_op( sam_id, lock_720_days, asset(2) );\n      BOOST_CHECK_THROW( propose( cop ), fc::exception );\n\n      ticket_update_operation uop = make_ticket_update_op( tmp_ticket, lock_720_days, {} );\n      BOOST_CHECK_THROW( propose( uop ), fc::exception );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_CASE( validation_and_basic_logic_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      auto fee_amount = 50 * GRAPHENE_BLOCKCHAIN_PRECISION;\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      // Able to propose\n      {\n         ticket_create_operation cop = make_ticket_create_op( sam_id, lock_720_days, asset(2) );\n         propose( cop );\n\n         ticket_object tmp_ticket;\n         tmp_ticket.account = sam_id;\n         ticket_update_operation uop = make_ticket_update_op( tmp_ticket, lock_720_days, {} );\n         propose( uop );\n      }\n\n      // Unable to create a ticket with invalid data\n      // zero amount\n      BOOST_CHECK_THROW( create_ticket( sam_id, lock_180_days, asset(0) ), fc::exception );\n      // negative amount\n      BOOST_CHECK_THROW( create_ticket( sam_id, lock_180_days, asset(-1) ), fc::exception );\n      // non-core asset\n      BOOST_CHECK_THROW( create_ticket( sam_id, lock_180_days, usd.amount(1) ), fc::exception );\n      // target type liquid\n      BOOST_CHECK_THROW( create_ticket( sam_id, liquid, asset(1) ), fc::exception );\n      // target type too big\n      BOOST_CHECK_THROW( create_ticket( sam_id, TICKET_TYPE_COUNT, asset(1) ), fc::exception );\n      // target type too big\n      {\n         ticket_create_operation cop = make_ticket_create_op( sam_id, lock_180_days, asset(1) );\n         cop.target_type = static_cast<uint8_t>(TICKET_TYPE_COUNT) + 1;\n         trx.operations.clear();\n         trx.operations.push_back( cop );\n\n         for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n         set_expiration( db, trx );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      }\n\n      // enable and update fee schedule\n      enable_fees();\n      db.modify(global_property_id_type()(db), [](global_property_object& gpo)\n      {\n         auto& fee_params = gpo.parameters.get_mutable_fees().parameters;\n\n         auto itr = fee_params.find( ticket_create_operation::fee_params_t() );\n         itr->get<ticket_create_operation::fee_params_t>().fee = 1;\n\n         itr = fee_params.find( ticket_update_operation::fee_params_t() );\n         itr->get<ticket_update_operation::fee_params_t>().fee = 2;\n      });\n\n      int64_t expected_balance = init_amount;\n\n      // Able to create a ticket with valid data\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_180_days, asset(1) );\n      BOOST_CHECK( tick_1.account == sam_id );\n      BOOST_CHECK( tick_1.target_type == lock_180_days );\n      BOOST_CHECK( tick_1.amount == asset(1) );\n      expected_balance -= (1 + fee_amount);\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n\n      const ticket_object& tick_2 = create_ticket( sam_id, lock_360_days, asset(1000) );\n      BOOST_CHECK( tick_2.account == sam_id );\n      BOOST_CHECK( tick_2.target_type == lock_360_days );\n      BOOST_CHECK( tick_2.amount == asset(1000) );\n      expected_balance -= (1000 + fee_amount);\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n\n      const ticket_object& tick_3 = create_ticket( sam_id, lock_720_days, asset(10) );\n      BOOST_CHECK( tick_3.account == sam_id );\n      BOOST_CHECK( tick_3.target_type == lock_720_days );\n      BOOST_CHECK( tick_3.amount == asset(10) );\n      expected_balance -= (10 + fee_amount);\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n\n      const ticket_object& tick_4 = create_ticket( sam_id, lock_forever, asset(100000) );\n      BOOST_CHECK( tick_4.account == sam_id );\n      BOOST_CHECK( tick_4.target_type == lock_forever );\n      BOOST_CHECK( tick_4.amount == asset(100000) );\n      expected_balance -= (100000 + fee_amount);\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n\n      // Unable to update a ticket with invalid data\n      // zero amount\n      BOOST_CHECK_THROW( update_ticket( tick_1, liquid, asset(0) ), fc::exception );\n      // negative amount\n      BOOST_CHECK_THROW( update_ticket( tick_1, liquid, asset(-1) ), fc::exception );\n      // non-core asset\n      BOOST_CHECK_THROW( update_ticket( tick_1, liquid, asset(1, usd.get_id()) ), fc::exception );\n      // too big amount\n      BOOST_CHECK_THROW( update_ticket( tick_1, liquid, asset(2) ), fc::exception );\n      // target type unchanged\n      BOOST_CHECK_THROW( update_ticket( tick_1, lock_180_days, {} ), fc::exception );\n      // target type unchanged\n      BOOST_CHECK_THROW( update_ticket( tick_1, lock_180_days, asset(1) ), fc::exception );\n      // target type too big\n      BOOST_CHECK_THROW( update_ticket( tick_1, TICKET_TYPE_COUNT, {} ), fc::exception );\n      {\n         // target type too big\n         ticket_update_operation uop = make_ticket_update_op( tick_1, liquid, asset(1) );\n         uop.target_type = static_cast<uint8_t>(TICKET_TYPE_COUNT) + 1;\n         trx.operations.clear();\n         trx.operations.push_back( uop );\n\n         for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o);\n         set_expiration( db, trx );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n         // account mismatch\n         uop.target_type = liquid;\n         uop.account = ted_id;\n         trx.operations.clear();\n         trx.operations.push_back( uop );\n         BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      }\n\n      ticket_id_type tick_1_id = tick_1.get_id();\n      ticket_id_type tick_2_id = tick_2.get_id();\n      ticket_id_type tick_4_id = tick_4.get_id();\n\n      // Update ticket 1 to liquid\n      generic_operation_result result = update_ticket( tick_1, liquid, asset(1) );\n      BOOST_REQUIRE( db.find( tick_1_id ) );\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).amount == asset(1) );\n      expected_balance -= fee_amount;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n      BOOST_REQUIRE_EQUAL( result.updated_objects.size(), 1u );\n      BOOST_CHECK( *result.updated_objects.begin() == tick_1_id );\n      BOOST_CHECK_EQUAL( result.removed_objects.size(), 0u );\n\n      // target type unchanged\n      BOOST_CHECK_THROW( update_ticket( tick_1, liquid, {} ), fc::exception );\n\n      // Update ticket 1 to lock_forever\n      result = update_ticket( tick_1, lock_forever, {} );\n      BOOST_REQUIRE( db.find( tick_1_id ) );\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).amount == asset(1) );\n      expected_balance -= fee_amount;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n      BOOST_REQUIRE_EQUAL( result.updated_objects.size(), 1u );\n      BOOST_CHECK( *result.updated_objects.begin() == tick_1_id );\n      BOOST_CHECK_EQUAL( result.removed_objects.size(), 0u );\n\n      // Update 3 CORE in ticket 2 to lock_180_days\n      result = update_ticket( tick_2, lock_180_days, asset(3) );\n      BOOST_REQUIRE( db.find( tick_2_id ) );\n      BOOST_CHECK( tick_2_id(db).account == sam_id );\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days ); // target type of the remaining ticket is unchanged\n      BOOST_CHECK( tick_2_id(db).amount == asset(1000-3) );\n      expected_balance -= fee_amount;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n      BOOST_REQUIRE_EQUAL( result.updated_objects.size(), 1u );\n      BOOST_CHECK( *result.updated_objects.begin() == tick_2_id );\n      BOOST_CHECK_EQUAL( result.removed_objects.size(), 0u );\n\n      ticket_id_type new_ticket_id { *result.new_objects.begin() };\n      BOOST_CHECK( new_ticket_id > tick_4_id );\n      BOOST_REQUIRE( db.find( new_ticket_id ) );\n      BOOST_CHECK( new_ticket_id(db).account == sam_id );\n      BOOST_CHECK( new_ticket_id(db).target_type == lock_180_days ); // target type of the new ticket is set\n      BOOST_CHECK( new_ticket_id(db).amount == asset(3) );\n\n      generate_block();\n\n      BOOST_REQUIRE( db.find( tick_1_id ) );\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).amount == asset(1) );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, expected_balance );\n\n      BOOST_REQUIRE( db.find( tick_2_id ) );\n      BOOST_CHECK( tick_2_id(db).account == sam_id );\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).amount == asset(1000-3) );\n\n      BOOST_REQUIRE( db.find( new_ticket_id ) );\n      BOOST_CHECK( new_ticket_id(db).account == sam_id );\n      BOOST_CHECK( new_ticket_id(db).target_type == lock_180_days );\n      BOOST_CHECK( new_ticket_id(db).amount == asset(3) );\n\n} FC_CAPTURE_LOG_AND_RETHROW( (0) ) }\n\nBOOST_AUTO_TEST_CASE( one_lock_180_ticket )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_180_days, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be stable now\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( one_lock_360_ticket )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_360_days, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be stable now\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( one_lock_720_ticket )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_720_days, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be stable now\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( one_lock_720_ticket_if_blocks_missed )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_720_days, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 800 days passed\n      generate_blocks( db.head_block_time() + fc::days(800) );\n      set_expiration( db, trx );\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( one_lock_forever_ticket )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_forever, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should have reached the target\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // no longer be able to update ticket\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( one_lock_forever_ticket_if_blocks_missed )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_forever, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 60 days passed\n      generate_blocks( db.head_block_time() + fc::days(60) );\n      set_expiration( db, trx );\n\n      // ticket should have reached the target\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // no longer be able to update ticket\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( one_lock_forever_ticket_if_too_many_blocks_missed )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_forever, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n\n      // 1060 days passed\n      generate_blocks( db.head_block_time() + fc::days(1060) );\n      set_expiration( db, trx );\n\n      // check ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // no longer be able to update ticket\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n\n      BOOST_REQUIRE( tick_1_id == ticket_id_type() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( upgrade_lock_180_ticket_to_360 )\n{ try {\n\n      INVOKE( one_lock_180_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_360_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be upgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( upgrade_lock_180_ticket_to_720 )\n{ try {\n\n      INVOKE( one_lock_180_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be upgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be upgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( upgrade_lock_180_ticket_to_forever )\n{ try {\n\n      INVOKE( one_lock_180_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_forever, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be upgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should have reached the target\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( upgrade_lock_360_ticket_to_720 )\n{ try {\n\n      INVOKE( one_lock_360_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be upgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( upgrade_lock_360_ticket_to_forever )\n{ try {\n\n      INVOKE( one_lock_360_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_forever, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be upgraded now, and still charging\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should have reached the target\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( upgrade_lock_720_ticket_to_forever )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_forever, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should have reached the target\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( withdraw_lock_180_ticket )\n{ try {\n\n      INVOKE( one_lock_180_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      bool has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 100 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( withdraw_lock_360_ticket )\n{ try {\n\n      INVOKE( one_lock_360_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded\n      bool has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 100 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( withdraw_lock_720_ticket )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 359 days passed\n      generate_blocks( db.head_block_time() + fc::days(359) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded\n      bool has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 100 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( withdraw_lock_720_ticket_if_blocks_missed )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 900 days passed\n      generate_blocks( db.head_block_time() + fc::days(900) );\n      set_expiration( db, trx );\n\n      // the ticket should be freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 100 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( downgrade_lock_720_ticket_to_180 )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), lock_180_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 359 days passed\n      generate_blocks( db.head_block_time() + fc::days(359) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded, and is stable now\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( downgrade_lock_720_ticket_to_360 )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), lock_360_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 359 days passed\n      generate_blocks( db.head_block_time() + fc::days(359) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded, and is stable now\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( downgrade_lock_360_ticket_to_180 )\n{ try {\n\n      INVOKE( one_lock_360_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), lock_180_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded, and is stable now\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( forever_ticket_auto_update )\n{ try {\n\n      INVOKE( one_lock_forever_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // can not update ticket\n      auto check_no_update = [&]()\n      {\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      };\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n      check_no_update();\n\n      for( int8_t i = 0; i < 4; ++i )\n      {\n         // 179 days passed\n         generate_blocks( db.head_block_time() + fc::days(179) );\n         set_expiration( db, trx );\n\n         // no change\n         BOOST_CHECK( tick_1_id(db).account == sam_id );\n         BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n         BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n         BOOST_CHECK( tick_1_id(db).status == withdrawing );\n         BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n         BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 * (4-i) );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n         check_no_update();\n\n         // 1 day passed\n         generate_blocks( db.head_block_time() + fc::days(1) );\n         set_expiration( db, trx );\n\n         // the ticket should have been updated\n         BOOST_CHECK( tick_1_id(db).account == sam_id );\n         BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n         BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n         if( i < 3 ) {\n            BOOST_CHECK( tick_1_id(db).status == withdrawing );\n         } else {\n            BOOST_CHECK( tick_1_id(db).status == stable );\n            BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n            BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n         }\n         BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n         BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 * (3-i) );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n         check_no_update();\n      }\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n      check_no_update();\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( forever_ticket_auto_update_if_blocks_missed )\n{ try {\n\n      INVOKE( one_lock_forever_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // can not update ticket\n      auto check_no_update = [&]()\n      {\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_forever, asset(100) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n         BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      };\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n      check_no_update();\n\n      // 750 days passed\n      generate_blocks( db.head_block_time() + fc::days(750) );\n      set_expiration( db, trx );\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n      check_no_update();\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( cancel_charging_from_liquid )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_360_days, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_360_days, asset(100) ), fc::exception );\n\n      // cancel charging\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 6 day passed\n      generate_blocks( db.head_block_time() + fc::days(6) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 100 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( cancel_charging_from_non_liquid )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_forever, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // cancel charging\n      result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 6 day passed\n      generate_blocks( db.head_block_time() + fc::days(6) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be stable now\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_720_days, asset(100) ), fc::exception );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( update_from_charging_to_withdrawing )\n{ try {\n\n      INVOKE( one_lock_720_ticket );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_forever, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // update target\n      result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), liquid, asset(100) ), fc::exception );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( update_from_withdrawing_to_charging_step_1 )\n{ try {\n\n      INVOKE( update_from_charging_to_withdrawing );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 114 days passed\n      generate_blocks( db.head_block_time() + fc::days(114) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // upgrade the ticket\n      auto result = update_ticket( tick_1_id(db), lock_forever, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time != time_point_sec::maximum() );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( update_from_withdrawing_to_charging_then_wait )\n{ try {\n\n      INVOKE( update_from_withdrawing_to_charging_step_1 );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time != time_point_sec::maximum() );\n      auto down_time = tick_1_id(db).next_type_downgrade_time;\n\n      // 14 days passed\n      generate_blocks( db.head_block_time() + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have upgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 8 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( update_from_withdrawing_to_charging_then_withdraw_again )\n{ try {\n\n      INVOKE( update_from_withdrawing_to_charging_step_1 );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // check the ticket\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time != time_point_sec::maximum() );\n      auto down_time = tick_1_id(db).next_type_downgrade_time;\n\n      // 6 days passed\n      generate_blocks( db.head_block_time() + fc::days(6) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n\n      // downgrade again\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      // current type should not change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == down_time );\n\n      // X days passed, now about to downgrade\n      generate_blocks( down_time - fc::days(1) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == down_time );\n\n      // upgrade again\n      result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n\n      // 6 days passed\n      generate_blocks( db.head_block_time() + fc::days(6) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 4 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n      // should have downgraded if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // downgrade again\n      result = update_ticket( tick_1_id(db), lock_180_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n      BOOST_CHECK_EQUAL( result.updated_objects.size(), 1u );\n\n      // the ticket should have downgraded\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time + 180*86400 );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == down_time + 180*86400 );\n\n      // X days passed, now about to downgrade\n      generate_blocks( down_time + 180*86400 - fc::days(1) );\n      set_expiration( db, trx );\n\n      // upgrade again\n      result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time + 180*86400 );\n\n      // 6 days passed\n      generate_blocks( db.head_block_time() + fc::days(6) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time + 180*86400 );\n      // should have downgraded if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // partially cancel charging\n      result = update_ticket( tick_1_id(db), lock_180_days, asset(10) );\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n      BOOST_REQUIRE_EQUAL( result.updated_objects.size(), 1u );\n      BOOST_CHECK_EQUAL( result.removed_objects.size(), 0u );\n      BOOST_CHECK( *result.updated_objects.begin() == tick_1_id );\n\n      // the new ticket is stable\n      ticket_id_type tick_2_id { *result.new_objects.begin() };\n\n      BOOST_REQUIRE( db.find( tick_2_id ) );\n      BOOST_CHECK( tick_2_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_2_id(db).status == stable );\n      BOOST_CHECK( tick_2_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 10 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // check the remainder\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 90 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time + 180*86400 );\n      // should have downgraded if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // generate a block\n      generate_block();\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 90 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time + 180*86400 );\n      // should have downgraded if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // cancel charging\n      result = update_ticket( tick_1_id(db), lock_180_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n      BOOST_CHECK_EQUAL( result.updated_objects.size(), 1u );\n\n      // the ticket is now stable\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 90 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // downgrade again\n      bool has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 90 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time != time_point_sec::maximum() );\n      down_time = tick_1_id(db).next_type_downgrade_time;\n\n      // X days passed, 30 days to downgrade\n      generate_blocks( down_time - fc::days(30) );\n      set_expiration( db, trx );\n\n      // upgrade again\n      result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 90 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n\n      // downgrade again\n      result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 90 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n\n      // X days passed, now about to free\n      generate_blocks( down_time - fc::days(1) );\n      set_expiration( db, trx );\n\n      // upgrade again\n      result = update_ticket( tick_1_id(db), lock_720_days, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 90 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n\n      // 6 days passed\n      generate_blocks( db.head_block_time() + fc::days(6) );\n      set_expiration( db, trx );\n\n      // no change\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(90) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 90 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n      // should have freed if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // partially cancel charging\n      result = update_ticket( tick_1_id(db), liquid, asset(15) );\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n      BOOST_REQUIRE_EQUAL( result.updated_objects.size(), 1u );\n      BOOST_REQUIRE_EQUAL( result.removed_objects.size(), 1u );\n      BOOST_CHECK( *result.updated_objects.begin() == tick_1_id );\n\n      // the new created ticket is freed already\n      ticket_id_type tick_3_id { *result.new_objects.begin() };\n      BOOST_CHECK( *result.removed_objects.begin() == tick_3_id );\n      BOOST_CHECK( !db.find( tick_3_id ) );\n\n      // check the remainder\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(75) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 75 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 15 );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n      // should have freed if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // generate a block\n      generate_block();\n\n      // no change\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(75) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, has_hf_2262 ? 0 : 75 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 15 );\n\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == down_time );\n      // should have freed if not changed to upgrade\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time < db.head_block_time() );\n\n      // cancel charging\n      result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n      BOOST_CHECK_EQUAL( result.updated_objects.size(), 0u );\n      BOOST_REQUIRE_EQUAL( result.removed_objects.size(), 1u );\n      BOOST_CHECK( *result.removed_objects.begin() == tick_1_id );\n\n      // the ticket is freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 90 );\n\n      // generate a block\n      generate_block();\n\n      // the ticket is freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 90 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( multiple_tickets )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n      int64_t ted_balance = init_amount;\n\n      // Sam create some tickets\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_180_days, asset(1) );\n      BOOST_CHECK( tick_1.account == sam_id );\n      BOOST_CHECK( tick_1.target_type == lock_180_days );\n      BOOST_CHECK( tick_1.current_type == liquid );\n      BOOST_CHECK( tick_1.status == charging );\n      BOOST_CHECK( tick_1.amount == asset(1) );\n      BOOST_CHECK_EQUAL( tick_1.value.value, 1 );\n      sam_balance -= 1;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      const ticket_object& tick_2 = create_ticket( sam_id, lock_360_days, asset(1000) );\n      BOOST_CHECK( tick_2.account == sam_id );\n      BOOST_CHECK( tick_2.target_type == lock_360_days );\n      BOOST_CHECK( tick_2.current_type == liquid );\n      BOOST_CHECK( tick_2.status == charging );\n      BOOST_CHECK( tick_2.amount == asset(1000) );\n      BOOST_CHECK_EQUAL( tick_2.value.value, 1000 );\n      sam_balance -= 1000;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      const ticket_object& tick_3 = create_ticket( sam_id, lock_720_days, asset(10) );\n      BOOST_CHECK( tick_3.account == sam_id );\n      BOOST_CHECK( tick_3.target_type == lock_720_days );\n      BOOST_CHECK( tick_3.current_type == liquid );\n      BOOST_CHECK( tick_3.status == charging );\n      BOOST_CHECK( tick_3.amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3.value.value, 10 );\n      sam_balance -= 10;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // Ted create a ticket\n      const ticket_object& tick_4 = create_ticket( ted_id, lock_forever, asset(100000) );\n      BOOST_CHECK( tick_4.account == ted_id );\n      BOOST_CHECK( tick_4.target_type == lock_forever );\n      BOOST_CHECK( tick_4.current_type == liquid );\n      BOOST_CHECK( tick_4.status == charging );\n      BOOST_CHECK( tick_4.amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4.value.value, 100000 );\n      ted_balance -= 100000;\n      BOOST_CHECK_EQUAL( db.get_balance( ted_id, asset_id_type() ).amount.value, ted_balance );\n\n      ticket_id_type tick_1_id = tick_1.get_id();\n      ticket_id_type tick_2_id = tick_2.get_id();\n      ticket_id_type tick_3_id = tick_3.get_id();\n      ticket_id_type tick_4_id = tick_4.get_id();\n\n      // one day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // Update ticket 1 to liquid\n      generic_operation_result result = update_ticket( tick_1_id(db), liquid, asset(1) );\n      BOOST_REQUIRE( db.find( tick_1_id ) );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(1) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 1 );\n\n      // Update 30 CORE in ticket 2 to lock_180_days\n      result = update_ticket( tick_2_id(db), lock_180_days, asset(30) );\n      BOOST_REQUIRE( db.find( tick_2_id ) );\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days ); // target type of the remaining ticket is unchanged\n      BOOST_CHECK( tick_2_id(db).current_type == liquid );\n      BOOST_CHECK( tick_2_id(db).status == charging );\n      BOOST_CHECK( tick_2_id(db).amount == asset(970) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 970 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_5_id { *result.new_objects.begin() };\n      BOOST_REQUIRE( db.find( tick_5_id ) );\n      BOOST_CHECK( tick_5_id(db).target_type == lock_180_days ); // target type of the new ticket is set\n      BOOST_CHECK( tick_5_id(db).current_type == liquid );\n      BOOST_CHECK( tick_5_id(db).status == charging );\n      BOOST_CHECK( tick_5_id(db).amount == asset(30) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 30 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == liquid );\n      BOOST_CHECK( tick_3_id(db).status == charging );\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == liquid );\n      BOOST_CHECK( tick_4_id(db).status == charging );\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 );\n\n      // 7 days passed\n      generate_blocks( db.head_block_time() + fc::days(7) );\n      set_expiration( db, trx );\n\n      // ticket 1 should have been freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n      sam_balance += 1;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == liquid );\n      BOOST_CHECK( tick_2_id(db).status == charging );\n      BOOST_CHECK( tick_2_id(db).amount == asset(970) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 970 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == liquid );\n      BOOST_CHECK( tick_3_id(db).status == charging );\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == liquid );\n      BOOST_CHECK( tick_4_id(db).status == charging );\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 );\n\n      BOOST_CHECK( tick_5_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).current_type == liquid );\n      BOOST_CHECK( tick_5_id(db).status == charging );\n      BOOST_CHECK( tick_5_id(db).amount == asset(30) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 30 );\n\n      // 7 days passed\n      generate_blocks( db.head_block_time() + fc::days(7) );\n      set_expiration( db, trx );\n\n      // ticket 2,3,4,5 should have upgraded\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_2_id(db).status == charging );\n      BOOST_CHECK( tick_2_id(db).amount == asset(970) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 970 * 2 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_3_id(db).status == charging );\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 * 2 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_4_id(db).status == charging );\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 * 2 );\n\n      BOOST_CHECK( tick_5_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).status == stable );\n      BOOST_CHECK( tick_5_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).amount == asset(30) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 30 * 2 );\n\n      // split ticket 2, cancel upgrade of some\n      result = update_ticket( tick_2_id(db), lock_180_days, asset(50) );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_2_id(db).status == charging ); // 15 days to finish\n      BOOST_CHECK( tick_2_id(db).amount == asset(920) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 920 * 2 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_6_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_6_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).status == withdrawing ); // 7 days to finish\n      BOOST_CHECK( tick_6_id(db).amount == asset(50) );\n      BOOST_CHECK_EQUAL( tick_6_id(db).value.value, 50 * 2 );\n\n      // split ticket 2 again, downgrade some\n      result = update_ticket( tick_2_id(db), liquid, asset(20) );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_2_id(db).status == charging ); // 15 days to finish\n      BOOST_CHECK( tick_2_id(db).amount == asset(900) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 900 * 2 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_7_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_7_id(db).target_type == liquid );\n      BOOST_CHECK( tick_7_id(db).current_type == liquid );\n      BOOST_CHECK( tick_7_id(db).status == withdrawing ); // 180 days to finish\n      BOOST_CHECK( tick_7_id(db).amount == asset(20) );\n      BOOST_CHECK_EQUAL( tick_7_id(db).value.value, 20 );\n\n      // 2 days passed\n      generate_blocks( db.head_block_time() + fc::days(2) );\n      set_expiration( db, trx );\n\n      // split ticket 5\n      result = update_ticket( tick_5_id(db), liquid, asset(12) );\n\n      BOOST_CHECK( tick_5_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).status == stable );\n      BOOST_CHECK( tick_5_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).amount == asset(18) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 18 * 2 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_51_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_51_id(db).target_type == liquid );\n      BOOST_CHECK( tick_51_id(db).current_type == liquid );\n      BOOST_CHECK( tick_51_id(db).status == withdrawing ); // 180 days to finish\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 );\n\n      // split ticket 5 again\n      result = update_ticket( tick_5_id(db), lock_forever, asset(13) );\n\n      BOOST_CHECK( tick_5_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).status == stable );\n      BOOST_CHECK( tick_5_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).amount == asset(5) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 5 * 2 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_52_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_52_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_52_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_52_id(db).status == charging ); // 15 days to next step\n      BOOST_CHECK( tick_52_id(db).amount == asset(13) );\n      BOOST_CHECK_EQUAL( tick_52_id(db).value.value, 13 * 2 );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // check ticket 5\n      BOOST_CHECK( tick_5_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_5_id(db).status == stable );\n      BOOST_CHECK( tick_5_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_5_id(db).amount == asset(5) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 5 * 2 );\n\n      // downgrade ticket 5\n      result = update_ticket( tick_5_id(db), liquid, {} );\n\n      BOOST_CHECK( tick_5_id(db).target_type == liquid );\n      BOOST_CHECK( tick_5_id(db).current_type == liquid );\n      BOOST_CHECK( tick_5_id(db).status == withdrawing ); // 180 days to finish\n      BOOST_CHECK( tick_5_id(db).amount == asset(5) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 5 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 0u );\n\n      // check ticket 51\n      BOOST_CHECK( tick_51_id(db).target_type == liquid );\n      BOOST_CHECK( tick_51_id(db).current_type == liquid );\n      BOOST_CHECK( tick_51_id(db).status == withdrawing ); // 179 days to finish\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 );\n\n      // cancel downgrading ticket 51\n      result = update_ticket( tick_51_id(db), lock_180_days, asset(12) );\n\n      BOOST_CHECK( tick_51_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).current_type == liquid );\n      BOOST_CHECK( tick_51_id(db).status == charging ); // 15 days to finish\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 0u );\n\n      // check ticket 7\n      BOOST_CHECK( tick_7_id(db).target_type == liquid );\n      BOOST_CHECK( tick_7_id(db).current_type == liquid );\n      BOOST_CHECK( tick_7_id(db).status == withdrawing ); // 177 days to finish\n      BOOST_CHECK( tick_7_id(db).amount == asset(20) );\n      BOOST_CHECK_EQUAL( tick_7_id(db).value.value, 20 );\n\n      // partly cancel downgrading ticket 7\n      result = update_ticket( tick_7_id(db), lock_180_days, asset(17) );\n\n      BOOST_CHECK( tick_7_id(db).target_type == liquid );\n      BOOST_CHECK( tick_7_id(db).current_type == liquid );\n      BOOST_CHECK( tick_7_id(db).status == withdrawing ); // 177 days to finish\n      BOOST_CHECK( tick_7_id(db).amount == asset(3) );\n      BOOST_CHECK_EQUAL( tick_7_id(db).value.value, 3 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_8_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_8_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).current_type == liquid );\n      BOOST_CHECK( tick_8_id(db).status == charging ); // 15 days to finish\n      BOOST_CHECK( tick_8_id(db).amount == asset(17) );\n      BOOST_CHECK_EQUAL( tick_8_id(db).value.value, 17 );\n\n      // downgrade some amount of ticket 6\n      result = update_ticket( tick_6_id(db), liquid, asset(23) );\n\n      BOOST_CHECK( tick_6_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).status == withdrawing ); // 4 days to finish\n      BOOST_CHECK( tick_6_id(db).amount == asset(27) );\n      BOOST_CHECK_EQUAL( tick_6_id(db).value.value, 27 * 2 );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_9_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_9_id(db).target_type == liquid );\n      BOOST_CHECK( tick_9_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_9_id(db).status == withdrawing ); // 4 days to next step\n      BOOST_CHECK( tick_9_id(db).amount == asset(23) );\n      BOOST_CHECK_EQUAL( tick_9_id(db).value.value, 23 * 2 );\n\n      // 4 days passed\n      generate_blocks( db.head_block_time() + fc::days(4) );\n      set_expiration( db, trx );\n\n      // ticket 6 should be stable now, ticket 9 should have entered the next step, others no change\n      BOOST_CHECK( tick_6_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).status == stable );\n      BOOST_CHECK( tick_6_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).amount == asset(27) );\n      BOOST_CHECK_EQUAL( tick_6_id(db).value.value, 27 * 2 );\n\n      BOOST_CHECK( tick_9_id(db).target_type == liquid );\n      BOOST_CHECK( tick_9_id(db).current_type == liquid );\n      BOOST_CHECK( tick_9_id(db).status == withdrawing ); // 180 days to next step\n      BOOST_CHECK( tick_9_id(db).amount == asset(23) );\n      BOOST_CHECK_EQUAL( tick_9_id(db).value.value, 23 );\n\n      BOOST_CHECK( tick_7_id(db).target_type == liquid );\n      BOOST_CHECK( tick_7_id(db).current_type == liquid );\n      BOOST_CHECK( tick_7_id(db).status == withdrawing ); // 173 days to finish\n      BOOST_CHECK( tick_7_id(db).amount == asset(3) );\n      BOOST_CHECK_EQUAL( tick_7_id(db).value.value, 3 );\n\n      BOOST_CHECK( tick_8_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).current_type == liquid );\n      BOOST_CHECK( tick_8_id(db).status == charging ); // 11 days to finish\n      BOOST_CHECK( tick_8_id(db).amount == asset(17) );\n      BOOST_CHECK_EQUAL( tick_8_id(db).value.value, 17 );\n\n      BOOST_CHECK( tick_5_id(db).target_type == liquid );\n      BOOST_CHECK( tick_5_id(db).current_type == liquid );\n      BOOST_CHECK( tick_5_id(db).status == withdrawing ); // 176 days to finish\n      BOOST_CHECK( tick_5_id(db).amount == asset(5) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 5 );\n\n      BOOST_CHECK( tick_51_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).current_type == liquid );\n      BOOST_CHECK( tick_51_id(db).status == charging ); // 11 days to finish\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 );\n\n      BOOST_CHECK( tick_52_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_52_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_52_id(db).status == charging ); // 10 days to finish\n      BOOST_CHECK( tick_52_id(db).amount == asset(13) );\n      BOOST_CHECK_EQUAL( tick_52_id(db).value.value, 13 * 2 );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_2_id(db).status == charging ); // 8 days to finish\n      BOOST_CHECK( tick_2_id(db).amount == asset(900) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 900 * 2 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_3_id(db).status == charging ); // 8 days to next step\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 * 2 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_4_id(db).status == charging ); // 8 days to next step\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 * 2 );\n\n      // 8 days passed\n      generate_blocks( db.head_block_time() + fc::days(8) );\n      set_expiration( db, trx );\n\n      // ticket 2,3,4 should be updated\n      BOOST_CHECK( tick_6_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).status == stable );\n      BOOST_CHECK( tick_6_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).amount == asset(27) );\n      BOOST_CHECK_EQUAL( tick_6_id(db).value.value, 27 * 2 );\n\n      BOOST_CHECK( tick_9_id(db).target_type == liquid );\n      BOOST_CHECK( tick_9_id(db).current_type == liquid );\n      BOOST_CHECK( tick_9_id(db).status == withdrawing ); // 172 days to finish\n      BOOST_CHECK( tick_9_id(db).amount == asset(23) );\n      BOOST_CHECK_EQUAL( tick_9_id(db).value.value, 23 );\n\n      BOOST_CHECK( tick_7_id(db).target_type == liquid );\n      BOOST_CHECK( tick_7_id(db).current_type == liquid );\n      BOOST_CHECK( tick_7_id(db).status == withdrawing ); // 165 days to finish\n      BOOST_CHECK( tick_7_id(db).amount == asset(3) );\n      BOOST_CHECK_EQUAL( tick_7_id(db).value.value, 3 );\n\n      BOOST_CHECK( tick_8_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).current_type == liquid );\n      BOOST_CHECK( tick_8_id(db).status == charging ); // 3 days to finish\n      BOOST_CHECK( tick_8_id(db).amount == asset(17) );\n      BOOST_CHECK_EQUAL( tick_8_id(db).value.value, 17 );\n\n      BOOST_CHECK( tick_5_id(db).target_type == liquid );\n      BOOST_CHECK( tick_5_id(db).current_type == liquid );\n      BOOST_CHECK( tick_5_id(db).status == withdrawing ); // 168 days to finish\n      BOOST_CHECK( tick_5_id(db).amount == asset(5) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 5 );\n\n      BOOST_CHECK( tick_51_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).current_type == liquid );\n      BOOST_CHECK( tick_51_id(db).status == charging ); // 3 days to finish\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 );\n\n      BOOST_CHECK( tick_52_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_52_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_52_id(db).status == charging ); // 2 days to finish\n      BOOST_CHECK( tick_52_id(db).amount == asset(13) );\n      BOOST_CHECK_EQUAL( tick_52_id(db).value.value, 13 * 2 );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).status == stable );\n      BOOST_CHECK( tick_2_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).amount == asset(900) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 900 * 4 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_3_id(db).status == charging ); // 15 days to next step\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 * 4 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_4_id(db).status == charging ); // 15 days to next step\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 * 4 );\n\n      // 3 days passed\n      generate_blocks( db.head_block_time() + fc::days(3) );\n      set_expiration( db, trx );\n\n      // ticket 8,51,52 should be updated\n      BOOST_CHECK( tick_6_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).status == stable );\n      BOOST_CHECK( tick_6_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).amount == asset(27) );\n      BOOST_CHECK_EQUAL( tick_6_id(db).value.value, 27 * 2 );\n\n      BOOST_CHECK( tick_9_id(db).target_type == liquid );\n      BOOST_CHECK( tick_9_id(db).current_type == liquid );\n      BOOST_CHECK( tick_9_id(db).status == withdrawing ); // 169 days to finish\n      BOOST_CHECK( tick_9_id(db).amount == asset(23) );\n      BOOST_CHECK_EQUAL( tick_9_id(db).value.value, 23 );\n\n      BOOST_CHECK( tick_7_id(db).target_type == liquid );\n      BOOST_CHECK( tick_7_id(db).current_type == liquid );\n      BOOST_CHECK( tick_7_id(db).status == withdrawing ); // 162 days to finish\n      BOOST_CHECK( tick_7_id(db).amount == asset(3) );\n      BOOST_CHECK_EQUAL( tick_7_id(db).value.value, 3 );\n\n      BOOST_CHECK( tick_8_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).status == stable );\n      BOOST_CHECK( tick_8_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_8_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_8_id(db).amount == asset(17) );\n      BOOST_CHECK_EQUAL( tick_8_id(db).value.value, 17 * 2 );\n\n      BOOST_CHECK( tick_5_id(db).target_type == liquid );\n      BOOST_CHECK( tick_5_id(db).current_type == liquid );\n      BOOST_CHECK( tick_5_id(db).status == withdrawing ); // 165 days to finish\n      BOOST_CHECK( tick_5_id(db).amount == asset(5) );\n      BOOST_CHECK_EQUAL( tick_5_id(db).value.value, 5 );\n\n      BOOST_CHECK( tick_51_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).status == stable );\n      BOOST_CHECK( tick_51_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_51_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 * 2 );\n\n      BOOST_CHECK( tick_52_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_52_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_52_id(db).status == charging ); // 14 days to next step\n      BOOST_CHECK( tick_52_id(db).amount == asset(13) );\n      BOOST_CHECK_EQUAL( tick_52_id(db).value.value, 13 * 4 );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).status == stable );\n      BOOST_CHECK( tick_2_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).amount == asset(900) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 900 * 4 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_3_id(db).status == charging ); // 12 days to finish\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 * 4 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_4_id(db).status == charging ); // 12 days to next step\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 * 4 );\n\n      // 170 days passed\n      generate_blocks( db.head_block_time() + fc::days(170) );\n      set_expiration( db, trx );\n\n      // check tickets\n      BOOST_CHECK( tick_6_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_6_id(db).status == stable );\n      BOOST_CHECK( tick_6_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_6_id(db).amount == asset(27) );\n      BOOST_CHECK_EQUAL( tick_6_id(db).value.value, 27 * 2 );\n\n      BOOST_CHECK( !db.find( tick_9_id ) );\n      BOOST_CHECK( !db.find( tick_7_id ) );\n\n      BOOST_CHECK( tick_8_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_8_id(db).status == stable );\n      BOOST_CHECK( tick_8_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_8_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_8_id(db).amount == asset(17) );\n      BOOST_CHECK_EQUAL( tick_8_id(db).value.value, 17 * 2 );\n\n      BOOST_CHECK( !db.find( tick_5_id ) );\n\n      BOOST_CHECK( tick_51_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_51_id(db).status == stable );\n      BOOST_CHECK( tick_51_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_51_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_51_id(db).amount == asset(12) );\n      BOOST_CHECK_EQUAL( tick_51_id(db).value.value, 12 * 2 );\n\n      BOOST_CHECK( tick_52_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_52_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_52_id(db).status == withdrawing ); // 39 days to next step\n      BOOST_CHECK( tick_52_id(db).amount == asset(13) );\n      BOOST_CHECK_EQUAL( tick_52_id(db).value.value, 13 * 8 );\n\n      BOOST_CHECK( tick_2_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).current_type == lock_360_days );\n      BOOST_CHECK( tick_2_id(db).status == stable );\n      BOOST_CHECK( tick_2_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_2_id(db).amount == asset(900) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 900 * 4 );\n\n      BOOST_CHECK( tick_3_id(db).target_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).current_type == lock_720_days );\n      BOOST_CHECK( tick_3_id(db).status == stable );\n      BOOST_CHECK( tick_3_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_3_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 10 * 8 );\n\n      BOOST_CHECK( tick_4_id(db).target_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).current_type == lock_forever );\n      BOOST_CHECK( tick_4_id(db).status == withdrawing ); // 37 days to next step\n      BOOST_CHECK( tick_4_id(db).amount == asset(100000) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 100000 * 8 );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( hf2262_test )\n{ try {\n\n      // Proceed to a time near the core-2262 hard fork.\n      // Note: only works if the maintenance interval is less than 14 days\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks( HARDFORK_CORE_2262_TIME - mi );\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      int64_t sam_balance = init_amount;\n\n      // create a ticket\n      const ticket_object& tick_1 = create_ticket( sam_id, lock_180_days, asset(100) );\n      ticket_id_type tick_1_id = tick_1.get_id();\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 );\n      sam_balance -= 100;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      auto create_time =  db.head_block_time();\n\n      // activate hf2262\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n\n      BOOST_REQUIRE( db.head_block_time() < create_time + fc::days(14) );\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 14 days passed\n      generate_blocks( create_time + fc::days(14) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // unable to update ticket if not to change target type\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, {} ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(1) ), fc::exception );\n      BOOST_CHECK_THROW( update_ticket( tick_1_id(db), lock_180_days, asset(100) ), fc::exception );\n\n      // split ticket 1, cancel some\n      auto result = update_ticket( tick_1_id(db), liquid, asset(6) );\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == charging );\n      BOOST_CHECK( tick_1_id(db).amount == asset(94) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_2_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_2_id(db).target_type == liquid );\n      BOOST_CHECK( tick_2_id(db).current_type == liquid );\n      BOOST_CHECK( tick_2_id(db).status == withdrawing );\n      BOOST_CHECK( tick_2_id(db).amount == asset(6) );\n      BOOST_CHECK_EQUAL( tick_2_id(db).value.value, 0 );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // ticket should be stable now\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(94) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 94 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // split ticket 1, downgrade some\n      result = update_ticket( tick_1_id(db), liquid, asset(10) );\n\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == stable );\n      BOOST_CHECK( tick_1_id(db).next_auto_update_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).next_type_downgrade_time == time_point_sec::maximum() );\n      BOOST_CHECK( tick_1_id(db).amount == asset(84) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 84 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      BOOST_REQUIRE_EQUAL( result.new_objects.size(), 1u );\n\n      ticket_id_type tick_3_id { *result.new_objects.begin() };\n      BOOST_CHECK( tick_3_id(db).target_type == liquid );\n      BOOST_CHECK( tick_3_id(db).current_type == liquid );\n      BOOST_CHECK( tick_3_id(db).status == withdrawing );\n      BOOST_CHECK( tick_3_id(db).amount == asset(10) );\n      BOOST_CHECK_EQUAL( tick_3_id(db).value.value, 0 );\n\n      // update ticket 1, downgrade all\n      update_ticket( tick_1_id(db), liquid, {} );\n\n      // check new data\n      BOOST_CHECK( tick_1_id(db).account == sam_id );\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(84) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // create a new ticket\n      const ticket_object& tick_4 = create_ticket( sam_id, lock_360_days, asset(200) );\n      ticket_id_type tick_4_id = tick_4.get_id();\n\n      BOOST_CHECK( tick_4_id(db).account == sam_id );\n      BOOST_CHECK( tick_4_id(db).target_type == lock_360_days );\n      BOOST_CHECK( tick_4_id(db).current_type == liquid );\n      BOOST_CHECK( tick_4_id(db).status == charging );\n      BOOST_CHECK( tick_4_id(db).amount == asset(200) );\n      BOOST_CHECK_EQUAL( tick_4_id(db).value.value, 0 );\n      sam_balance -= 200;\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( hf2262_auto_update_test )\n{ try {\n\n      INVOKE( one_lock_360_ticket );\n\n      // activate hf2262\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks( HARDFORK_CORE_2262_TIME - mi );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      GET_ACTOR( sam );\n\n      int64_t sam_balance = db.get_balance( sam_id, asset_id_type() ).amount.value;\n\n      ticket_id_type tick_1_id; // default value\n\n      // withdraw the ticket\n      auto result = update_ticket( tick_1_id(db), liquid, {} );\n      BOOST_CHECK_EQUAL( result.new_objects.size(), 0u );\n\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100 * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == lock_180_days );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 100  * 2 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should have downgraded\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 179 days passed\n      generate_blocks( db.head_block_time() + fc::days(179) );\n      set_expiration( db, trx );\n\n      // no change\n      BOOST_CHECK( tick_1_id(db).target_type == liquid );\n      BOOST_CHECK( tick_1_id(db).current_type == liquid );\n      BOOST_CHECK( tick_1_id(db).status == withdrawing );\n      BOOST_CHECK( tick_1_id(db).amount == asset(100) );\n      BOOST_CHECK_EQUAL( tick_1_id(db).value.value, 0 );\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance );\n\n      // 1 day passed\n      generate_blocks( db.head_block_time() + fc::days(1) );\n      set_expiration( db, trx );\n\n      // the ticket should be freed\n      BOOST_CHECK( !db.find( tick_1_id ) );\n\n      BOOST_CHECK_EQUAL( db.get_balance( sam_id, asset_id_type() ).amount.value, sam_balance + 100 );\n\n   } FC_LOG_AND_RETHROW()\n}\n\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/samet_fund_tests.cpp",
    "content": "/*\n * Copyright (c) 2021 Abit More, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common/database_fixture.hpp\"\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/samet_fund_object.hpp>\n#include <graphene/chain/proposal_object.hpp>\n\n#include <graphene/app/api.hpp>\n\n#include <boost/test/unit_test.hpp>\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( samet_fund_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( samet_fund_hardfork_time_test )\n{\n   try {\n\n      // Proceeds to a recent hard fork\n      generate_blocks( HARDFORK_CORE_2262_TIME );\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n\n      // Before the hard fork, unable to create a samet fund or transact against a samet fund,\n      // or do any of them with proposals\n      BOOST_CHECK_THROW( create_samet_fund( sam_id, core.get_id(), 10000, 100 ), fc::exception );\n\n      samet_fund_id_type tmp_sf_id;\n      BOOST_CHECK_THROW( delete_samet_fund( sam_id, tmp_sf_id ), fc::exception );\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, tmp_sf_id, core.amount(100), 200 ), fc::exception );\n      BOOST_CHECK_THROW( borrow_from_samet_fund( sam_id, tmp_sf_id, core.amount(100) ), fc::exception );\n      BOOST_CHECK_THROW( repay_to_samet_fund( sam_id, tmp_sf_id, core.amount(100), core.amount(100) ),\n                         fc::exception );\n\n      samet_fund_create_operation cop = make_samet_fund_create_op( sam_id, core.get_id(), 10000, 100 );\n      BOOST_CHECK_THROW( propose( cop ), fc::exception );\n\n      samet_fund_delete_operation dop = make_samet_fund_delete_op( sam_id, tmp_sf_id );\n      BOOST_CHECK_THROW( propose( dop ), fc::exception );\n\n      samet_fund_update_operation uop = make_samet_fund_update_op( sam_id, tmp_sf_id, core.amount(100), 200 );\n      BOOST_CHECK_THROW( propose( uop ), fc::exception );\n\n      samet_fund_borrow_operation bop = make_samet_fund_borrow_op( sam_id, tmp_sf_id, core.amount(100) );\n      BOOST_CHECK_THROW( propose( bop ), fc::exception );\n\n      samet_fund_repay_operation rop =\n                         make_samet_fund_repay_op( sam_id, tmp_sf_id, core.amount(100), core.amount(100) );\n      BOOST_CHECK_THROW( propose( rop ), fc::exception );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( samet_fund_crud_and_proposal_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2351_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted)(por));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = eur.id;\n         uop.issuer = sam_id;\n         uop.new_options = eur.options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Sam to the whitelist, but do not add others\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      asset_id_type no_asset_id( core.id + 100 );\n      BOOST_REQUIRE( !db.find( no_asset_id ) );\n\n      // Able to propose\n      {\n         samet_fund_create_operation cop = make_samet_fund_create_op( sam_id, core.get_id(), 10000, 100 );\n         propose( cop );\n\n         samet_fund_id_type tmp_sf_id;\n\n         samet_fund_delete_operation dop = make_samet_fund_delete_op( sam_id, tmp_sf_id );\n         propose( dop );\n\n         samet_fund_update_operation uop = make_samet_fund_update_op( sam_id, tmp_sf_id, core.amount(100), 200 );\n         propose( uop );\n\n         samet_fund_borrow_operation bop = make_samet_fund_borrow_op( sam_id, tmp_sf_id, core.amount(100) );\n         propose( bop );\n\n         samet_fund_repay_operation rop =\n                         make_samet_fund_repay_op( sam_id, tmp_sf_id, core.amount(100), core.amount(100) );\n         propose( rop );\n      }\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_sam_usd = init_amount;\n      int64_t expected_balance_sam_eur = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_ted_usd = init_amount;\n      int64_t expected_balance_ted_eur = init_amount;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur );\n      };\n\n      check_balances();\n\n      // Able to create samet funds with valid data\n      const samet_fund_object& sfo1 = create_samet_fund( sam_id, core.get_id(), 10000, 100u );\n      samet_fund_id_type sf1_id = sfo1.get_id();\n      BOOST_CHECK( sfo1.owner_account == sam_id );\n      BOOST_CHECK( sfo1.asset_type == core.id );\n      BOOST_CHECK( sfo1.balance == 10000 );\n      BOOST_CHECK( sfo1.fee_rate == 100u );\n      BOOST_CHECK( sfo1.unpaid_amount == 0 );\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      const samet_fund_object& sfo2 = create_samet_fund( ted_id, usd.get_id(), 1, 10000000u );\n      samet_fund_id_type sf2_id = sfo2.get_id();\n      BOOST_CHECK( sfo2.owner_account == ted_id );\n      BOOST_CHECK( sfo2.asset_type == usd.id );\n      BOOST_CHECK( sfo2.balance == 1 );\n      BOOST_CHECK( sfo2.fee_rate == 10000000u );\n      BOOST_CHECK( sfo2.unpaid_amount == 0 );\n\n      expected_balance_ted_usd -= 1;\n      check_balances();\n\n      const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur.get_id(), 10, 1u ); // Account is whitelisted\n      samet_fund_id_type sf3_id = sfo3.get_id();\n      BOOST_CHECK( sfo3.owner_account == sam_id );\n      BOOST_CHECK( sfo3.asset_type == eur_id );\n      BOOST_CHECK( sfo3.balance == 10 );\n      BOOST_CHECK( sfo3.fee_rate == 1u );\n      BOOST_CHECK( sfo3.unpaid_amount == 0 );\n\n      expected_balance_sam_eur -= 10;\n      check_balances();\n\n      // Unable to create a samet fund with invalid data\n      // Non-positive balance\n      BOOST_CHECK_THROW( create_samet_fund( sam_id, core.get_id(), -1, 100u ), fc::exception );\n      BOOST_CHECK_THROW( create_samet_fund( ted_id, usd.get_id(), 0, 10000000u ), fc::exception );\n      // Insufficient account balance\n      BOOST_CHECK_THROW( create_samet_fund( por_id, usd.get_id(), 1, 100u ), fc::exception );\n      // Nonexistent asset type\n      BOOST_CHECK_THROW( create_samet_fund( sam_id, no_asset_id, 1, 100u ), fc::exception );\n      // Account is not whitelisted\n      BOOST_CHECK_THROW( create_samet_fund( ted_id, eur.get_id(), 10, 1u ), fc::exception );\n\n      check_balances();\n\n      // Uable to update a fund with invalid data\n      // Changes nothing\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, {}, {} ), fc::exception );\n      // Zero delta\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(0), 10u ), fc::exception );\n      // Specified new fee rate but no change\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(1), sf1_id(db).fee_rate ), fc::exception );\n      // Fund owner mismatch\n      BOOST_CHECK_THROW( update_samet_fund( ted_id, sf1_id, asset(1), {} ), fc::exception );\n      // Asset type mismatch\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(1, usd_id), {} ), fc::exception );\n      // Trying to withdraw too much\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(-10000), {} ), fc::exception );\n      // Insufficient account balance\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(init_amount), {} ), fc::exception );\n\n      check_balances();\n\n      // Able to update a fund with valid data\n      // Only deposit\n      update_samet_fund( sam_id, sf1_id, asset(1), {} );\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10001 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 100u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core -= 1;\n      check_balances();\n\n      // Only update fee rate\n      update_samet_fund( sam_id, sf1_id, {}, 101u );\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10001 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 101u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      check_balances();\n\n      // Withdraw and update fee rate\n      update_samet_fund( sam_id, sf1_id, asset(-9999), 10u );\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 2 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core += 9999;\n      check_balances();\n\n      // Sam is able to delete his own fund\n      asset released = delete_samet_fund( sam_id, sf1_id );\n\n      BOOST_REQUIRE( !db.find( sf1_id ) );\n      BOOST_REQUIRE( db.find( sf2_id ) );\n      BOOST_REQUIRE( db.find( sf3_id ) );\n\n      BOOST_CHECK( released == asset( 2, core_id ) );\n\n      expected_balance_sam_core += 2;\n      check_balances();\n\n      // Unable to update a fund that does not exist\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(1), {} ), fc::exception );\n      // Unable to delete a fund that does not exist\n      BOOST_CHECK_THROW( delete_samet_fund( sam_id, sf1_id ), fc::exception );\n      // Unable to delete a fund that is not owned by him\n      BOOST_CHECK_THROW( delete_samet_fund( sam_id, sfo2.get_id() ), fc::exception );\n\n      BOOST_REQUIRE( !db.find( sf1_id ) );\n      BOOST_REQUIRE( db.find( sf2_id ) );\n      BOOST_REQUIRE( db.find( sf3_id ) );\n\n      check_balances();\n\n      {\n         // Add Ted to the whitelist and remove Sam\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = ted_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::no_listing;\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      // Sam is now unable to deposit to the fund\n      BOOST_CHECK_THROW( update_samet_fund( sam_id, sf3_id, asset(1, eur_id), {} ), fc::exception );\n\n      BOOST_CHECK( sf3_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf3_id(db).asset_type == eur_id );\n      BOOST_CHECK( sf3_id(db).balance == 10 );\n      BOOST_CHECK( sf3_id(db).fee_rate == 1u );\n      BOOST_CHECK( sf3_id(db).unpaid_amount == 0 );\n\n      check_balances();\n\n      // Sam is still able to withdraw from the fund\n      update_samet_fund( sam_id, sf3_id, asset(-1, eur_id), {} );\n      BOOST_CHECK( sf3_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf3_id(db).asset_type == eur_id );\n      BOOST_CHECK( sf3_id(db).balance == 9 );\n      BOOST_CHECK( sf3_id(db).fee_rate == 1u );\n      BOOST_CHECK( sf3_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_eur += 1;\n      check_balances();\n\n      // Sam is still able to update fee rate\n      update_samet_fund( sam_id, sf3_id, {}, 2u );\n      BOOST_CHECK( sf3_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf3_id(db).asset_type == eur_id );\n      BOOST_CHECK( sf3_id(db).balance == 9 );\n      BOOST_CHECK( sf3_id(db).fee_rate == 2u );\n      BOOST_CHECK( sf3_id(db).unpaid_amount == 0 );\n\n      check_balances();\n\n      // Sam is still able to delete the fund\n      released = delete_samet_fund( sam_id, sf3_id );\n      BOOST_REQUIRE( !db.find( sf3_id ) );\n\n      BOOST_CHECK( released == asset( 9, eur_id ) );\n\n      expected_balance_sam_eur += 9;\n      check_balances();\n\n      // Sam is unable to recreate the fund\n      BOOST_CHECK_THROW( create_samet_fund( sam_id, eur.get_id(), 10, 1u ), fc::exception );\n      check_balances();\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( samet_fund_borrow_repay_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2351_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted)(por));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      const asset_object& core = asset_id_type()(db);\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n      // Make a whitelist\n      {\n         BOOST_TEST_MESSAGE( \"Setting up whitelisting\" );\n         asset_update_operation uop;\n         uop.asset_to_update = eur.id;\n         uop.issuer = sam_id;\n         uop.new_options = eur.options;\n         // The whitelist is managed by Sam\n         uop.new_options.whitelist_authorities.insert(sam_id);\n         trx.operations.clear();\n         trx.operations.push_back(uop);\n         PUSH_TX( db, trx, ~0 );\n\n         // Upgrade Sam so that he can manage the whitelist\n         upgrade_to_lifetime_member( sam_id );\n\n         // Add Sam to the whitelist, but do not add others\n         account_whitelist_operation wop;\n         wop.authorizing_account = sam_id;\n         wop.account_to_list = sam_id;\n         wop.new_listing = account_whitelist_operation::white_listed;\n         trx.operations.clear();\n         trx.operations.push_back(wop);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      asset_id_type no_asset_id( core.id + 100 );\n      BOOST_REQUIRE( !db.find( no_asset_id ) );\n\n      int64_t expected_balance_sam_core = init_amount;\n      int64_t expected_balance_sam_usd = init_amount;\n      int64_t expected_balance_sam_eur = init_amount;\n      int64_t expected_balance_ted_core = init_amount;\n      int64_t expected_balance_ted_usd = init_amount;\n      int64_t expected_balance_ted_eur = init_amount;\n\n      const auto& check_balances = [&]() {\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd );\n         BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur );\n      };\n\n      check_balances();\n\n      // create samet funds\n      const samet_fund_object& sfo1 = create_samet_fund( sam_id, core.get_id(), 10000, 10000u ); // fee rate is 1%\n      samet_fund_id_type sf1_id = sfo1.get_id();\n\n      expected_balance_sam_core -= 10000;\n      check_balances();\n\n      const samet_fund_object& sfo2 = create_samet_fund( ted_id, usd.get_id(), 1, 10000000u ); // fee rate is 1000%\n      samet_fund_id_type sf2_id = sfo2.get_id();\n\n      expected_balance_ted_usd -= 1;\n      check_balances();\n\n      const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur.get_id(), 10, 1u ); // Account is whitelisted\n      samet_fund_id_type sf3_id = sfo3.get_id();\n\n      expected_balance_sam_eur -= 10;\n      check_balances();\n\n      // Unable to borrow without repayment\n      BOOST_CHECK_THROW( borrow_from_samet_fund( sam_id, sf1_id, asset(1) ), fc::exception );\n      // Unable to repay without borrowing\n      BOOST_CHECK_THROW( repay_to_samet_fund( sam_id, sf1_id, asset(1), asset(100) ), fc::exception );\n\n      // Valid : borrow and repay\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(1) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10001 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core -= 1;\n      check_balances();\n\n      // Valid : borrow multiple times and repay at once\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) );\n         auto bop2 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(2) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(3), asset(1) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(bop2);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10002 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core -= 1;\n      check_balances();\n\n      // Valid : borrow with one account and repay with another account\n      {\n         auto bop1 = make_samet_fund_borrow_op( ted_id, sf1_id, asset(5) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(5), asset(1) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10003 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_ted_core += 5;\n      expected_balance_sam_core -= 6;\n      check_balances();\n\n      // Valid : borrow at once, repay via multiple times\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(7) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(3), asset(1) );\n         auto rop2 = make_samet_fund_repay_op( ted_id, sf1_id, asset(4), asset(1) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         trx.operations.push_back(rop2);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10005 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core += 3;\n      expected_balance_ted_core -= 5;\n      check_balances();\n\n      // Valid : borrow from multiple funds and repay\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(7) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(3), asset(1) );\n         auto bop2 = make_samet_fund_borrow_op( ted_id, sf2_id, asset(1, usd_id) );\n         auto rop2 = make_samet_fund_repay_op( ted_id, sf1_id, asset(4), asset(1) );\n         auto rop3 = make_samet_fund_repay_op( sam_id, sf2_id, asset(1, usd_id), asset(10, usd_id) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         trx.operations.push_back(bop2);\n         trx.operations.push_back(rop2);\n         trx.operations.push_back(rop3);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10007 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      BOOST_CHECK( sf2_id(db).owner_account == ted_id );\n      BOOST_CHECK( sf2_id(db).asset_type == usd_id );\n      BOOST_CHECK( sf2_id(db).balance == 11 );\n      BOOST_CHECK( sf2_id(db).fee_rate == 10000000u );\n      BOOST_CHECK( sf2_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core += 3;\n      expected_balance_ted_core -= 5;\n      expected_balance_sam_usd -= 11;\n      expected_balance_ted_usd += 1;\n      check_balances();\n\n      // Valid : borrow and repay with more fee than enough\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(2) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == 10009 );\n      BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_core -= 2;\n      check_balances();\n\n      // Valid: account whitelisted by asset\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf3_id, asset(1, eur_id) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf3_id, asset(1, eur_id), asset(1, eur_id) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      BOOST_CHECK( sf3_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf3_id(db).asset_type == eur_id );\n      BOOST_CHECK( sf3_id(db).balance == 11 );\n      BOOST_CHECK( sf3_id(db).fee_rate == 1u );\n      BOOST_CHECK( sf3_id(db).unpaid_amount == 0 );\n\n      expected_balance_sam_eur -= 1;\n      check_balances();\n\n      // Invalid operations\n      {\n         // Borrow 0\n         auto bop = make_samet_fund_borrow_op( sam_id, sf1_id, asset(0) );\n         BOOST_CHECK_THROW( bop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( bop ), fc::exception );\n\n         // Borrow a negative amount\n         bop = make_samet_fund_borrow_op( sam_id, sf1_id, asset(-1) );\n         BOOST_CHECK_THROW( bop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( bop ), fc::exception );\n\n         // Repay 0\n         auto rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(0), asset(1) );\n         BOOST_CHECK_THROW( rop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( rop ), fc::exception );\n\n         // Repay a negative amount\n         rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(-1), asset(1) );\n         BOOST_CHECK_THROW( rop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( rop ), fc::exception );\n\n         // Repay with a negative fee\n         rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(-1) );\n         BOOST_CHECK_THROW( rop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( rop ), fc::exception );\n\n         // Repay amount and fee in different assets\n         rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(1, usd_id) );\n         BOOST_CHECK_THROW( rop.validate(), fc::exception );\n         BOOST_CHECK_THROW( propose( rop ), fc::exception );\n      }\n\n      // Valid : borrow all from a fund\n      auto expected_sf1_balance = sf1_id(db).balance;\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n\n         expected_sf1_balance += fund_fee;\n\n         BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n         BOOST_CHECK( sf1_id(db).asset_type == core.id );\n         BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n         BOOST_CHECK( sf1_id(db).fee_rate == 10000u );\n         BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n         expected_balance_sam_core -= fund_fee.value;\n         check_balances();\n      }\n\n      // Valid : update fund fee rate after borrowed\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto uop1 = make_samet_fund_update_op( sam_id, sf1_id, {}, 9999u ); // new fee rate is 0.9999%\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(uop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n\n         expected_sf1_balance += fund_fee;\n\n         BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n         BOOST_CHECK( sf1_id(db).asset_type == core.id );\n         BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n         BOOST_CHECK( sf1_id(db).fee_rate == 9999u );\n         BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n         expected_balance_sam_core -= fund_fee.value;\n         check_balances();\n\n         // Able to do the same via a proposal\n         auto cop = make_proposal_create_op( bop1, sam_id, 300, {} );\n         auto uop2 = make_samet_fund_update_op( sam_id, sf1_id, {}, 9998u ); // new fee rate is 0.9998%\n         cop.proposed_ops.emplace_back(uop2);\n         cop.proposed_ops.emplace_back(rop1);\n         trx.operations.clear();\n         trx.operations.push_back( cop );\n         processed_transaction ptx = PUSH_TX(db, trx, ~0);\n         const operation_result& op_result = ptx.operation_results.front();\n         proposal_id_type pid { op_result.get<object_id_type>() };\n\n         proposal_update_operation puo;\n         puo.proposal = pid;\n         puo.fee_paying_account = sam_id;\n         puo.active_approvals_to_add.emplace( sam_id );\n         trx.operations.clear();\n         trx.operations.push_back(puo);\n         PUSH_TX(db, trx, ~0);\n\n         BOOST_CHECK( !db.find(pid) );\n\n         expected_sf1_balance += fund_fee;\n\n         BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n         BOOST_CHECK( sf1_id(db).asset_type == core.id );\n         BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n         BOOST_CHECK( sf1_id(db).fee_rate == 9998u );\n         BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n         expected_balance_sam_core -= fund_fee.value;\n         check_balances();\n      }\n\n      vector<proposal_id_type> proposals;\n      auto make_proposal_from_trx = [&]() {\n         proposal_create_operation cop;\n         cop.fee_paying_account = sam_id;\n         cop.expiration_time = db.head_block_time() + 30;\n         cop.review_period_seconds = {};\n         for( auto& op : trx.operations )\n         {\n            cop.proposed_ops.emplace_back( op );\n         }\n         for( auto& o : cop.proposed_ops ) db.current_fee_schedule().set_fee(o.op);\n\n         trx.operations.clear();\n         trx.operations.push_back( cop );\n         processed_transaction ptx = PUSH_TX(db, trx, ~0);\n         const operation_result& op_result = ptx.operation_results.front();\n         proposal_id_type pid { op_result.get<object_id_type>() };\n         proposals.push_back( pid );\n      };\n\n      // Invalid : borrow more amount than available\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance + 1;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : borrow more amount than available\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance + 1;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow - 2) );\n         auto bop2 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(2) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(bop2);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : borrow asset type mismatch\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow, usd_id) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n\n         rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow, usd_id), asset(fund_fee, usd_id) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : repay asset type mismatch\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow, usd_id), asset(fund_fee, usd_id) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : repay less than borrowed\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow - 1), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : repay more than borrowed\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = (to_borrow + 1) / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow + 1), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n\n         // Invalid too if repaid more and borrow again\n         auto bop2 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         trx.operations.push_back(bop2);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : insufficient fund fee paid\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = ( to_borrow - 1 ) / 100;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : insufficient account balance to repay the debt\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( por_id, sf1_id, asset(to_borrow) );\n         auto rop1 = make_samet_fund_repay_op( por_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : update fund balance after borrowed\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto uop1 = make_samet_fund_update_op( sam_id, sf1_id, asset(1), {} );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(uop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n\n         auto uop2 = make_samet_fund_update_op( sam_id, sf1_id, asset(-1), {} );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(uop2);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid : delete fund after borrowed\n      {\n         auto balance = sf1_id(db).balance;\n         auto to_borrow = balance;\n         auto fund_fee = to_borrow / 100 + 1;\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) );\n         auto dop1 = make_samet_fund_delete_op( sam_id, sf1_id );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(dop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(dop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid: borrow account is not whitelisted by asset\n      {\n         auto bop1 = make_samet_fund_borrow_op( ted_id, sf3_id, asset(1, eur_id) );\n         auto rop1 = make_samet_fund_repay_op( sam_id, sf3_id, asset(1, eur_id), asset(1, eur_id) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      // Invalid: repay account is not whitelisted by asset\n      {\n         auto bop1 = make_samet_fund_borrow_op( sam_id, sf3_id, asset(1, eur_id) );\n         auto rop1 = make_samet_fund_repay_op( ted_id, sf3_id, asset(1, eur_id), asset(1, eur_id) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n         make_proposal_from_trx();\n      }\n\n      generate_block();\n\n      // Nothing changed\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n      BOOST_CHECK( sf1_id(db).fee_rate == 9998u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      check_balances();\n\n      // Approve the proposals\n      for( auto& pid : proposals )\n      {\n         auto& p = pid(db);\n         proposal_update_operation puo;\n         puo.proposal = pid;\n         puo.fee_paying_account = sam_id;\n         for(auto& req : p.required_active_approvals)\n            puo.active_approvals_to_add.emplace( req );\n         trx.operations.clear();\n         trx.operations.push_back(puo);\n         PUSH_TX(db, trx, ~0);\n\n         // Approved but failed to execute\n         BOOST_CHECK( pid(db).is_authorized_to_execute(db) );\n         BOOST_CHECK( !pid(db).fail_reason.empty() );\n\n         // Nothing changed\n         BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n         BOOST_CHECK( sf1_id(db).asset_type == core.id );\n         BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n         BOOST_CHECK( sf1_id(db).fee_rate == 9998u );\n         BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n         check_balances();\n      }\n\n      // Nothing changed\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n      BOOST_CHECK( sf1_id(db).fee_rate == 9998u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      check_balances();\n\n      // Time goes by\n      generate_blocks( db.head_block_time() + fc::seconds(300) );\n\n      // proposals expired\n      for( auto& pid : proposals )\n      {\n         BOOST_CHECK( !db.find(pid) );\n      }\n\n      // Nothing changed\n      BOOST_CHECK( sf1_id(db).owner_account == sam_id );\n      BOOST_CHECK( sf1_id(db).asset_type == core.id );\n      BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance );\n      BOOST_CHECK( sf1_id(db).fee_rate == 9998u );\n      BOOST_CHECK( sf1_id(db).unpaid_amount == 0 );\n\n      check_balances();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( samet_fund_apis_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2351_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      asset_id_type core_id;\n\n      const asset_object& usd = create_user_issued_asset( \"MYUSD\" );\n      asset_id_type usd_id = usd.get_id();\n      issue_uia( sam, usd.amount(init_amount) );\n      issue_uia( ted, usd.amount(init_amount) );\n\n      const asset_object& eur = create_user_issued_asset( \"MYEUR\", sam, white_list );\n      asset_id_type eur_id = eur.get_id();\n      issue_uia( sam, eur.amount(init_amount) );\n      issue_uia( ted, eur.amount(init_amount) );\n\n      // create samet funds\n      const samet_fund_object& sfo1 = create_samet_fund( sam_id, core_id, 10000, 10000u ); // fee rate is 1%\n      samet_fund_id_type sf1_id = sfo1.get_id();\n\n      const samet_fund_object& sfo2 = create_samet_fund( ted_id, usd_id, 1, 10000000u ); // fee rate is 1000%\n      samet_fund_id_type sf2_id = sfo2.get_id();\n\n      const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur_id, 10, 1u );\n      samet_fund_id_type sf3_id = sfo3.get_id();\n\n      const samet_fund_object& sfo4 = create_samet_fund( sam_id, eur_id, 10, 2u );\n      samet_fund_id_type sf4_id = sfo4.get_id();\n\n      const samet_fund_object& sfo5 = create_samet_fund( sam_id, usd_id, 100, 20u );\n      samet_fund_id_type sf5_id = sfo5.get_id();\n\n      const samet_fund_object& sfo6 = create_samet_fund( ted_id, usd_id, 1000, 200u );\n      samet_fund_id_type sf6_id = sfo6.get_id();\n\n      generate_block();\n\n      // Check database API\n      graphene::app::database_api db_api( db, &( app.get_options() ) );\n\n      // List all SameT Funds\n      auto funds = db_api.list_samet_funds();\n      BOOST_REQUIRE_EQUAL( funds.size(), 6u );\n      BOOST_CHECK( funds.front().id == sf1_id );\n      BOOST_CHECK( funds.back().id == sf6_id );\n\n      // Pagination : the first page\n      funds = db_api.list_samet_funds( 5 );\n      BOOST_REQUIRE_EQUAL( funds.size(), 5u );\n      BOOST_CHECK( funds.front().id == sf1_id );\n      BOOST_CHECK( funds.back().id == sf5_id );\n\n      // Pagination : the last page\n      funds = db_api.list_samet_funds( 5, sf3_id );\n      BOOST_REQUIRE_EQUAL( funds.size(), 4u );\n      BOOST_CHECK( funds.front().id == sf3_id );\n      BOOST_CHECK( funds.back().id == sf6_id );\n\n      // Limit too large\n      BOOST_CHECK_THROW( db_api.list_samet_funds( 102 ), fc::exception );\n\n      // Get all SameT Funds owned by Sam\n      funds = db_api.get_samet_funds_by_owner( \"sam\" );\n      BOOST_REQUIRE_EQUAL( funds.size(), 4u );\n      BOOST_CHECK( funds.front().id == sf1_id );\n      BOOST_CHECK( funds.back().id == sf5_id );\n\n      // Pagination : the first page\n      funds = db_api.get_samet_funds_by_owner( \"sam\", 3, {} );\n      BOOST_REQUIRE_EQUAL( funds.size(), 3u );\n      BOOST_CHECK( funds.front().id == sf1_id );\n      BOOST_CHECK( funds.back().id == sf4_id );\n\n      // Pagination : another page\n      funds = db_api.get_samet_funds_by_owner( \"sam\", 3, sf2_id );\n      BOOST_REQUIRE_EQUAL( funds.size(), 3u );\n      BOOST_CHECK( funds.front().id == sf3_id );\n      BOOST_CHECK( funds.back().id == sf5_id );\n\n      // Pagination : the first page of SameT Funds owned by Ted\n      funds = db_api.get_samet_funds_by_owner( string(\"1.2.\")+fc::to_string(ted_id.instance.value), 3 );\n      BOOST_REQUIRE_EQUAL( funds.size(), 2u );\n      BOOST_CHECK( funds.front().id == sf2_id );\n      BOOST_CHECK( funds.back().id == sf6_id );\n\n      // Nonexistent account\n      BOOST_CHECK_THROW( db_api.get_samet_funds_by_owner( \"nonexistent-account\" ), fc::exception );\n\n      // Limit too large\n      BOOST_CHECK_THROW( db_api.get_samet_funds_by_owner( \"ted\", 102 ), fc::exception );\n\n      // Get all SameT Funds whose asset type is USD\n      funds = db_api.get_samet_funds_by_asset( \"MYUSD\" );\n      BOOST_REQUIRE_EQUAL( funds.size(), 3u );\n      BOOST_CHECK( funds.front().id == sf2_id );\n      BOOST_CHECK( funds.back().id == sf6_id );\n\n      // Pagination : the first page\n      funds = db_api.get_samet_funds_by_asset( \"MYUSD\", 2 );\n      BOOST_REQUIRE_EQUAL( funds.size(), 2u );\n      BOOST_CHECK( funds.front().id == sf2_id );\n      BOOST_CHECK( funds.back().id == sf5_id );\n\n      // Pagination : another page\n      funds = db_api.get_samet_funds_by_asset( \"MYUSD\", 2, sf4_id );\n      BOOST_REQUIRE_EQUAL( funds.size(), 2u );\n      BOOST_CHECK( funds.front().id == sf5_id );\n      BOOST_CHECK( funds.back().id == sf6_id );\n\n      // Pagination : the first page of SameT Funds whose asset type is CORE\n      funds = db_api.get_samet_funds_by_asset( \"1.3.0\", 2, {} );\n      BOOST_REQUIRE_EQUAL( funds.size(), 1u );\n      BOOST_CHECK( funds.front().id == sf1_id );\n      BOOST_CHECK( funds.back().id == sf1_id );\n\n      // Nonexistent asset\n      BOOST_CHECK_THROW( db_api.get_samet_funds_by_asset( \"NOSUCHASSET\" ), fc::exception );\n\n      // Limit too large\n      BOOST_CHECK_THROW( db_api.get_samet_funds_by_asset( \"MYUSD\", 102 ), fc::exception );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( samet_fund_account_history_test )\n{ try {\n\n      // Pass the hard fork time\n      generate_blocks( HARDFORK_CORE_2351_TIME );\n      set_expiration( db, trx );\n\n      ACTORS((sam)(ted));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( ted, asset(init_amount) );\n\n      asset_id_type core_id;\n\n      // create samet funds\n      const samet_fund_object& sfo1 = create_samet_fund( sam_id, core_id, 10000, 10000u ); // fee rate is 1%\n      samet_fund_id_type sf1_id = sfo1.get_id();\n\n      generate_block();\n\n      // Check history API\n      graphene::app::history_api hist_api(app);\n\n      // Sam's last operation is fund creation\n      auto histories = hist_api.get_relative_account_history( \"sam\", 0, 1, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 1u );\n      BOOST_CHECK( histories[0].op.is_type<samet_fund_create_operation>() );\n\n      // Ted's last operation is transfer\n      histories = hist_api.get_relative_account_history( \"ted\", 0, 1, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 1u );\n      BOOST_CHECK( histories[0].op.is_type<transfer_operation>() );\n\n      // Ted borrow and repay\n      {\n         auto bop1 = make_samet_fund_borrow_op( ted_id, sf1_id, asset(1) );\n         auto rop1 = make_samet_fund_repay_op( ted_id, sf1_id, asset(1), asset(1) );\n         trx.operations.clear();\n         trx.operations.push_back(bop1);\n         trx.operations.push_back(rop1);\n         PUSH_TX( db, trx, ~0 );\n      }\n\n      generate_block();\n\n      // Sam's last 2 operations are Ted's borrowing and repayment\n      histories = hist_api.get_relative_account_history( \"sam\", 0, 2, 0 );\n      BOOST_REQUIRE_EQUAL( histories.size(), 2u );\n      BOOST_CHECK( histories[0].op.is_type<samet_fund_repay_operation>() );\n      BOOST_CHECK( histories[1].op.is_type<samet_fund_borrow_operation>() );\n\n      // Ted's last 2 operations are the same\n      auto histories_ted = hist_api.get_relative_account_history( \"ted\", 0, 2, 0 );\n      BOOST_REQUIRE_EQUAL( histories_ted.size(), 2u );\n      BOOST_CHECK( histories[0].id == histories_ted[0].id );\n      BOOST_CHECK( histories[1].id == histories_ted[1].id );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/serialization_tests.cpp",
    "content": "/*\n * Copyright (c) 2015 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n\n\n#include <fc/crypto/digest.hpp>\n#include <fc/crypto/elliptic.hpp>\n#include <fc/reflect/variant.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\n\nBOOST_FIXTURE_TEST_SUITE( operation_unit_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( serialization_raw_test )\n{\n   try {\n      make_account();\n      transfer_operation op;\n      op.from = account_id_type(1);\n      op.to = account_id_type(2);\n      op.amount = asset(100);\n      trx.operations.push_back( op );\n      auto packed = fc::raw::pack( trx );\n      signed_transaction unpacked = fc::raw::unpack<signed_transaction>(packed);\n      unpacked.validate();\n      BOOST_CHECK( digest(trx) == digest(unpacked) );\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\nBOOST_AUTO_TEST_CASE( serialization_json_test )\n{\n   try {\n      make_account();\n      transfer_operation op;\n      op.from = account_id_type(1);\n      op.to = account_id_type(2);\n      op.amount = asset(100);\n      trx.operations.push_back( op );\n      fc::variant packed(trx, GRAPHENE_MAX_NESTED_OBJECTS);\n      signed_transaction unpacked = packed.as<signed_transaction>( GRAPHENE_MAX_NESTED_OBJECTS );\n      unpacked.validate();\n      BOOST_CHECK( digest(trx) == digest(unpacked) );\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( json_tests )\n{\n   try {\n   auto var = fc::json::variants_from_string( \"10.6 \" );\n   var = fc::json::variants_from_string( \"10.5\" );\n   } catch ( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( extension_serialization_test )\n{\n   try\n   {\n      buyback_account_options bbo;\n      bbo.asset_to_buy = asset_id_type(1000);\n      bbo.asset_to_buy_issuer = account_id_type(2000);\n      bbo.markets.emplace( asset_id_type() );\n      bbo.markets.emplace( asset_id_type(777) );\n      account_create_operation create_op = make_account( \"rex\" );\n      create_op.registrar = account_id_type(1234);\n      create_op.extensions.value.buyback_options = bbo;\n\n      auto packed = fc::raw::pack( create_op );\n      account_create_operation unpacked = fc::raw::unpack<account_create_operation>(packed);\n\n      ilog( \"original: ${x}\", (\"x\", create_op) );\n      ilog( \"unpacked: ${x}\", (\"x\", unpacked) );\n   }\n   catch ( const fc::exception& e )\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/settle_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n\n#include <graphene/protocol/market.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( settle_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( settle_rounding_test )\n{\n   try {\n      // get around Graphene issue #615 feed expiration bug\n      generate_blocks(HARDFORK_615_TIME);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((paul)(michael)(rachel)(alice)(bob)(ted)(joe)(jim));\n\n      // create assets\n      const auto& bitusd = create_bitasset(\"USDBIT\", paul_id);\n      const auto& bitcny = create_bitasset(\"CNYBIT\", paul_id);\n      const auto& core   = asset_id_type()(db);\n      asset_id_type bitusd_id = bitusd.get_id();\n      asset_id_type bitcny_id = bitcny.get_id();\n      asset_id_type core_id = core.get_id();\n\n      // fund accounts\n      transfer(committee_account, michael_id, asset( 100000000 ) );\n      transfer(committee_account, paul_id, asset(10000000));\n      transfer(committee_account, alice_id, asset(10000000));\n      transfer(committee_account, bob_id, asset(10000000));\n      transfer(committee_account, jim_id, asset(10000000));\n\n      // add a feed to asset\n      update_feed_producers( bitusd, {paul.get_id()} );\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5);\n      publish_feed( bitusd, paul, current_feed );\n\n      // paul gets some bitusd\n      const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) );\n      call_order_id_type call_paul_id = call_paul.get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 );\n\n      // and transfer some to rachel\n      transfer(paul.get_id(), rachel.get_id(), asset(200, bitusd.get_id()));\n\n      BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0);\n      BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000);\n\n      // michael gets some bitusd\n      const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8));\n      call_order_id_type call_michael_id = call_michael.get_id();\n\n      // add settle order and check rounding issue\n      operation_result result = force_settle(rachel, bitusd.amount(4));\n\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196);\n      BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800);\n\n      BOOST_CHECK_EQUAL( 1000, call_paul.debt.value );\n      BOOST_CHECK_EQUAL( 100, call_paul.collateral.value );\n      BOOST_CHECK_EQUAL( 6, call_michael.debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael.collateral.value );\n\n      generate_blocks( db.head_block_time() + fc::hours(20) );\n      set_expiration( db, trx );\n\n      // default feed and settlement expires at the same time\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      // now yes expire settlement\n      generate_blocks( db.head_block_time() + fc::hours(6) );\n\n      // checks\n      BOOST_CHECK( !db.find( settle_id ) );\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); // rachel paid 4 usd and got nothing\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800);\n\n      BOOST_CHECK_EQUAL( 996, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1002 ); // 1000 + 6 - 4\n\n      // settle more and check rounding issue\n      // by default 20% of total supply can be settled per maintenance interval, here we test less than it\n      set_expiration( db, trx );\n      operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34));\n\n      force_settlement_id_type settle_id2 { *result2.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 162); // 196-34\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800);\n\n      BOOST_CHECK_EQUAL( 996, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value );\n\n      generate_blocks( db.head_block_time() + fc::hours(10) );\n      set_expiration( db, trx );\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      // now yes expire settlement\n      generate_blocks( db.head_block_time() + fc::hours(16) );\n      set_expiration( db, trx );\n\n      // checks\n      BOOST_CHECK( !db.find( settle_id2 ) );\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); // rachel got 1 core and paid 34 usd\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 162);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800);\n\n      BOOST_CHECK_EQUAL( 962, call_paul_id(db).debt.value ); // 996 - 34\n      BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value ); // 100 - 1\n      BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 968 ); // 1002 - 34\n\n      // prepare for more tests\n      transfer(paul_id, rachel_id, asset(300, bitusd_id));\n      borrow(michael_id(db), bitusd_id(db).amount(2), core_id(db).amount(3));\n\n      // settle even more and check rounding issue\n      // by default 20% of total supply can be settled per maintenance interval, here we test more than it\n      const operation_result result3 = force_settle(rachel_id(db), bitusd_id(db).amount(3));\n      const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434));\n      const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5));\n\n      force_settlement_id_type settle_id3 { *result3.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 );\n\n      force_settlement_id_type settle_id4 { *result4.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 );\n\n      force_settlement_id_type settle_id5 { *result5.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // 162 + 300 - 3 - 434 - 5\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8); // 6 + 2\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989); // 99999992 - 3\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); // 800 - 300\n\n      BOOST_CHECK_EQUAL( 962, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value ); // 6 + 2\n      BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value ); // 8 + 3\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 970 ); // 968 + 2\n\n      generate_blocks( db.head_block_time() + fc::hours(4) );\n      set_expiration( db, trx );\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // now yes expire settlement\n      generate_blocks( db.head_block_time() + fc::hours(22) );\n      set_expiration( db, trx );\n\n      // checks\n      // maximum amount that can be settled now is round_down(970 * 20%) = 194.\n      // settle_id3 (amount was 3) will be filled and get nothing.\n      // settle_id4 will pay 194 - 3 = 191 usd, will get round_down(191*5/101) = 9 core\n      BOOST_CHECK( !db.find( settle_id3 ) );\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 243 ); // 434 - 191\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); // 1 + 9\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 768, call_paul_id(db).debt.value ); // 962 - 3 - 191\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); // 99 - 9\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 776 ); // 970 - 3 - 191\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 194 ); // 3 + 191\n\n      generate_block();\n\n      // michael borrows more\n      set_expiration( db, trx );\n      borrow(michael_id(db), bitusd_id(db).amount(18), core_id(db).amount(200));\n\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 243 );\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26); // 8 + 18\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789); // 99999989 - 200\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 768, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value ); // 8 + 18\n      BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value ); // 11 + 200\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 794 ); // 776 + 18\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 194 );\n\n      generate_block();\n\n      // maximum amount that can be settled now is round_down((794+194) * 20%) = 197,\n      //   already settled 194, so 197 - 194 = 3 more usd can be settled,\n      //   so settle_id3 will pay 3 usd and get nothing\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 240 ); // 243 - 3\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 765, call_paul_id(db).debt.value ); // 768 - 3\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 791 ); // 794 - 3\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 197 ); // 194 + 3\n\n      // michael borrows a little more\n      set_expiration( db, trx );\n      borrow(michael_id(db), bitusd_id(db).amount(20), core_id(db).amount(20));\n\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 240 );\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); // 26 + 20\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); // 99999789 - 20\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 765, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); // 26 + 20\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); // 211 + 20\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 811 ); // 791 + 20\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 197 );\n\n      generate_block();\n\n      // maximum amount that can be settled now is round_down((811+197) * 20%) = 201,\n      //   already settled 197, so 201 - 197 = 4 more usd can be settled,\n      //   so settle_id4 will pay 4 usd and get nothing\n\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 236 ); // 240 - 4\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 761, call_paul_id(db).debt.value ); // 765 - 4\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 807 ); // 811 - 4\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 201 ); // 197 + 4\n\n      generate_block();\n\n      // jim borrow some cny\n      call_order_id_type call_jim_id = borrow(jim_id(db), bitcny_id(db).amount(2000), core_id(db).amount(2000))\n                                          ->get_id();\n\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 2000);\n\n      // jim transfer some cny to joe\n      transfer(jim_id, joe_id, asset(1500, bitcny_id));\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 1500);\n\n      generate_block();\n\n      // give ted some usd\n      transfer(paul_id, ted_id, asset(100, bitusd_id));\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 100); // new: 100\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); // 500 - 100\n\n      // ted settle\n      const operation_result result6 = force_settle(ted_id(db), bitusd_id(db).amount(20));\n      const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21));\n      const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22));\n\n      force_settlement_id_type settle_id6 { *result6.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 );\n\n      force_settlement_id_type settle_id7 { *result7.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 );\n\n      force_settlement_id_type settle_id8 { *result8.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 );\n\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); // 100 - 20 - 21 - 22\n\n      // joe settle\n      const operation_result result101 = force_settle(joe_id(db), bitcny_id(db).amount(100));\n      const operation_result result102 = force_settle(joe_id(db), bitcny_id(db).amount(1000));\n      const operation_result result103 = force_settle(joe_id(db), bitcny_id(db).amount(300));\n\n      force_settlement_id_type settle_id101 { *result101.get<extendable_operation_result>()\n                                                        .value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 );\n\n      force_settlement_id_type settle_id102 { *result102.get<extendable_operation_result>()\n                                                        .value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 );\n\n      force_settlement_id_type settle_id103 { *result103.get<extendable_operation_result>()\n                                                        .value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 );\n\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 100); // 1500 - 100 - 1000 - 300\n\n      generate_block();\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // get to another maintenance interval\n      generate_blocks( db.head_block_time() + fc::hours(22) );\n      set_expiration( db, trx );\n\n      // maximum amount that can be settled now is round_down(807 * 20%) = 161,\n      // settle_id4 will pay 161 usd, will get round_down(161*5/101) = 7 core\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 75 ); // 236 - 161\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n      BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); // no change since not expired\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 17); // 10 + 7\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37);\n\n      BOOST_CHECK_EQUAL( 600, call_paul_id(db).debt.value ); // 761 - 161\n      BOOST_CHECK_EQUAL( 83, call_paul_id(db).collateral.value ); // 90 - 7\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 646 ); // 807 - 161\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 161 ); // reset to 0, then 161 more\n\n      // current cny data\n      BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); // no change since not expired\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 100); // 1500 - 100 - 1000 - 300\n\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitcny_id(db).dynamic_data(db).current_supply.value, 2000 );\n      BOOST_CHECK_EQUAL( bitcny_id(db).bitasset_data(db).force_settled_volume.value, 0 );\n\n      // bob borrow some\n      const call_order_object& call_bob = *borrow( bob_id(db), bitusd_id(db).amount(19), core_id(db).amount(2) );\n      call_order_id_type call_bob_id = call_bob.get_id();\n\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998); // 10000000 - 2\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); // new\n\n      BOOST_CHECK_EQUAL( 19, call_bob_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 665 ); // 646 + 19\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 161 );\n\n      generate_block();\n\n      // maximum amount that can be settled now is round_down((665+161) * 20%) = 165,\n      // settle_id4 will pay 165-161=4 usd, will get nothing\n      // bob's call order will get partially settled since its collateral ratio is the lowest\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 71 ); // 75 - 4\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n      BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); // no change since not expired\n\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998);\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 17); // no change\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37);\n\n      BOOST_CHECK_EQUAL( 15, call_bob_id(db).debt.value ); // 19 - 4\n      BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value ); // no change\n      BOOST_CHECK_EQUAL( 600, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 83, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 661 ); // 665 - 4\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 165 ); // 161 + 4\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // generate some blocks\n      generate_blocks( db.head_block_time() + fc::hours(10) );\n      set_expiration( db, trx );\n\n      // check cny\n      // maximum amount that can be settled now is round_down(2000 * 20%) = 400,\n      //   settle_id101's remaining amount is 100, so it can be fully processed,\n      //      according to price 50 core / 101 cny, it will get 49 core and pay 100 cny;\n      //   settle_id102's remaining amount is 1000, so 400-100=300 cny will be processed,\n      //      according to price 50 core / 101 cny, it will get 148 core and pay 300 cny;\n      //   settle_id103 won't be processed since it's after settle_id102\n      BOOST_CHECK( !db.find( settle_id101 ) );\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 700 ); // 1000 - 300\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); // no change since it's after settle_id102\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 197); // 49 + 148\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 100);\n\n      BOOST_CHECK_EQUAL( 1600, call_jim_id(db).debt.value ); // 2000 - 100 - 300\n      BOOST_CHECK_EQUAL( 1803, call_jim_id(db).collateral.value ); // 2000 - 49 - 148\n\n      BOOST_CHECK_EQUAL( bitcny_id(db).dynamic_data(db).current_supply.value, 1600 );\n      BOOST_CHECK_EQUAL( bitcny_id(db).bitasset_data(db).force_settled_volume.value, 400 ); // 100 + 300\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // get to another maintenance interval\n      generate_blocks( db.head_block_time() + fc::hours(14) );\n      set_expiration( db, trx );\n\n      // maximum amount that can be settled now is round_down(661 * 20%) = 132,\n      //   settle_id4's remaining amount is 71,\n      //      firstly it will pay 15 usd to call_bob and get nothing,\n      //        call_bob will pay off all debt, so it will be closed and remaining collateral (2 core) will be returned;\n      //      then it will pay 71-15=56 usd to call_paul and get round_down(56*5/101) = 2 core;\n      //   settle_id5 (has 5 usd) will pay 5 usd and get nothing;\n      //   settle_id6 (has 20 usd) will pay 20 usd and get nothing;\n      //   settle_id7 (has 21 usd) will pay 21 usd and get 1 core;\n      //   settle_id8 (has 22 usd) will pay 15 usd and get nothing, since reached 132\n      BOOST_CHECK( !db.find( settle_id4 ) );\n      BOOST_CHECK( !db.find( settle_id5 ) );\n      BOOST_CHECK( !db.find( settle_id6 ) );\n      BOOST_CHECK( !db.find( settle_id7 ) );\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 7 ); // 22 - 15\n\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 10000000); // 9999998 + 2\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19); // 17 + 2\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 1); // 0 + 1\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37);\n\n      BOOST_CHECK( !db.find( call_bob_id ) );\n      BOOST_CHECK_EQUAL( 483, call_paul_id(db).debt.value ); // 600 - 56 - 5 - 20 - 21 - 15\n      BOOST_CHECK_EQUAL( 80, call_paul_id(db).collateral.value ); // 83 - 2 - 1\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 529 ); // 661 - 132\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 132 ); // reset to 0, then 132 more\n\n      // check cny\n      // maximum amount that can be settled now is round_down(1600 * 20%) = 320,\n      //   settle_id102's remaining amount is 700, so 320 cny will be processed,\n      //      according to price 50 core / 101 cny, it will get 158 core and pay 320 cny;\n      //   settle_id103 won't be processed since it's after settle_id102\n      BOOST_CHECK( !db.find( settle_id101 ) );\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 380 ); // 700 - 320\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); // no change since it's after settle_id102\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 355); // 197 + 158\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 100);\n\n      BOOST_CHECK_EQUAL( 1280, call_jim_id(db).debt.value ); // 1600 - 320\n      BOOST_CHECK_EQUAL( 1645, call_jim_id(db).collateral.value ); // 1803 - 158\n\n      BOOST_CHECK_EQUAL( bitcny_id(db).dynamic_data(db).current_supply.value, 1280 );\n      BOOST_CHECK_EQUAL( bitcny_id(db).bitasset_data(db).force_settled_volume.value, 320 ); // reset to 0, then 320\n\n      generate_block();\n\n      // Note: the scenario that a big settle order matching several smaller call orders,\n      //       and another scenario about force_settlement_offset_percent parameter,\n      //       are tested in force_settle_test in operation_test2.cpp.\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 )\n{\n   try {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n      if(hf2481)\n         generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n      else if(hf1270)\n         generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n      else\n         generate_blocks(HARDFORK_CORE_184_TIME - mi);\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((paul)(michael)(rachel)(alice)(bob)(ted)(joe)(jim));\n\n      // create assets\n      const auto& bitusd = create_bitasset(\"USDBIT\", paul_id);\n      const auto& bitcny = create_bitasset(\"CNYBIT\", paul_id);\n      const auto& core   = asset_id_type()(db);\n      asset_id_type bitusd_id = bitusd.get_id();\n      asset_id_type bitcny_id = bitcny.get_id();\n      asset_id_type core_id = core.get_id();\n\n      // fund accounts\n      transfer(committee_account, michael_id, asset( 100000000 ) );\n      transfer(committee_account, paul_id, asset(10000000));\n      transfer(committee_account, alice_id, asset(10000000));\n      transfer(committee_account, bob_id, asset(10000000));\n      transfer(committee_account, jim_id, asset(10000000));\n\n      // add a feed to asset\n      update_feed_producers( bitusd, {paul.get_id()} );\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5);\n      publish_feed( bitusd, paul, current_feed );\n\n      // paul gets some bitusd\n      const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) );\n      call_order_id_type call_paul_id = call_paul.get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 );\n\n      // and transfer some to rachel\n      transfer(paul.get_id(), rachel.get_id(), asset(200, bitusd.get_id()));\n\n      BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0);\n      BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000);\n\n      // michael gets some bitusd\n      const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8));\n      call_order_id_type call_michael_id = call_michael.get_id();\n\n      // add settle order and check rounding issue\n      const operation_result result = force_settle(rachel, bitusd.amount(4));\n\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel, core), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196);\n      BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800);\n\n      BOOST_CHECK_EQUAL( 1000, call_paul.debt.value );\n      BOOST_CHECK_EQUAL( 100, call_paul.collateral.value );\n      BOOST_CHECK_EQUAL( 6, call_michael.debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael.collateral.value );\n\n      generate_blocks( db.head_block_time() + fc::hours(20) );\n      set_expiration( db, trx );\n\n      // default feed and settlement expires at the same time\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      // now yes expire settlement\n      generate_blocks( db.head_block_time() + fc::hours(6) );\n\n      // checks\n      BOOST_CHECK( !db.find( settle_id ) );\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); // rachel's settle order is cancelled and he get refunded\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800);\n\n      BOOST_CHECK_EQUAL( 1000, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1006 ); // 1000 + 6\n\n      // settle more and check rounding issue\n      // by default 20% of total supply can be settled per maintenance interval, here we test less than it\n      set_expiration( db, trx );\n      const operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34));\n\n      force_settlement_id_type settle_id2 { *result2.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 166); // 200-34\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800);\n\n      BOOST_CHECK_EQUAL( 1000, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value );\n\n      generate_blocks( db.head_block_time() + fc::hours(10) );\n      set_expiration( db, trx );\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      // now yes expire settlement\n      generate_blocks( db.head_block_time() + fc::hours(16) );\n      set_expiration( db, trx );\n\n      // checks\n      BOOST_CHECK( !db.find( settle_id2 ) );\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); // rachel got 1 core\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 179); // paid 21 usd since 1 core worths a little more than 20 usd\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800);\n\n      BOOST_CHECK_EQUAL( 979, call_paul_id(db).debt.value ); // 1000 - 21\n      BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value ); // 100 - 1\n      BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 985 ); // 1006 - 21\n\n      // prepare for more tests\n      transfer(paul_id, rachel_id, asset(300, bitusd_id));\n      borrow(michael_id(db), bitusd_id(db).amount(2), core_id(db).amount(3));\n\n      // settle even more and check rounding issue\n      // by default 20% of total supply can be settled per maintenance interval, here we test more than it\n      const operation_result result3 = force_settle(rachel_id(db), bitusd_id(db).amount(3));\n      const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434));\n      const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5));\n\n      force_settlement_id_type settle_id3 { *result3.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 );\n\n      force_settlement_id_type settle_id4 { *result4.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 );\n\n      force_settlement_id_type settle_id5 { *result5.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 37); // 179 + 300 - 3 - 434 - 5\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8); // 6 + 2\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989); // 99999992 - 3\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); // 800 - 300\n\n      BOOST_CHECK_EQUAL( 979, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value ); // 6 + 2\n      BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value ); // 8 + 3\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 987 ); // 985 + 2\n\n      generate_blocks( db.head_block_time() + fc::hours(4) );\n      set_expiration( db, trx );\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // now yes expire settlement\n      generate_blocks( db.head_block_time() + fc::hours(22) );\n      set_expiration( db, trx );\n\n      // checks\n      // settle_id3 will be cancelled due to too small.\n      // maximum amount that can be settled now is round_down(987 * 20%) = 197,\n      //   according to price (101/5), the amount worths more than 9 core but less than 10 core, so 9 core will be settled,\n      //   and 9 core worths 181.5 usd, so rachel will pay 182 usd and get 9 core\n      BOOST_CHECK( !db.find( settle_id3 ) );\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 ); // 434 - 182\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); // 1 + 9\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // 37 + 3\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value ); // 979 - 182\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); // 99 - 9\n      BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 805 ); // 987 - 182\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 );\n\n      generate_block();\n\n      // michael borrows more\n      set_expiration( db, trx );\n      borrow(michael_id(db), bitusd_id(db).amount(18), core_id(db).amount(200));\n\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 );\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26); // 8 + 18\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789); // 99999989 - 200\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value ); // 8 + 18\n      BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value ); // 11 + 200\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 823 ); // 805 + 18\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 );\n\n      generate_block();\n\n      // maximum amount that can be settled now is round_down((823+182) * 20%) = 201,\n      //   already settled 182, so 201 - 182 = 19 more usd can be settled,\n      //   according to price (101/5), the amount worths less than 1 core,\n      //   so nothing will happen.\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 );\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 823 );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 );\n\n      // michael borrows a little more\n      set_expiration( db, trx );\n      borrow(michael_id(db), bitusd_id(db).amount(20), core_id(db).amount(20));\n\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 );\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); // 26 + 20\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); // 99999789 - 20\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); // 26 + 20\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); // 211 + 20\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 843 ); // 823 + 20\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 );\n\n      generate_block();\n\n      // maximum amount that can be settled now is round_down((843+182) * 20%) = 205,\n      //   already settled 182, so 205 - 182 = 23 more usd can be settled,\n      //   according to price (101/5), the amount worths more than 1 core but less than 2 core,\n      //   so settle order will fill 1 more core, since 1 core worth more than 20 usd but less than 21 usd,\n      //   so rachel will pay 21 usd and get 1 core\n\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 231 ); // 252 - 21\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 11); // 10 + 1\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // no change\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500);\n\n      BOOST_CHECK_EQUAL( 776, call_paul_id(db).debt.value ); // 797 - 21\n      BOOST_CHECK_EQUAL( 89, call_paul_id(db).collateral.value ); // 90 - 1\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 822 ); // 843 - 21\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 203 ); // 182 + 21\n\n      // jim borrow some cny\n      call_order_id_type call_jim_id = borrow(jim_id(db), bitcny_id(db).amount(2000), core_id(db).amount(2000))\n                                          ->get_id();\n\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 2000);\n\n      // jim transfer some cny to joe\n      transfer(jim_id, joe_id, asset(1500, bitcny_id));\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 1500);\n\n      generate_block();\n\n      // give ted some usd\n      transfer(paul_id, ted_id, asset(100, bitusd_id));\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 100); // new: 100\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); // 500 - 100\n\n      // ted settle\n      const operation_result result6 = force_settle(ted_id(db), bitusd_id(db).amount(20));\n      const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21));\n      const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22));\n\n      force_settlement_id_type settle_id6 { *result6.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 );\n\n      force_settlement_id_type settle_id7 { *result7.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 );\n\n      force_settlement_id_type settle_id8 { *result8.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 );\n\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); // 100 - 20 - 21 - 22\n\n      // joe settle\n      const operation_result result101 = force_settle(joe_id(db), bitcny_id(db).amount(100));\n      const operation_result result102 = force_settle(joe_id(db), bitcny_id(db).amount(1000));\n      const operation_result result103 = force_settle(joe_id(db), bitcny_id(db).amount(300));\n\n      force_settlement_id_type settle_id101 { *result101.get<extendable_operation_result>()\n                                                        .value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 );\n\n      force_settlement_id_type settle_id102 { *result102.get<extendable_operation_result>()\n                                                        .value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 );\n\n      force_settlement_id_type settle_id103 { *result103.get<extendable_operation_result>()\n                                                        .value.new_objects->begin() };\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 );\n\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 100); // 1500 - 100 - 1000 - 300\n\n      generate_block();\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // get to another maintenance interval\n      generate_blocks( db.head_block_time() + fc::hours(22) );\n      set_expiration( db, trx );\n\n      // maximum amount that can be settled now is round_down(822 * 20%) = 164,\n      //   according to price (101/5), the amount worths more than 8 core but less than 9 core,\n      //   so settle order will fill 8 more core, since 8 core worth more than 161 usd but less than 162 usd,\n      //   so rachel will pay 162 usd and get 8 core\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 69 ); // 231 - 162\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4\n      BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); // no change since not expired\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19); // 11 + 8\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // no change\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37);\n\n      BOOST_CHECK_EQUAL( 614, call_paul_id(db).debt.value ); // 776 - 162\n      BOOST_CHECK_EQUAL( 81, call_paul_id(db).collateral.value ); // 89 - 8\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 660 ); // 822 - 162\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 ); // reset to 0, then 162 more\n\n      // current cny data\n      BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 ); // no change since not expired\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); // no change since not expired\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 100); // 1500 - 100 - 1000 - 300\n\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2000, call_jim_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitcny_id(db).dynamic_data(db).current_supply.value, 2000 );\n      BOOST_CHECK_EQUAL( bitcny_id(db).bitasset_data(db).force_settled_volume.value, 0 );\n\n      // bob borrow some\n      const call_order_object& call_bob = *borrow( bob_id(db), bitusd_id(db).amount(19), core_id(db).amount(2) );\n      call_order_id_type call_bob_id = call_bob.get_id();\n\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998); // 10000000 - 2\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); // new\n\n      BOOST_CHECK_EQUAL( 19, call_bob_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 679 ); // 660 + 19\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 );\n\n      generate_block();\n\n      // maximum amount that can be settled now is round_down((679+162) * 20%) = 168,\n      //   already settled 162, so 168 - 162 = 6 more usd can be settled,\n      //   according to price (101/5), the amount worths less than 1 core,\n      //   so nothing will happen.\n      BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 69 );\n      BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 );\n      BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 );\n      BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 );\n      BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 );\n\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998);\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37);\n\n      BOOST_CHECK_EQUAL( 19, call_bob_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 614, call_paul_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 81, call_paul_id(db).collateral.value );\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 679 );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 );\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // generate some blocks\n      generate_blocks( db.head_block_time() + fc::hours(10) );\n      set_expiration( db, trx );\n\n      // check cny\n      // maximum amount that can be settled now is round_down(2000 * 20%) = 400,\n      //   settle_id101's remaining amount is 100, so it can be fully processed,\n      //      according to price 50 core / 101 cny, it will get 49 core and pay 99 cny, the rest (1 cny) will be refunded;\n      //   settle_id102's remaining amount is 1000, so 400-99=301 cny will be processed,\n      //      according to price 50 core / 101 cny, it will get 149 core and pay 301 cny;\n      //   settle_id103 won't be processed since it's after settle_id102\n      BOOST_CHECK( !db.find( settle_id101 ) );\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 699 ); // 1000 - 301\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); // no change since it's after settle_id102\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 198); // 49 + 149\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 101); // 100 + 1\n\n      BOOST_CHECK_EQUAL( 1600, call_jim_id(db).debt.value ); // 2000 - 99 - 301\n      BOOST_CHECK_EQUAL( 1802, call_jim_id(db).collateral.value ); // 2000 - 49 - 149\n\n      BOOST_CHECK_EQUAL( bitcny_id(db).dynamic_data(db).current_supply.value, 1600 );\n      BOOST_CHECK_EQUAL( bitcny_id(db).bitasset_data(db).force_settled_volume.value, 400 ); // 99 + 301\n\n      // adding new feed so we have valid price to exit\n      update_feed_producers( bitusd_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), alice_id(db), current_feed );\n\n      update_feed_producers( bitcny_id(db), {alice_id} );\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitcny_id(db).amount( 101 ) / core_id(db).amount(50);\n      publish_feed( bitcny_id(db), alice_id(db), current_feed );\n\n      // get to another maintenance interval\n      generate_blocks( db.head_block_time() + fc::hours(14) );\n      set_expiration( db, trx );\n\n      // maximum amount that can be settled now is round_down(679 * 20%) = 135,\n      //   settle_id4's remaining amount is 69, so it can be fully processed:\n      //     firstly call_bob will be matched, since it owes only 19 usd which worths less than 1 core,\n      //       it will pay 1 core, and the rest (2-1=1 core) will be returned, short position will be closed;\n      //     then call_paul will be matched,\n      //       according to price (101/5), the amount (69-19=50 usd) worths more than 2 core but less than 3 core,\n      //       so settle_id4 will get 2 more core, since 2 core worth more than 40 usd but less than 41 usd,\n      //       call_rachel will pay 41 usd and get 2 core, the rest (50-41=9 usd) will be returned due to too small.\n      //   settle_id5 (has 5 usd) will be cancelled due to too small;\n      //   settle_id6 (has 20 usd) will be cancelled as well due to too small;\n      //   settle_id7 (has 21 usd) will be filled and get 1 core, since it worths more than 1 core; but no more fund can be returned;\n      //   settle_id8 (has 22 usd) will be filled and get 1 core, and 1 usd will be returned.\n      BOOST_CHECK( !db.find( settle_id4 ) );\n      BOOST_CHECK( !db.find( settle_id5 ) );\n      BOOST_CHECK( !db.find( settle_id6 ) );\n      BOOST_CHECK( !db.find( settle_id7 ) );\n      BOOST_CHECK( !db.find( settle_id8 ) );\n\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999999); // 9999998 + 1\n      BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 22); // 19 + 1 + 2\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 54); // 40 + 9 + 5\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400);\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 2); // 0 + 1 + 1\n      BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 58); // 37 + 20 + 1\n\n      BOOST_CHECK( !db.find( call_bob_id ) );\n      BOOST_CHECK_EQUAL( 531, call_paul_id(db).debt.value ); // 614 - 41 - 21 - 21\n      BOOST_CHECK_EQUAL( 77, call_paul_id(db).collateral.value ); // 81 - 2 - 1 - 1\n      BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value );\n      BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value );\n\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 577 ); // 679 - 19 - 41 - 21 - 21\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 102 ); // reset to 0, then 19 + 41 + 21 + 21\n\n      // check cny\n      // maximum amount that can be settled now is round_down(1600 * 20%) = 320,\n      //   settle_id102's remaining amount is 699, so 320 cny will be processed,\n      //      according to price 50 core / 101 cny, it will get 158 core and pay 320 cny;\n      //   settle_id103 won't be processed since it's after settle_id102\n      BOOST_CHECK( !db.find( settle_id101 ) );\n      BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 379 ); // 699 - 320\n      BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); // no change since it's after settle_id102\n\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), core_id(db)), 9998000);\n      BOOST_CHECK_EQUAL(get_balance(jim_id(db), bitcny_id(db)), 500);\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 356); // 198 + 158\n      BOOST_CHECK_EQUAL(get_balance(joe_id(db), bitcny_id(db)), 101);\n\n      BOOST_CHECK_EQUAL( 1280, call_jim_id(db).debt.value ); // 1600 - 320\n      BOOST_CHECK_EQUAL( 1644, call_jim_id(db).collateral.value ); // 1802 - 158\n\n      BOOST_CHECK_EQUAL( bitcny_id(db).dynamic_data(db).current_supply.value, 1280 );\n      BOOST_CHECK_EQUAL( bitcny_id(db).bitasset_data(db).force_settled_volume.value, 320 ); // reset to 0, then 320\n\n      generate_block();\n\n      // Note: the scenario that a big settle order matching several smaller call orders,\n      //       and another scenario about force_settlement_offset_percent parameter,\n      //       are tested in force_settle_test in operation_test2.cpp.\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( global_settle_rounding_test )\n{\n   try {\n      // get around Graphene issue #615 feed expiration bug\n      generate_blocks(HARDFORK_615_TIME);\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((paul)(michael)(rachel)(alice));\n\n      // create assets\n      const auto& bitusd = create_bitasset(\"USDBIT\", paul_id);\n      const auto& core   = asset_id_type()(db);\n      asset_id_type bitusd_id = bitusd.get_id();\n      asset_id_type core_id = core.get_id();\n\n      // fund accounts\n      transfer(committee_account, michael_id, asset( 100000000 ) );\n      transfer(committee_account, paul_id,    asset(  10000000 ) );\n      transfer(committee_account, alice_id,   asset(  10000000 ) );\n\n      // allow global settle in bitusd\n      asset_update_operation op;\n      op.issuer = bitusd.issuer;\n      op.asset_to_update = bitusd.id;\n      op.new_options.issuer_permissions = global_settle;\n      op.new_options.flags = bitusd.options.flags;\n      op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) );\n      trx.operations.push_back(op);\n      sign(trx, paul_private_key);\n      PUSH_TX(db, trx);\n      generate_block();\n      trx.clear();\n\n      // add a feed to asset\n      update_feed_producers( bitusd_id(db), {paul_id} );\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), paul_id(db), current_feed );\n\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000);\n\n      // paul gets some bitusd\n      const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1001), core_id(db).amount(101));\n      call_order_id_type call_paul_id = call_paul.get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1001 );\n      BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), core_id(db) ), 10000000-101);\n\n      // and transfer some to rachel\n      transfer(paul_id, rachel_id, asset(200, bitusd_id));\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n      // michael borrow some bitusd\n      const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8));\n      call_order_id_type call_michael_id = call_michael.get_id();\n\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000-8);\n\n      // add global settle\n      force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1));\n      generate_block();\n\n      BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price\n                   == price( bitusd_id(db).amount(1007), core_id(db).amount(100) ) );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 100 ); // 100 from paul, and 0 from michael\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1007 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); // michael paid nothing for 6 usd\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); // paul paid 100 core for 1001 usd\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n      // all call orders are gone after global settle\n      BOOST_CHECK( !db.find(call_paul_id) );\n      BOOST_CHECK( !db.find(call_michael_id) );\n\n      // add settle order and check rounding issue\n      force_settle(rachel_id(db), bitusd_id(db).amount(4));\n      generate_block();\n\n      BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price\n                   == price( bitusd_id(db).amount(1007), core_id(db).amount(100) ) );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 100 ); // paid nothing\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1003 ); // settled 4 usd\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); // rachel paid 4 usd and got nothing\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n      // rachel settle more than 1 core\n      force_settle(rachel_id(db), bitusd_id(db).amount(13));\n      generate_block();\n\n      BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price\n                   == price( bitusd_id(db).amount(1007), core_id(db).amount(100) ) );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 99 ); // paid 1 core\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 990 ); // settled 13 usd\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 183); // rachel paid 13 usd and got 1 core\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( global_settle_rounding_test_after_hf_184 )\n{\n   try {\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n\n      if(hf2481)\n         generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n      else if(hf1270)\n         generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n      else\n         generate_blocks(HARDFORK_CORE_184_TIME - mi); // assume that hf core-184 and core-342 happen at same time\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((paul)(michael)(rachel)(alice));\n\n      // create assets\n      const auto& bitusd = create_bitasset(\"USDBIT\", paul_id);\n      const auto& core   = asset_id_type()(db);\n      asset_id_type bitusd_id = bitusd.get_id();\n      asset_id_type core_id = core.get_id();\n\n      // fund accounts\n      transfer(committee_account, michael_id, asset( 100000000 ) );\n      transfer(committee_account, paul_id,    asset(  10000000 ) );\n      transfer(committee_account, alice_id,   asset(  10000000 ) );\n\n      // allow global settle in bitusd\n      asset_update_operation op;\n      op.issuer = bitusd_id(db).issuer;\n      op.asset_to_update = bitusd_id;\n      op.new_options.issuer_permissions = global_settle;\n      op.new_options.flags = bitusd.options.flags;\n      op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) );\n      trx.operations.push_back(op);\n      sign(trx, paul_private_key);\n      PUSH_TX(db, trx);\n      generate_block();\n      trx.clear();\n\n      // add a feed to asset\n      update_feed_producers( bitusd_id(db), {paul_id} );\n      price_feed current_feed;\n      current_feed.maintenance_collateral_ratio = 1750;\n      current_feed.maximum_short_squeeze_ratio = 1100;\n      current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5);\n      publish_feed( bitusd_id(db), paul_id(db), current_feed );\n\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000);\n\n      // paul gets some bitusd\n      const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1001), core_id(db).amount(101));\n      call_order_id_type call_paul_id = call_paul.get_id();\n      BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1001 );\n      BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), core_id(db) ), 10000000-101);\n\n      // and transfer some to rachel\n      transfer(paul_id, rachel_id, asset(200, bitusd_id));\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n      // michael borrow some bitusd\n      const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8));\n      call_order_id_type call_michael_id = call_michael.get_id();\n\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000-8);\n\n      // add global settle\n      force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1));\n      generate_block();\n\n      BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price\n                   == price( bitusd_id(db).amount(1007), core_id(db).amount(102) ) );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 102 ); // 101 from paul, and 1 from michael\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1007 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); // michael paid 1 core for 6 usd\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899); // paul paid 101 core for 1001 usd\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n      // all call orders are gone after global settle\n      BOOST_CHECK( !db.find(call_paul_id));\n      BOOST_CHECK( !db.find(call_michael_id));\n\n      // settle order will not execute after HF due to too small\n      GRAPHENE_REQUIRE_THROW( force_settle(rachel_id(db), bitusd_id(db).amount(4)), fc::exception );\n\n      generate_block();\n\n      // balances unchanged\n      BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price\n                   == price( bitusd_id(db).amount(1007), core_id(db).amount(102) ) );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 102 );\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1007 );\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n      // rachel settle more than 1 core\n      force_settle(rachel_id(db), bitusd_id(db).amount(13));\n      generate_block();\n\n      BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price\n                   == price( bitusd_id(db).amount(1007), core_id(db).amount(102) ) );\n      BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 101 ); // paid 1 core\n      BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 997 ); // settled 10 usd\n\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1);\n      BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 190); // rachel paid 10 usd and got 1 core, 3 usd returned\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6);\n      BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899);\n      BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801);\n\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( create_bitassets )\n{\n   try {\n\n      generate_blocks( HARDFORK_480_TIME ); // avoid being affected by the price feed bug\n      generate_block();\n      set_expiration( db, trx );\n\n      ACTORS((paul)(rachelregistrar)(rachelreferrer));\n\n      upgrade_to_lifetime_member(rachelregistrar);\n      upgrade_to_lifetime_member(rachelreferrer);\n\n      constexpr auto market_fee_percent      = 50 * GRAPHENE_1_PERCENT;\n      constexpr auto biteur_reward_percent   = 90 * GRAPHENE_1_PERCENT;\n      constexpr auto referrer_reward_percent = 10 * GRAPHENE_1_PERCENT;\n\n      const auto& biteur = create_bitasset( \"EURBIT\", paul_id, market_fee_percent, charge_market_fee, 2 );\n      asset_id_type biteur_id = biteur.get_id();\n\n      const auto& bitusd = create_bitasset( \"USDBIT\", paul_id, market_fee_percent, charge_market_fee, 2, biteur_id );\n\n      const account_object rachel  = create_account( \"rachel\", rachelregistrar, rachelreferrer,\n                                                     referrer_reward_percent );\n\n      transfer( committee_account, rachelregistrar_id, asset( 10000000 ) );\n      transfer( committee_account, rachelreferrer_id, asset( 10000000 ) );\n      transfer( committee_account, rachel.get_id(), asset( 10000000) );\n      transfer( committee_account, paul_id, asset( 10000000000LL ) );\n\n      asset_update_operation op;\n      op.issuer = biteur.issuer;\n      op.asset_to_update = biteur_id;\n      op.new_options.issuer_permissions = charge_market_fee;\n      op.new_options.extensions.value.reward_percent = biteur_reward_percent;\n      op.new_options.flags = bitusd.options.flags | charge_market_fee;\n      op.new_options.core_exchange_rate = price( asset(20,biteur_id), asset(1,asset_id_type()) );\n      op.new_options.market_fee_percent = market_fee_percent;\n      trx.operations.push_back(op);\n      sign(trx, paul_private_key);\n      PUSH_TX(db, trx);\n      generate_block();\n      trx.clear();\n      set_expiration( db, trx );\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( market_fee_of_settle_order_before_hardfork_1780 )\n{\n   try {\n      INVOKE(create_bitassets);\n\n      GET_ACTOR(paul);\n      GET_ACTOR(rachel);\n      GET_ACTOR(rachelregistrar);\n      GET_ACTOR(rachelreferrer);\n\n      const asset_object &biteur = get_asset( \"EURBIT\" );\n      asset_id_type biteur_id = biteur.get_id();\n      const asset_object &bitusd = get_asset( \"USDBIT\" );\n      asset_id_type bitusd_id = bitusd.get_id();\n\n      const auto& core = asset_id_type()(db);\n\n      {// add a feed to asset bitusd\n         update_feed_producers( bitusd, {paul_id} );\n         price_feed feed;\n         feed.settlement_price = price( bitusd.amount(100), biteur.amount(5) );\n         feed.core_exchange_rate = price( bitusd.amount(100), asset(1) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( bitusd_id, paul_id, feed );\n      }\n\n      {// add a feed to asset biteur\n         update_feed_producers( biteur, {paul_id} );\n         price_feed feed;\n         feed.settlement_price = price( biteur.amount(100), core.amount(5) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( biteur_id, paul_id, feed );\n      }\n\n      enable_fees();\n\n      // paul gets some bitusd and biteur\n      borrow( paul_id, biteur.amount(20000), core.amount(2000) );\n      borrow( paul_id, bitusd.amount(10000), biteur.amount(1000) );\n\n      // and transfer some bitusd to rachel\n      constexpr auto rachel_bitusd_count = 1000;\n      transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) );\n\n      force_settle( rachel, bitusd.amount(rachel_bitusd_count) );\n      generate_block();\n      generate_blocks( db.head_block_time() + fc::hours(24) );\n      set_expiration( db, trx );\n\n      // Check results\n      int64_t biteur_balance = 0;\n      int64_t biteur_accumulated_fee = 0;\n      int64_t bitusd_accumulated_fee = 0;\n      {\n         // 1 biteur = 20 bitusd see publish_feed\n         const auto biteur_expected_result = rachel_bitusd_count/20;\n         const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50%\n         biteur_balance += biteur_expected_result - biteur_market_fee;\n\n         BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance );\n         BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 );\n\n         const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur );\n         const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur );\n\n         BOOST_CHECK_EQUAL( rachelregistrar_reward, 0 );\n         BOOST_CHECK_EQUAL( rachelreferrer_reward, 0 );\n\n         // market fee\n         biteur_accumulated_fee += biteur_market_fee;\n         bitusd_accumulated_fee += 0; // usd market fee percent 50%, but call orders don't pay\n         BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee);\n         BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee );\n      }\n\n      // Update the feed to asset bitusd to trigger a global settlement\n      {\n         price_feed feed;\n         feed.settlement_price = price( bitusd.amount(10), biteur.amount(5) );\n         feed.core_exchange_rate = price( bitusd.amount(100), asset(1) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( bitusd_id, paul_id, feed );\n      }\n\n      // Transfer more bitusd to rachel\n      transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) );\n      // Instant settlement\n      force_settle( rachel, bitusd.amount(rachel_bitusd_count) );\n\n      // Check results\n      {\n         // 950 biteur = 9000 bitusd in settlement fund\n         const auto biteur_expected_result = rachel_bitusd_count * 950 / 9000;\n         const auto biteur_market_fee = 0; // market fee percent = 50%, but doesn't pay before hf\n         biteur_balance += biteur_expected_result - biteur_market_fee;\n\n         BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance );\n         BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 );\n\n         const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur );\n         const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur );\n\n         BOOST_CHECK_EQUAL( rachelregistrar_reward, 0 );\n         BOOST_CHECK_EQUAL( rachelreferrer_reward, 0 );\n\n         // No market fee for instant settlement before hf\n         biteur_accumulated_fee += 0;\n         bitusd_accumulated_fee += 0;\n         BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee);\n         BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee );\n      }\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( market_fee_of_settle_order_after_hardfork_1780 )\n{\n   try {\n      INVOKE(create_bitassets);\n\n      if(hf2481)\n      {\n         auto mi = db.get_global_properties().parameters.maintenance_interval;\n         generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      }\n      else\n         generate_blocks( HARDFORK_CORE_1780_TIME );\n\n      set_expiration( db, trx );\n\n      GET_ACTOR(paul);\n      GET_ACTOR(rachel);\n      GET_ACTOR(rachelregistrar);\n      GET_ACTOR(rachelreferrer);\n\n      const asset_object &biteur = get_asset( \"EURBIT\" );\n      asset_id_type biteur_id = biteur.get_id();\n      const asset_object &bitusd = get_asset( \"USDBIT\" );\n      asset_id_type bitusd_id = bitusd.get_id();\n\n      const auto& core = asset_id_type()(db);\n\n      {// add a feed to asset bitusd\n         update_feed_producers( bitusd, {paul_id} );\n         price_feed feed;\n         feed.settlement_price = price( bitusd.amount(100), biteur.amount(5) );\n         feed.core_exchange_rate = price( bitusd.amount(100), asset(1) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( bitusd_id, paul_id, feed );\n      }\n\n      {// add a feed to asset biteur\n         update_feed_producers( biteur, {paul_id} );\n         price_feed feed;\n         feed.settlement_price = price( biteur.amount(100), core.amount(5) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( biteur_id, paul_id, feed );\n      }\n\n      enable_fees();\n\n      // paul gets some bitusd and biteur\n      borrow( paul_id, biteur.amount(20000), core.amount(2000) );\n      borrow( paul_id, bitusd.amount(10000), biteur.amount(1000) );\n\n      // and transfer some bitusd to rachel\n      constexpr auto rachel_bitusd_count = 1000;\n      transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) );\n\n      force_settle( rachel, bitusd.amount(rachel_bitusd_count) );\n      generate_block();\n      generate_blocks( db.head_block_time() + fc::hours(24) );\n      set_expiration( db, trx );\n\n      // Check results\n      int64_t biteur_balance = 0;\n      int64_t biteur_accumulated_fee = 0;\n      int64_t bitusd_accumulated_fee = 0;\n      {\n         // 1 biteur = 20 bitusd see publish_feed\n         const auto biteur_expected_result = rachel_bitusd_count/20;\n         const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50%\n         biteur_balance += biteur_expected_result - biteur_market_fee;\n\n         BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance );\n         BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 );\n\n         const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur );\n         const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur );\n\n         const auto biteur_reward = biteur_market_fee * 9 / 10; // 90%\n         const auto referrer_reward = biteur_reward / 10; // 10%\n         const auto registrar_reward = biteur_reward - referrer_reward;\n\n         BOOST_CHECK_EQUAL( rachelregistrar_reward, registrar_reward  );\n         BOOST_CHECK_EQUAL( rachelreferrer_reward, referrer_reward );\n\n         // market fee\n         biteur_accumulated_fee += biteur_market_fee - biteur_reward;\n         bitusd_accumulated_fee += 0; // usd market fee percent 50%, but call orders don't pay\n         BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee);\n         BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee );\n\n      }\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( market_fee_of_instant_settle_order_after_hardfork_1780 )\n{\n   try {\n      INVOKE(create_bitassets);\n\n      if(hf2481)\n      {\n         auto mi = db.get_global_properties().parameters.maintenance_interval;\n         generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      }\n      else\n         generate_blocks( HARDFORK_CORE_1780_TIME );\n\n      set_expiration( db, trx );\n\n      GET_ACTOR(paul);\n      GET_ACTOR(rachel);\n      GET_ACTOR(rachelregistrar);\n      GET_ACTOR(rachelreferrer);\n\n      const asset_object &biteur = get_asset( \"EURBIT\" );\n      asset_id_type biteur_id = biteur.get_id();\n      const asset_object &bitusd = get_asset( \"USDBIT\" );\n      asset_id_type bitusd_id = bitusd.get_id();\n\n      const auto& core = asset_id_type()(db);\n\n      {// add a feed to asset bitusd\n         update_feed_producers( bitusd, {paul_id} );\n         price_feed feed;\n         feed.settlement_price = price( bitusd.amount(100), biteur.amount(5) );\n         feed.core_exchange_rate = price( bitusd.amount(100), asset(1) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( bitusd_id, paul_id, feed );\n      }\n\n      {// add a feed to asset biteur\n         update_feed_producers( biteur, {paul_id} );\n         price_feed feed;\n         feed.settlement_price = price( biteur.amount(100), core.amount(5) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( biteur_id, paul_id, feed );\n      }\n\n      enable_fees();\n\n      // paul gets some bitusd and biteur\n      borrow( paul_id, biteur.amount(20000), core.amount(2000) );\n      borrow( paul_id, bitusd.amount(10000), biteur.amount(1000) );\n\n      // Update the feed to asset bitusd to trigger a global settlement\n      {\n         price_feed feed;\n         feed.settlement_price = price( bitusd.amount(10), biteur.amount(5) );\n         feed.core_exchange_rate = price( bitusd.amount(100), asset(1) );\n         feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100;\n         publish_feed( bitusd_id, paul_id, feed );\n      }\n\n      // Transfer some bitusd to rachel\n      constexpr auto rachel_bitusd_count = 1000;\n      transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) );\n      // Instant settlement\n      force_settle( rachel, bitusd.amount(rachel_bitusd_count) ); // instant settlement\n\n      // Check results\n      int64_t biteur_balance = 0;\n      int64_t biteur_accumulated_fee = 0;\n      int64_t bitusd_accumulated_fee = 0;\n      {\n         // before hf2481: 1000 biteur = 10000 bitusd in settlement fund\n         // after hf2481: round_up(10000/11) in settlement fund\n         const auto biteur_expected_result = hf2481 ? (rachel_bitusd_count+10)/11 : rachel_bitusd_count/10;\n         const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50%\n         biteur_balance += biteur_expected_result - biteur_market_fee;\n\n         BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance );\n         BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 );\n\n         const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur );\n         const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur );\n\n         const auto biteur_reward = biteur_market_fee * 9 / 10; // 90%\n         const auto referrer_reward = biteur_reward / 10; // 10%\n         const auto registrar_reward = biteur_reward - referrer_reward;\n\n         BOOST_CHECK_EQUAL( rachelregistrar_reward, registrar_reward  );\n         BOOST_CHECK_EQUAL( rachelreferrer_reward, referrer_reward );\n\n         // market fee\n         biteur_accumulated_fee += biteur_market_fee - biteur_reward;\n         bitusd_accumulated_fee += 0; // usd market fee percent 50%, but call orders don't pay\n         BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee);\n         BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee );\n\n      }\n\n   } FC_LOG_AND_RETHROW()\n}\n\n/// Tests instant settlement:\n/// * After hf core-2591, forced-settlements are filled at margin call order price (MCOP) when applicable\nBOOST_AUTO_TEST_CASE( collateral_fee_of_instant_settlement_test )\n{ try {\n\n   // Advance to a recent hard fork\n   generate_blocks(HARDFORK_CORE_2582_TIME);\n   generate_block();\n\n   // multiple passes,\n   // i == 0 : before hf core-2591, extreme feed price\n   // i == 1 : before hf core-2591, normal feed price\n   // i == 2 : before hf core-2591, normal feed price, and price recovers after GS\n   // i == 3 : after hf core-2591, extreme feed price\n   // i == 4 : after hf core-2591, normal feed price\n   // i == 5 : after hf core-2591, normal feed price, and price recovers after GS\n   for( int i = 0; i < 6; ++ i )\n   {\n      idump( (i) );\n\n      if( 3 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n\n      using bsrm_type = bitasset_options::black_swan_response_type;\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method()\n                   == bsrm_type::global_settlement );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // Transfer funds to sellers\n      transfer( borrower, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that borrower's debt position is undercollateralized\n      if( 0 == i || 3 == i )\n         f.settlement_price = price( asset(1,mpa_id), asset(GRAPHENE_MAX_SHARE_SUPPLY) );\n      else\n         f.settlement_price = price( asset(100,mpa_id), asset(2) );\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price );\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n      BOOST_CHECK( !db.find_settled_debt_order(mpa_id) );\n\n      // fund receives 1600\n      BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 1600 );\n\n      BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 400 );\n\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).settlement_price\n                   == price( asset(100000,mpa_id), asset(1600) ) );\n\n      BOOST_CHECK( !db.find( call_id ) );\n\n      if( 2 == i || 5 == i )\n      {\n         // price recovers\n         f.settlement_price = price( asset(100,mpa_id), asset(1) );\n         // call pays price  (MSSP) = 100:1 * 1000:1250 = 100000:1250 = 80\n         // call match price (MCOP) = 100:1 * 1000:1239 = 100000:1239 = 80.710250202\n         publish_feed( mpa_id, feeder_id, f, feed_icr );\n      }\n\n      // seller settles\n      share_type amount_to_settle = 56789;\n      auto result = force_settle( seller, asset(amount_to_settle, mpa_id) );\n      auto op_result = result.get<extendable_operation_result>().value;\n\n      auto check_result = [&]\n      {\n         BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n         if( 5 == i )\n         {\n            // settlement fund pays = round_down(56789 * 1600 / 100000) = 908\n            // seller pays = round_up(908 * 100000 / 1600) = 56750\n            // settlement fund = 1600 - 908 = 692\n            // settlement debt = 100000 - 56750 = 43250\n            // seller would receive = round_up(56750 * 1239 / 10000 ) = 704 (<908, so ok)\n            // collateral fee = 908 - 704 = 204\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 56750, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 704 ) );\n            BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() );\n            BOOST_CHECK( *op_result.fees->begin() == asset( 204 ) );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 692 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 604 ); // 400 + 204\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 43250 );\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 704 );\n         }\n         else\n         {\n            // receives = round_down(56789 * 1600 / 100000) = 908\n            // pays = round_up(908 * 100000 / 1600) = 56750\n            // settlement fund = 1600 - 908 = 692\n            // settlement debt = 100000 - 56750 = 43250\n            BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n            BOOST_CHECK( *op_result.paid->begin() == asset( 56750, mpa_id ) );\n            BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n            BOOST_CHECK( *op_result.received->begin() == asset( 908 ) );\n            BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() );\n            BOOST_CHECK( *op_result.fees->begin() == asset( 0 ) );\n\n            BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n            BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n            BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 692 );\n\n            BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 400 );\n\n            BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 43250 );\n            BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 908 );\n         }\n\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests instant settlement:\n/// * After hf core-2591, for prediction markets, forced-settlements are NOT filled at margin call order price (MCOP)\nBOOST_AUTO_TEST_CASE( pm_instant_settlement_price_test )\n{ try {\n\n   // Advance to a recent hard fork\n   generate_blocks(HARDFORK_CORE_2582_TIME);\n   generate_block();\n\n   // multiple passes,\n   // i == 0 : before hf core-2591\n   // i == 1 : after hf core-2591\n   for( int i = 0; i < 2; ++ i )\n   {\n      idump( (i) );\n\n      if( 1 == i )\n      {\n         // Advance to core-2591 hard fork\n         generate_blocks(HARDFORK_CORE_2591_TIME);\n         generate_block();\n      }\n\n      set_expiration( db, trx );\n\n      ACTORS((judge)(alice)(feeder));\n\n      const auto& pmark = create_prediction_market(\"PMARK\", judge_id);\n      const auto& core  = asset_id_type()(db);\n\n      asset_id_type pm_id = pmark.get_id();\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, judge_id, asset(init_balance));\n      transfer(committee_account, alice_id, asset(init_balance));\n\n      BOOST_TEST_MESSAGE( \"Open position with equal collateral\" );\n      borrow( alice, pmark.amount(1000), asset(1000) );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 1000 );\n      BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 1000 );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( pm_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,pm_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,pm_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( pm_id, feeder_id, f, feed_icr );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 1000 );\n      BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 1000 );\n\n      BOOST_TEST_MESSAGE( \"Globally settling\" );\n      force_global_settle( pmark, pmark.amount(1) / core.amount(1) );\n\n      BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 1000 );\n      BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 1000 );\n\n      // alice settles\n      auto result = force_settle( alice, asset(300, pm_id) );\n      auto op_result = result.get<extendable_operation_result>().value;\n\n      BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created\n\n      BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() );\n      BOOST_CHECK( *op_result.paid->begin() == asset( 300, pm_id ) );\n      BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() );\n      BOOST_CHECK( *op_result.received->begin() == asset( 300 ) );\n      BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() );\n      BOOST_CHECK( *op_result.fees->begin() == asset( 0 ) );\n\n      auto check_result = [&]\n      {\n         BOOST_CHECK( !pm_id(db).bitasset_data(db).is_individually_settled_to_fund() );\n         BOOST_CHECK( pm_id(db).bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK_EQUAL( pm_id(db).bitasset_data(db).settlement_fund.value, 700 );\n\n         BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).accumulated_collateral_fees.value, 0 );\n\n         BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 700 );\n         BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 700 );\n      };\n\n      check_result();\n\n      BOOST_TEST_MESSAGE( \"Generate a block\" );\n      generate_block();\n\n      check_result();\n\n      // reset\n      db.pop_block();\n\n   } // for i\n\n} FC_LOG_AND_RETHROW() }\n\n/**\n * Test case to reproduce https://github.com/bitshares/bitshares-core/issues/1883.\n * When there is only one fill_order object in the ticker rolling buffer, it should only be rolled out once.\n */\nBOOST_AUTO_TEST_CASE( global_settle_ticker_test )\n{\n   try {\n      generate_block();\n\n      const auto& meta_idx = db.get_index_type<simple_index<graphene::market_history::market_ticker_meta_object>>();\n      const auto& ticker_idx = db.get_index_type<graphene::market_history::market_ticker_index>().indices();\n      const auto& history_idx = db.get_index_type<graphene::market_history::history_index>().indices();\n\n      BOOST_CHECK_EQUAL( meta_idx.size(), 0 );\n      BOOST_CHECK_EQUAL( ticker_idx.size(), 0 );\n      BOOST_CHECK_EQUAL( history_idx.size(), 0 );\n\n      ACTORS((judge)(alice));\n\n      const auto& pmark = create_prediction_market(\"PMARK\", judge_id);\n      const auto& core  = asset_id_type()(db);\n\n      int64_t init_balance(1000000);\n      transfer(committee_account, judge_id, asset(init_balance));\n      transfer(committee_account, alice_id, asset(init_balance));\n\n      BOOST_TEST_MESSAGE( \"Open position with equal collateral\" );\n      borrow( alice, pmark.amount(1000), asset(1000) );\n\n      BOOST_TEST_MESSAGE( \"Globally settling\" );\n      force_global_settle( pmark, pmark.amount(1) / core.amount(1) );\n\n      generate_block();\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      {\n         BOOST_CHECK_EQUAL( meta_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( ticker_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( history_idx.size(), 1 );\n\n         const auto& meta = *meta_idx.begin();\n         const auto& tick = *ticker_idx.begin();\n         const auto& hist = *history_idx.begin();\n\n         BOOST_CHECK( meta.rolling_min_order_his_id == hist.id );\n         BOOST_CHECK( meta.skip_min_order_his_id == false );\n\n         BOOST_CHECK( tick.base_volume == 1000 );\n         BOOST_CHECK( tick.quote_volume == 1000 );\n      }\n\n      generate_blocks( db.head_block_time() + 86000 ); // less than a day\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      // nothing changes\n      {\n         BOOST_CHECK_EQUAL( meta_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( ticker_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( history_idx.size(), 1 );\n\n         const auto& meta = *meta_idx.begin();\n         const auto& tick = *ticker_idx.begin();\n         const auto& hist = *history_idx.begin();\n\n         BOOST_CHECK( meta.rolling_min_order_his_id == hist.id );\n         BOOST_CHECK( meta.skip_min_order_his_id == false );\n\n         BOOST_CHECK( tick.base_volume == 1000 );\n         BOOST_CHECK( tick.quote_volume == 1000 );\n      }\n\n      generate_blocks( db.head_block_time() + 4000 ); // now more than 24 hours\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      // the history is rolled out, new 24h volume should be 0\n      {\n         BOOST_CHECK_EQUAL( meta_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( ticker_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( history_idx.size(), 1 );\n\n         const auto& meta = *meta_idx.begin();\n         const auto& tick = *ticker_idx.begin();\n         const auto& hist = *history_idx.begin();\n\n         BOOST_CHECK( meta.rolling_min_order_his_id == hist.id );\n         BOOST_CHECK( meta.skip_min_order_his_id == true ); // the order should be skipped on next roll\n\n         BOOST_CHECK( tick.base_volume == 0 );\n         BOOST_CHECK( tick.quote_volume == 0 );\n      }\n\n      generate_block();\n      fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread\n\n      // nothing changes\n      {\n         BOOST_CHECK_EQUAL( meta_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( ticker_idx.size(), 1 );\n         BOOST_CHECK_EQUAL( history_idx.size(), 1 );\n\n         const auto& meta = *meta_idx.begin();\n         const auto& tick = *ticker_idx.begin();\n         const auto& hist = *history_idx.begin();\n\n         BOOST_CHECK( meta.rolling_min_order_his_id == hist.id );\n         BOOST_CHECK( meta.skip_min_order_his_id == true );\n\n         BOOST_CHECK( tick.base_volume == 0 );\n         BOOST_CHECK( tick.quote_volume == 0 );\n      }\n\n\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests a scenario that force settlements get cancelled on the block when the asset is globally settled\nBOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_gs )\n{\n   try {\n\n      // Advance to a desired hard fork time\n      // Note: this test doesn't apply after hf2481\n      generate_blocks( HARDFORK_CORE_1780_TIME );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 800;\n      acop.bitasset_opts->force_settlement_delay_sec = 600;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      publish_feed( mpa_id, feeder_id, f );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // Transfer funds to seller\n      transfer( borrower, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(11100,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n      // generate a block\n      generate_block();\n\n      // check again\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // publish a new feed so that the asset is globally settled\n      f.settlement_price = price( asset(100,mpa_id), asset(10) );\n      publish_feed( mpa_id, feeder_id, f );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // generate a block\n      generate_block();\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() );\n\n      // the settle order is cancelled\n      BOOST_REQUIRE( !db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests a scenario that force settlements get cancelled on expiration when there is no sufficient feed\nBOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_feed )\n{\n   try {\n\n      // Advance to a desired hard fork time\n      if(hf2467)\n      {\n         auto mi = db.get_global_properties().parameters.maintenance_interval;\n         generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      }\n      else\n         generate_blocks( HARDFORK_CORE_1780_TIME );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 400;\n      acop.bitasset_opts->force_settlement_delay_sec = 600;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      publish_feed( mpa_id, feeder_id, f );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // Transfer funds to seller\n      transfer( borrower, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(11100,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // let the price feed expire\n      generate_blocks( db.head_block_time() + fc::seconds(400) );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // let the settle order expire\n      generate_blocks( db.head_block_time() + fc::seconds(200) );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // the settle order is cancelled\n      BOOST_REQUIRE( !db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // unable to settle when no feed\n      BOOST_CHECK_THROW( force_settle( seller, asset(11100,mpa_id) ), fc::exception );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests a scenario that force settlements get cancelled on expiration when the amount is zero\nBOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_zero_amount )\n{\n   try {\n\n      // Advance to a desired hard fork time\n      if(hf2467)\n      {\n         auto mi = db.get_global_properties().parameters.maintenance_interval;\n         generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      }\n      else\n         generate_blocks( HARDFORK_CORE_1780_TIME );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 800;\n      acop.bitasset_opts->force_settlement_delay_sec = 600;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      publish_feed( mpa_id, feeder_id, f );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // Transfer funds to seller\n      transfer( borrower, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(0,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 0 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // generate a block\n      generate_block();\n\n      // check again\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 0 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // let the settle order expire\n      generate_blocks( db.head_block_time() + fc::seconds(600) );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // the settle order is cancelled\n      BOOST_REQUIRE( !db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests a scenario that force settlements get cancelled on expiration when offset is 100%\nBOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_100_percent_offset )\n{\n   try {\n\n      // Advance to a desired hard fork time\n      if(hf2467)\n      {\n         auto mi = db.get_global_properties().parameters.maintenance_interval;\n         generate_blocks(HARDFORK_CORE_2467_TIME - mi);\n         generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      }\n      else\n         generate_blocks( HARDFORK_CORE_1780_TIME );\n\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder)(borrower)(seller));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n      fund( borrower, asset(init_amount) );\n\n      // Create asset\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMMPA\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100; // 1%\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK;\n      acop.bitasset_opts = bitasset_options();\n      acop.bitasset_opts->minimum_feeds = 1;\n      acop.bitasset_opts->feed_lifetime_sec = 800;\n      acop.bitasset_opts->force_settlement_delay_sec = 600;\n      acop.bitasset_opts->force_settlement_offset_percent = GRAPHENE_100_PERCENT;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      processed_transaction ptx = PUSH_TX(db, trx, ~0);\n      const asset_object& mpa = db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n      asset_id_type mpa_id = mpa.get_id();\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(100,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(100,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      publish_feed( mpa_id, feeder_id, f );\n\n      // borrowers borrow some\n      // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600\n      const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) );\n      BOOST_REQUIRE( call_ptr );\n      call_order_id_type call_id = call_ptr->get_id();\n\n      // Transfer funds to seller\n      transfer( borrower, seller, asset(100000,mpa_id) );\n\n      BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 );\n      BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // seller settles some\n      auto result = force_settle( seller, asset(11100,mpa_id) );\n      force_settlement_id_type settle_id { *result.get<extendable_operation_result>().value.new_objects->begin() };\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // generate a block\n      generate_block();\n\n      // check again\n      BOOST_REQUIRE( db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n      // let the settle order expire\n      generate_blocks( db.head_block_time() + fc::seconds(600) );\n\n      // check\n      BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price );\n\n      // the settle order is cancelled\n      BOOST_REQUIRE( !db.find(settle_id) );\n\n      BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 );\n      BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 );\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_no_feed_after_hf_2467)\n{ try {\n   hf2467 = true;\n   INVOKE(settle_order_cancel_due_to_no_feed);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_zero_amount_after_hf_2467)\n{ try {\n   hf2467 = true;\n   INVOKE(settle_order_cancel_due_to_zero_amount);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_100_percent_offset_after_hf_2467)\n{ try {\n   hf2467 = true;\n   INVOKE(settle_order_cancel_due_to_100_percent_offset);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(settle_rounding_test_after_hf_1270)\n{ try {\n   hf1270 = true;\n   INVOKE(settle_rounding_test_after_hf_184);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(settle_rounding_test_after_hf_2481)\n{ try {\n   hf2481 = true;\n   INVOKE(settle_rounding_test_after_hf_184);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(global_settle_rounding_test_after_hf_1270)\n{ try {\n   hf1270 = true;\n   INVOKE(global_settle_rounding_test_after_hf_184);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(global_settle_rounding_test_after_hf_2481)\n{ try {\n   hf2481 = true;\n   INVOKE(global_settle_rounding_test_after_hf_184);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(market_fee_of_settle_order_after_hardfork_2481)\n{ try {\n   hf2481 = true;\n   INVOKE(market_fee_of_settle_order_after_hardfork_1780);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(market_fee_of_instant_settle_order_after_hardfork_2481)\n{ try {\n   hf2481 = true;\n   INVOKE(market_fee_of_instant_settle_order_after_hardfork_1780);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/simple_maker_taker_fee_tests.cpp",
    "content": "/*\n * Copyright (c) 2020 Michel Santos, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <string>\n#include <boost/test/unit_test.hpp>\n#include <fc/exception/exception.hpp>\n\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nstruct simple_maker_taker_database_fixture : database_fixture {\n   simple_maker_taker_database_fixture()\n           : database_fixture() {\n   }\n\n   const limit_order_create_operation\n   create_sell_operation(account_id_type user, const asset &amount, const asset &recv) {\n      const time_point_sec order_expiration = time_point_sec::maximum();\n      const price &fee_core_exchange_rate = price::unit_price();\n      limit_order_create_operation op = create_sell_operation(user, amount, recv, order_expiration,\n                                                              fee_core_exchange_rate);\n      return op;\n   }\n\n   const limit_order_create_operation\n   create_sell_operation(account_id_type user, const asset &amount, const asset &recv,\n                         const time_point_sec order_expiration,\n                         const price &fee_core_exchange_rate) {\n      limit_order_create_operation op = create_sell_operation(user(db), amount, recv, order_expiration,\n                                                              fee_core_exchange_rate);\n      return op;\n   }\n\n   const limit_order_create_operation\n   create_sell_operation(const account_object &user, const asset &amount, const asset &recv,\n                         const time_point_sec order_expiration,\n                         const price &fee_core_exchange_rate) {\n      limit_order_create_operation sell_order;\n      sell_order.seller = user.id;\n      sell_order.amount_to_sell = amount;\n      sell_order.min_to_receive = recv;\n      sell_order.expiration = order_expiration;\n\n      return sell_order;\n   }\n\n   const asset_create_operation create_user_issued_asset_operation(const string &name, const account_object &issuer,\n                                                                   uint16_t flags, const price &core_exchange_rate,\n                                                                   uint8_t precision, uint16_t maker_fee_percent,\n                                                                   uint16_t taker_fee_percent) {\n      asset_create_operation creator;\n      creator.issuer = issuer.id;\n      creator.fee = asset();\n      creator.symbol = name;\n      creator.common_options.max_supply = 0;\n      creator.precision = precision;\n\n      creator.common_options.core_exchange_rate = core_exchange_rate;\n      creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      creator.common_options.flags = flags;\n      creator.common_options.issuer_permissions = flags;\n      creator.common_options.market_fee_percent = maker_fee_percent;\n      creator.common_options.extensions.value.taker_fee_percent = taker_fee_percent;\n\n      return creator;\n\n   }\n};\n\n\n/**\n * BSIP81: Asset owners may specify different market fee rate for maker orders and taker orders\n */\nBOOST_FIXTURE_TEST_SUITE(simple_maker_taker_fee_tests, simple_maker_taker_database_fixture)\n\n   /**\n    * Test of setting taker fee before HF and after HF for a UIA\n    */\n   BOOST_AUTO_TEST_CASE(setting_taker_fees_uia) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy));\n         account_id_type issuer_id = jill.get_id();\n         fc::ecc::private_key issuer_private_key = jill_private_key;\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n         uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT;\n         const asset_object jillcoin = create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                market_fee_percent);\n\n         //////\n         // Before HF, test inability to set taker fees\n         //////\n         asset_update_operation uop;\n         uop.issuer = issuer_id;\n         uop.asset_to_update = jillcoin.get_id();\n         uop.new_options = jillcoin.options;\n         uint16_t new_taker_fee_percent = uop.new_options.market_fee_percent / 2;\n         uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent;\n\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, issuer_private_key);\n         GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception);\n         // TODO: Check the specific exception?\n\n         // Check the taker fee\n         asset_object updated_asset = jillcoin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n\n         //////\n         // Before HF, test inability to set taker fees with an asset update operation inside of a proposal\n         //////\n         {\n            trx.clear();\n            set_expiration(db, trx);\n\n            uint64_t alternate_taker_fee_percent = new_taker_fee_percent * 2;\n            uop.new_options.extensions.value.taker_fee_percent = alternate_taker_fee_percent;\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.operations.push_back(cop);\n            // sign(trx, issuer_private_key);\n            GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception);\n\n            // Check the taker fee is not changed because the proposal has not been approved\n            updated_asset = jillcoin.get_id()(db);\n            BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n         }\n\n\n         //////\n         // Before HF, test inability to set taker fees with an asset create operation inside of a proposal\n         //////\n         {\n            trx.clear();\n            set_expiration(db, trx);\n\n            uint64_t maker_fee_percent = 10 * GRAPHENE_1_PERCENT;\n            uint64_t taker_fee_percent = 2 * GRAPHENE_1_PERCENT;\n            asset_create_operation ac_op = create_user_issued_asset_operation(\"JCOIN2\", jill, charge_market_fee, price,\n                                                                              2,\n                                                                              maker_fee_percent, taker_fee_percent);\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(ac_op);\n\n            trx.operations.push_back(cop);\n            // sign(trx, issuer_private_key);\n\n            GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // The proposal should be rejected\n\n         }\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test default values of taker fee after HF\n         // After the HF its default value should still not be set\n         //////\n         updated_asset = jillcoin.get_id()(db);\n         uint16_t expected_taker_fee_percent = updated_asset.options.market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n\n         //////\n         // After HF, test invalid taker fees\n         //////\n         uop.new_options.extensions.value.taker_fee_percent = GRAPHENE_100_PERCENT + 1;\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, issuer_private_key);\n         GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // An exception should be thrown indicating the reason\n         // TODO: Check the specific exception?\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent;\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, issuer_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee\n         updated_asset = jillcoin.get_id()(db);\n         expected_taker_fee_percent = new_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n\n         //////\n         // After HF, test ability to set taker fees with an asset update operation inside of a proposal\n         //////\n         {\n            trx.clear();\n            set_expiration(db, trx);\n\n            uint64_t alternate_taker_fee_percent = new_taker_fee_percent * 2;\n            uop.new_options.extensions.value.taker_fee_percent = alternate_taker_fee_percent;\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(uop);\n\n            trx.operations.push_back(cop);\n            // sign(trx, issuer_private_key);\n            processed_transaction processed = PUSH_TX(db, trx); // No exception should be thrown\n\n            // Check the taker fee is not changed because the proposal has not been approved\n            updated_asset = jillcoin.get_id()(db);\n            expected_taker_fee_percent = new_taker_fee_percent;\n            BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n            BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n\n            // Approve the proposal\n            trx.clear();\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = jill.id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(jill.get_id());\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, jill_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n            // Advance to after proposal expires\n            generate_blocks(cop.expiration_time);\n\n            // Check the taker fee is not changed because the proposal has not been approved\n            updated_asset = jillcoin.get_id()(db);\n            expected_taker_fee_percent = alternate_taker_fee_percent;\n            BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n            BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         }\n\n\n         //////\n         // After HF, test ability to set taker fees with an asset create operation inside of a proposal\n         //////\n         {\n            trx.clear();\n            set_expiration(db, trx);\n\n            uint64_t maker_fee_percent = 10 * GRAPHENE_1_PERCENT;\n            uint64_t taker_fee_percent = 2 * GRAPHENE_1_PERCENT;\n            asset_create_operation ac_op = create_user_issued_asset_operation(\"JCOIN2\", jill, charge_market_fee, price,\n                                                                              2,\n                                                                              maker_fee_percent, taker_fee_percent);\n\n            proposal_create_operation cop;\n            cop.review_period_seconds = 86400;\n            uint32_t buffer_seconds = 60 * 60;\n            cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds;\n            cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT;\n            cop.proposed_ops.emplace_back(ac_op);\n\n            trx.operations.push_back(cop);\n            // sign(trx, issuer_private_key);\n\n            processed_transaction processed = PUSH_TX(db, trx); // No exception should be thrown\n\n            // Check the asset does not exist because the proposal has not been approved\n            const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_symbol>();\n            const auto itr = asset_idx.find(\"JCOIN2\");\n            BOOST_CHECK(itr == asset_idx.end());\n\n            // Approve the proposal\n            trx.clear();\n            proposal_id_type pid { processed.operation_results[0].get<object_id_type>() };\n\n            proposal_update_operation pup;\n            pup.fee_paying_account = jill.id;\n            pup.proposal = pid;\n            pup.active_approvals_to_add.insert(jill.get_id());\n            trx.operations.push_back(pup);\n            set_expiration(db, trx);\n            sign(trx, jill_private_key);\n\n            PUSH_TX(db, trx); // No exception should be thrown\n\n            // Advance to after proposal expires\n            generate_blocks(cop.expiration_time);\n\n            // Check the taker fee is not changed because the proposal has not been approved\n            BOOST_CHECK(asset_idx.find(\"JCOIN2\") != asset_idx.end());\n            updated_asset = *asset_idx.find(\"JCOIN2\");\n            expected_taker_fee_percent = taker_fee_percent;\n            BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n            BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n            uint16_t expected_maker_fee_percent = maker_fee_percent;\n            BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent);\n\n         }\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of setting taker fee before HF and after HF for a smart asset\n    */\n   BOOST_AUTO_TEST_CASE(setting_taker_fees_smart_asset) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         create_bitasset(\"SMARTBIT\", smartissuer.get_id());\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object &bitsmart = get_asset(\"SMARTBIT\");\n\n         generate_blocks(HARDFORK_615_TIME); // get around Graphene issue #615 feed expiration bug\n         generate_block();\n\n\n         //////\n         // Before HF, test inability to set taker fees\n         //////\n         asset_update_operation uop;\n         uop.issuer = smartissuer.id;\n         uop.asset_to_update = bitsmart.get_id();\n         uop.new_options = bitsmart.options;\n         uint16_t new_taker_fee_percent = uop.new_options.market_fee_percent / 2;\n         uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent;\n\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, smartissuer_private_key);\n         GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // An exception should be thrown indicating the reason\n         // TODO: Check the specific exception?\n\n         // Check the taker fee\n         asset_object updated_asset = bitsmart.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test default values of taker fee after HF\n         // After the HF its default value should still not be set\n         //////\n         updated_asset = bitsmart.get_id()(db);\n         uint16_t expected_taker_fee_percent = updated_asset.options.market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n\n         //////\n         // After HF, test invalid taker fees\n         //////\n         uop.new_options.extensions.value.taker_fee_percent = GRAPHENE_100_PERCENT + 1;\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, smartissuer_private_key);\n         GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // An exception should be thrown indicating the reason\n         // TODO: Check the specific exception?\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent;\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee\n         updated_asset = bitsmart.get_id()(db);\n         expected_taker_fee_percent = new_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test the default taker fee values of multiple different assets after HF\n    */\n   BOOST_AUTO_TEST_CASE(default_taker_fees) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((alice)(bob)(charlie)(smartissuer));\n\n         // Initialize tokens with custom market fees\n         price price(asset(1, asset_id_type(1)), asset(1));\n\n         const uint16_t alice1coin_market_fee_percent = 1 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"ALICE1COIN\", alice, charge_market_fee, price, 2,\n                                                                  alice1coin_market_fee_percent);\n\n         const uint16_t alice2coin_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"ALICE2COIN\", alice, charge_market_fee, price, 2,\n                                                                  alice2coin_market_fee_percent);\n\n         const uint16_t bob1coin_market_fee_percent = 3 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"BOB1COIN\", alice, charge_market_fee, price, 2,\n                                                                bob1coin_market_fee_percent);\n\n         const uint16_t bob2coin_market_fee_percent = 4 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"BOB2COIN\", alice, charge_market_fee, price, 2,\n                                                                bob2coin_market_fee_percent);\n\n         const uint16_t charlie1coin_market_fee_percent = 4 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"CHARLIE1COIN\", alice, charge_market_fee, price, 2,\n                                                                    charlie1coin_market_fee_percent);\n\n         const uint16_t charlie2coin_market_fee_percent = 5 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"CHARLIE2COIN\", alice, charge_market_fee, price, 2,\n                                                                    charlie2coin_market_fee_percent);\n\n         const uint16_t bitsmart1coin_market_fee_percent = 7 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT1\", smartissuer.get_id(), bitsmart1coin_market_fee_percent);\n\n\n         const uint16_t bitsmart2coin_market_fee_percent = 8 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT2\", smartissuer.get_id(), bitsmart2coin_market_fee_percent);\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object& alice1coin = get_asset(\"ALICE1COIN\");\n         const asset_object& alice2coin = get_asset(\"ALICE2COIN\");\n         const asset_object& bob1coin = get_asset(\"BOB1COIN\");\n         const asset_object& bob2coin = get_asset(\"BOB2COIN\");\n         const asset_object& charlie1coin = get_asset(\"CHARLIE1COIN\");\n         const asset_object& charlie2coin = get_asset(\"CHARLIE2COIN\");\n         const asset_object& bitsmart1 = get_asset(\"SMARTBIT1\");\n         const asset_object& bitsmart2 = get_asset(\"SMARTBIT2\");\n\n\n         //////\n         // Before HF, test the market/maker fees for each asset\n         //////\n         asset_object updated_asset;\n         uint16_t expected_fee_percent;\n\n         updated_asset = alice1coin.get_id()(db);\n         expected_fee_percent = alice1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = alice2coin.get_id()(db);\n         expected_fee_percent = alice2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bob1coin.get_id()(db);\n         expected_fee_percent = bob1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bob2coin.get_id()(db);\n         expected_fee_percent = bob2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = charlie1coin.get_id()(db);\n         expected_fee_percent = charlie1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = charlie2coin.get_id()(db);\n         expected_fee_percent = charlie2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bitsmart1.get_id()(db);\n         expected_fee_percent = bitsmart1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bitsmart2.get_id()(db);\n         expected_fee_percent = bitsmart2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n\n         //////\n         // Before HF, test that taker fees are not set\n         //////\n         // Check the taker fee\n         updated_asset = alice1coin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = alice2coin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bob1coin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bob2coin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = charlie1coin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = charlie2coin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bitsmart1.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bitsmart2.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test the maker fees for each asset are unchanged\n         //////\n         updated_asset = alice1coin.get_id()(db);\n         expected_fee_percent = alice1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = alice2coin.get_id()(db);\n         expected_fee_percent = alice2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bob1coin.get_id()(db);\n         expected_fee_percent = bob1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bob2coin.get_id()(db);\n         expected_fee_percent = bob2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = charlie1coin.get_id()(db);\n         expected_fee_percent = charlie1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = charlie2coin.get_id()(db);\n         expected_fee_percent = charlie2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bitsmart1.get_id()(db);\n         expected_fee_percent = bitsmart1coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n         updated_asset = bitsmart2.get_id()(db);\n         expected_fee_percent = bitsmart2coin_market_fee_percent;\n         BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent);\n\n\n         //////\n         // After HF, test the taker fees for each asset are not set\n         //////\n         updated_asset = alice1coin.get_id()(db);\n         expected_fee_percent = alice1coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = alice2coin.get_id()(db);\n         expected_fee_percent = alice2coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bob1coin.get_id()(db);\n         expected_fee_percent = bob1coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bob2coin.get_id()(db);\n         expected_fee_percent = bob2coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = charlie1coin.get_id()(db);\n         expected_fee_percent = charlie1coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = charlie2coin.get_id()(db);\n         expected_fee_percent = charlie2coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bitsmart1.get_id()(db);\n         expected_fee_percent = bitsmart1coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         updated_asset = bitsmart2.get_id()(db);\n         expected_fee_percent = bitsmart2coin_market_fee_percent;\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of different maker and taker fees charged when filling limit orders after HF for a UIA\n    */\n   BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_1) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                jill_market_fee_percent);\n\n         const uint16_t IZZY_PRECISION = 1000;\n         const uint16_t izzy_market_fee_percent = 5 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"ICOIN\", izzy, charge_market_fee, price, 3,\n                                                                izzy_market_fee_percent);\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object& jillcoin = get_asset(\"JCOIN\");\n         const asset_object& izzycoin = get_asset(\"ICOIN\");\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         // Define the new taker fees\n         uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = jill_maker_fee_percent / 2;\n\n         uint16_t izzy_maker_fee_percent = izzy_market_fee_percent;\n         uint16_t izzy_taker_fee_percent = izzy_maker_fee_percent / 2;\n\n         // Set the new taker fee for JILLCOIN\n         asset_update_operation uop;\n         uop.issuer = jill.id;\n         uop.asset_to_update = jillcoin.get_id();\n         uop.new_options = jillcoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, jill_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for JILLCOIN\n         asset_object updated_asset = jillcoin.get_id()(db);\n         uint16_t expected_taker_fee_percent = jill_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         // Set the new taker fee for IZZYCOIN\n         uop.issuer = izzy.id;\n         uop.asset_to_update = izzycoin.get_id();\n         uop.new_options = izzycoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = izzy_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, izzy_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for IZZYCOIN\n         updated_asset = izzycoin.get_id()(db);\n         expected_taker_fee_percent = izzy_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n\n         //////\n         // After HF, create limit orders that will perfectly match\n         //////\n         BOOST_TEST_MESSAGE(\"Issuing 10 jillcoin to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 300 izzycoin to bob\");\n         issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice.get_id(),\n                                                                            jillcoin.amount(10 * JILL_PRECISION),\n                                                                            izzycoin.amount(300 *\n                                                                                            IZZY_PRECISION));\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object* alice_order_before = db.find(alice_order_id);\n         BOOST_CHECK(alice_order_before != nullptr);\n\n         // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN\n         limit_order_create_operation bob_sell_op = create_sell_operation(bob.get_id(),\n                                                                          izzycoin.amount(300 * IZZY_PRECISION),\n                                                                          jillcoin.amount(\n                                                                                  10 *\n                                                                                  JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(bob_sell_op);\n         asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type bob_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that the orders were filled by ensuring that they are no longer on the order books\n         const limit_order_object* alice_order = db.find(alice_order_id);\n         BOOST_CHECK(alice_order == nullptr);\n         const limit_order_object* bob_order = db.find(bob_order_id);\n         BOOST_CHECK(bob_order == nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving IZZYCOIN\n         asset expected_izzy_fee = izzycoin.amount(\n                 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin),\n                             (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin),\n                             (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0);\n\n         // Check the asset issuer's accumulated fees\n         BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of different maker and taker fees charged when filling limit orders after HF for a UIA\n    *\n    * Test the filling of a taker fee when the **maker** fee percent is set to 0.  This tests some optimizations\n    * in database::calculate_market_fee().\n    */\n   BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_2) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 0 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                jill_market_fee_percent);\n\n         const uint16_t IZZY_PRECISION = 1000;\n         const uint16_t izzy_market_fee_percent = 0 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"ICOIN\", izzy, charge_market_fee, price, 3,\n                                                                izzy_market_fee_percent);\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object& jillcoin = get_asset(\"JCOIN\");\n         const asset_object& izzycoin = get_asset(\"ICOIN\");\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         // Define the new taker fees\n         uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = 1 * GRAPHENE_1_PERCENT;\n\n         uint16_t izzy_maker_fee_percent = izzy_market_fee_percent;\n         uint16_t izzy_taker_fee_percent = 3 * GRAPHENE_1_PERCENT;\n\n         // Set the new taker fee for JILLCOIN\n         asset_update_operation uop;\n         uop.issuer = jill.id;\n         uop.asset_to_update = jillcoin.get_id();\n         uop.new_options.market_fee_percent = jill_maker_fee_percent;\n         uop.new_options = jillcoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, jill_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for JILLCOIN\n         asset_object updated_asset = jillcoin.get_id()(db);\n         uint16_t expected_taker_fee_percent = jill_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         // Set the new taker fee for IZZYCOIN\n         uop.issuer = izzy.id;\n         uop.asset_to_update = izzycoin.get_id();\n         uop.new_options.market_fee_percent = izzy_maker_fee_percent;\n         uop.new_options = izzycoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = izzy_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, izzy_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for IZZYCOIN\n         updated_asset = izzycoin.get_id()(db);\n         expected_taker_fee_percent = izzy_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n\n         //////\n         // After HF, create limit orders that will perfectly match\n         //////\n         BOOST_TEST_MESSAGE(\"Issuing 10 jillcoin to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 300 izzycoin to bob\");\n         issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice.get_id(),\n                                                                            jillcoin.amount(10 * JILL_PRECISION),\n                                                                            izzycoin.amount(300 *\n                                                                                            IZZY_PRECISION));\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object* alice_order_before = db.find(alice_order_id);\n         BOOST_CHECK(alice_order_before != nullptr);\n\n         // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN\n         limit_order_create_operation bob_sell_op = create_sell_operation(bob.get_id(),\n                                                                          izzycoin.amount(300 * IZZY_PRECISION),\n                                                                          jillcoin.amount(\n                                                                                  10 *\n                                                                                  JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(bob_sell_op);\n         asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type bob_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that the orders were filled by ensuring that they are no longer on the order books\n         const limit_order_object* alice_order = db.find(alice_order_id);\n         BOOST_CHECK(alice_order == nullptr);\n         const limit_order_object* bob_order = db.find(bob_order_id);\n         BOOST_CHECK(bob_order == nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving IZZYCOIN\n         asset expected_izzy_fee = izzycoin.amount(\n                 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin),\n                             (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin),\n                             (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0);\n\n         // Check the asset issuer's accumulated fees\n         BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of different maker and taker fees charged when filling limit orders after HF for a UIA\n    *\n    * Test the filling of a taker fee when the **taker** fee percent is set to 0.  This tests some optimizations\n    * in database::calculate_market_fee().\n    */\n   BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_3) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                jill_market_fee_percent);\n\n         const uint16_t IZZY_PRECISION = 1000;\n         const uint16_t izzy_market_fee_percent = 5 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"ICOIN\", izzy, charge_market_fee, price, 3,\n                                                                izzy_market_fee_percent);\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object& jillcoin = get_asset(\"JCOIN\");\n         const asset_object& izzycoin = get_asset(\"ICOIN\");\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         // Define the new taker fees\n         uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = 0 * GRAPHENE_1_PERCENT;\n\n         uint16_t izzy_maker_fee_percent = izzy_market_fee_percent;\n         uint16_t izzy_taker_fee_percent = 0 * GRAPHENE_1_PERCENT;\n\n         // Set the new taker fee for JILLCOIN\n         asset_update_operation uop;\n         uop.issuer = jill.id;\n         uop.asset_to_update = jillcoin.get_id();\n         uop.new_options.market_fee_percent = jill_maker_fee_percent;\n         uop.new_options = jillcoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, jill_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for JILLCOIN\n         asset_object updated_asset = jillcoin.get_id()(db);\n         uint16_t expected_taker_fee_percent = jill_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         // Set the new taker fee for IZZYCOIN\n         uop.issuer = izzy.id;\n         uop.asset_to_update = izzycoin.get_id();\n         uop.new_options.market_fee_percent = izzy_maker_fee_percent;\n         uop.new_options = izzycoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = izzy_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, izzy_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for IZZYCOIN\n         updated_asset = izzycoin.get_id()(db);\n         expected_taker_fee_percent = izzy_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n\n         //////\n         // After HF, create limit orders that will perfectly match\n         //////\n         BOOST_TEST_MESSAGE(\"Issuing 10 jillcoin to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 300 izzycoin to bob\");\n         issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice.get_id(),\n                                                                            jillcoin.amount(10 * JILL_PRECISION),\n                                                                            izzycoin.amount(300 *\n                                                                                            IZZY_PRECISION));\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object* alice_order_before = db.find(alice_order_id);\n         BOOST_CHECK(alice_order_before != nullptr);\n\n         // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN\n         limit_order_create_operation bob_sell_op = create_sell_operation(bob.get_id(),\n                                                                          izzycoin.amount(300 * IZZY_PRECISION),\n                                                                          jillcoin.amount(\n                                                                                  10 *\n                                                                                  JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(bob_sell_op);\n         asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type bob_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that the orders were filled by ensuring that they are no longer on the order books\n         const limit_order_object* alice_order = db.find(alice_order_id);\n         BOOST_CHECK(alice_order == nullptr);\n         const limit_order_object* bob_order = db.find(bob_order_id);\n         BOOST_CHECK(bob_order == nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving IZZYCOIN\n         asset expected_izzy_fee = izzycoin.amount(\n                 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin),\n                             (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin),\n                             (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0);\n\n         // Check the asset issuer's accumulated fees\n         BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of **default** taker fees charged when filling limit orders after HF for a UIA.\n    *\n    * This test is similar to simple_match_and_fill_with_different_fees_uia_1\n    * except that the taker fee is not explicitly set and instead defaults to the maker fee.\n    */\n   BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_4) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                  jill_market_fee_percent);\n\n         const uint16_t IZZY_PRECISION = 1000;\n         const uint16_t izzy_market_fee_percent = 5 * GRAPHENE_1_PERCENT;\n         create_user_issued_asset(\"ICOIN\", izzy, charge_market_fee, price, 3,\n                                  izzy_market_fee_percent);\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object& jillcoin = get_asset(\"JCOIN\");\n         const asset_object& izzycoin = get_asset(\"ICOIN\");\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test that default taker values has not been set\n         //////\n         // The taker fees should automatically default to maker fees if the taker fee is not explicitly set\n         // UNUSED: uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = jill_market_fee_percent;\n\n         uint16_t izzy_maker_fee_percent = izzy_market_fee_percent;\n         // UNUSED: uint16_t izzy_taker_fee_percent = izzy_market_fee_percent;\n\n         // Check the taker fee for JCOIN: it should still not be set\n         asset_object updated_asset = jillcoin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         // Check the taker fee for ICOIN: it should still not be set\n         updated_asset = izzycoin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n\n         //////\n         // After HF, create limit orders that will perfectly match\n         //////\n         BOOST_TEST_MESSAGE(\"Issuing 10 jillcoin to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 300 izzycoin to bob\");\n         issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice.get_id(),\n                                                                            jillcoin.amount(10 * JILL_PRECISION),\n                                                                            izzycoin.amount(300 *\n                                                                                            IZZY_PRECISION));\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object* alice_order_before = db.find(alice_order_id);\n         BOOST_CHECK(alice_order_before != nullptr);\n\n         // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN\n         limit_order_create_operation bob_sell_op = create_sell_operation(bob.get_id(),\n                                                                          izzycoin.amount(300 * IZZY_PRECISION),\n                                                                          jillcoin.amount(\n                                                                                  10 *\n                                                                                  JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(bob_sell_op);\n         asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type bob_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that the orders were filled by ensuring that they are no longer on the order books\n         const limit_order_object* alice_order = db.find(alice_order_id);\n         BOOST_CHECK(alice_order == nullptr);\n         const limit_order_object* bob_order = db.find(bob_order_id);\n         BOOST_CHECK(bob_order == nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving IZZYCOIN\n         asset expected_izzy_fee = izzycoin.amount(\n                 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin),\n                             (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin),\n                             (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0);\n\n         // Check the asset issuer's accumulated fees\n         BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n\n   /**\n    * Test of different maker and taker fees charged when filling limit orders after HF for a smart asset\n    */\n   BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_smart_asset) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const asset_object jillcoin = create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                jill_market_fee_percent);\n\n         const uint16_t SMARTBIT_PRECISION = 10000;\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT\", smartissuer.get_id(), smartbit_market_fee_percent,\n                                                        charge_market_fee, 4);\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object &smartbit = get_asset(\"SMARTBIT\");\n\n         const auto &core = asset_id_type()(db);\n\n         update_feed_producers(smartbit, {feedproducer.get_id()});\n\n         price_feed current_feed;\n         current_feed.settlement_price = smartbit.amount(100) / core.amount(100);\n         current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n         publish_feed(smartbit, feedproducer, current_feed);\n\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         // Define the new taker fees\n         uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = jill_maker_fee_percent / 2;\n\n         uint16_t smartbit_maker_fee_percent = 1 * GRAPHENE_1_PERCENT;\n         uint16_t smartbit_taker_fee_percent = 3 * GRAPHENE_1_PERCENT;\n\n         // Set the new taker fee for JILLCOIN\n         asset_update_operation uop;\n         uop.issuer = jill.id;\n         uop.asset_to_update = jillcoin.get_id();\n         uop.new_options = jillcoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, jill_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for JILLCOIN\n         asset_object updated_asset = jillcoin.get_id()(db);\n         uint16_t expected_taker_fee_percent = jill_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n\n         // Set the new taker fee for SMARTBIT\n         uop = asset_update_operation();\n         uop.issuer = smartissuer.id;\n         uop.asset_to_update = smartbit.get_id();\n         uop.new_options = smartbit.options;\n         uop.new_options.market_fee_percent = smartbit_maker_fee_percent;\n         uop.new_options.extensions.value.taker_fee_percent = smartbit_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for SMARTBIT\n         updated_asset = smartbit.get_id()(db);\n         expected_taker_fee_percent = smartbit_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         // Check the maker fee for SMARTBIT\n         updated_asset = smartbit.get_id()(db);\n         expected_taker_fee_percent = smartbit_maker_fee_percent;\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, updated_asset.options.market_fee_percent);\n\n\n         //////\n         // After HF, create limit orders that will perfectly match\n         //////\n         BOOST_TEST_MESSAGE(\"Issuing 10 jillcoin to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 300 SMARTBIT to bob\");\n         transfer(committee_account, bob.get_id(), asset(10000000));\n         publish_feed(smartbit, feedproducer, current_feed); // Publish a recent feed\n         borrow(bob, smartbit.amount(300 * SMARTBIT_PRECISION), asset(2 * 300 * SMARTBIT_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 300 * SMARTBIT_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 SMARTBIT\n         limit_order_create_operation alice_sell_op = create_sell_operation(alice.get_id(),\n                                                                            jillcoin.amount(10 * JILL_PRECISION),\n                                                                            smartbit.amount(300 * SMARTBIT_PRECISION));\n         trx.clear();\n         trx.operations.push_back(alice_sell_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type alice_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object *alice_order_before = db.find(alice_order_id);\n         BOOST_CHECK(alice_order_before != nullptr);\n\n\n         // Bob is willing to sell 300 SMARTBIT for at least 10 JILLCOIN\n         limit_order_create_operation bob_sell_op\n                 = create_sell_operation(bob.get_id(), smartbit.amount(300 * SMARTBIT_PRECISION),\n                                         jillcoin.amount(10 * JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(bob_sell_op);\n         asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type bob_order_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that the orders were filled by ensuring that they are no longer on the order books\n         const limit_order_object *alice_order = db.find(alice_order_id);\n         BOOST_CHECK(alice_order == nullptr);\n         const limit_order_object *bob_order = db.find(bob_order_id);\n         BOOST_CHECK(bob_order == nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving SMARTBIT\n         asset expected_smartbit_fee = smartbit.amount(\n                 300 * SMARTBIT_PRECISION * smartbit_maker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit),\n                             (300 * SMARTBIT_PRECISION) - alice_sell_fee.amount.value -\n                             expected_smartbit_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin),\n                             (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0);\n\n         // Check the asset issuer's accumulated fees\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee.amount);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of different maker and taker fees charged when filling limit orders after HF for a smart asset\n    * and a user-issued asset\n    *\n    * 1. (Order 1) An order will be placed to offer JCOIN\n    *\n    * 2. (Order 2) A matching-order will be placed to offer SMARTBIT.\n    *     Order 2 is large enough that it should be partially filled, and Order 1 will be completely filled.\n    *     Order 1 should be charged a maker fee, and Order 2 should be charged a taker fee.\n    *     Order 2 should remain on the book.\n    *\n    * 3. (Order 3) A matching order will be placed to offer JCOIN.\n    *     Order 3 should be charged a taker fee, and Order 2 should be charged a maker fee.\n    *\n    * Summary: Order 2 should be charged a taker fee when matching Order 1,\n    * and Order 2 should be charged a maker fee when matching Order 3.\n    */\n   BOOST_AUTO_TEST_CASE(partial_maker_partial_taker_fills_1) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob)(charlie));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const asset_object jillcoin = create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                jill_market_fee_percent);\n\n         const uint16_t SMARTBIT_PRECISION = 10000;\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT\", smartissuer.get_id(), smartbit_market_fee_percent,\n                                                        charge_market_fee, 4);\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object &smartbit = get_asset(\"SMARTBIT\");\n\n         const auto &core = asset_id_type()(db);\n\n         update_feed_producers(smartbit, {feedproducer.get_id()});\n\n         price_feed current_feed;\n         current_feed.settlement_price = smartbit.amount(100) / core.amount(100);\n         current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n         publish_feed(smartbit, feedproducer, current_feed);\n\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // Advance to activate hardfork\n         //////\n         generate_blocks(HARDFORK_BSIP_81_TIME);\n         generate_block();\n         trx.clear();\n         set_expiration(db, trx);\n\n\n         //////\n         // After HF, test that new values can be set\n         //////\n         // Define the new taker fees\n         uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = jill_maker_fee_percent / 2;\n\n         uint16_t smartbit_maker_fee_percent = 1 * GRAPHENE_1_PERCENT;\n         uint16_t smartbit_taker_fee_percent = 3 * GRAPHENE_1_PERCENT;\n\n         // Set the new taker fee for JILLCOIN\n         asset_update_operation uop;\n         uop.issuer = jill.id;\n         uop.asset_to_update = jillcoin.get_id();\n         uop.new_options = jillcoin.options;\n         uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, jill_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for JILLCOIN\n         asset_object updated_asset = jillcoin.get_id()(db);\n         uint16_t expected_taker_fee_percent = jill_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         // Check the maker fee for JILLCOIN\n         uint16_t expected_maker_fee_percent = jill_maker_fee_percent;\n         BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent);\n\n\n         // Set the new taker fee for SMARTBIT\n         uop = asset_update_operation();\n         uop.issuer = smartissuer.id;\n         uop.asset_to_update = smartbit.get_id();\n         uop.new_options = smartbit.options;\n         uop.new_options.market_fee_percent = smartbit_maker_fee_percent;\n         uop.new_options.extensions.value.taker_fee_percent = smartbit_taker_fee_percent;\n\n         trx.clear();\n         trx.operations.push_back(uop);\n         db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, smartissuer_private_key);\n         PUSH_TX(db, trx); // No exception should be thrown\n\n         // Check the taker fee for SMARTBIT\n         updated_asset = smartbit.get_id()(db);\n         expected_taker_fee_percent = smartbit_taker_fee_percent;\n         BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid());\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent);\n\n         // Check the maker fee for SMARTBIT\n         updated_asset = smartbit.get_id()(db);\n         expected_taker_fee_percent = smartbit_maker_fee_percent;\n         BOOST_CHECK_EQUAL(expected_taker_fee_percent, updated_asset.options.market_fee_percent);\n\n\n         //////\n         // Create Orders 1 and 2 to match.\n         // Order 1 will be completely filled, and Order 2 will be partially filled.\n         //////\n         // Initialize token balance of actors\n         BOOST_TEST_MESSAGE(\"Issuing 10 JCOIN to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 600 SMARTBIT to bob\");\n         transfer(committee_account, bob.get_id(), asset(2 * 1000 * SMARTBIT_PRECISION));\n         publish_feed(smartbit, feedproducer, current_feed); // Publish a recent feed\n         borrow(bob, smartbit.amount(600 * SMARTBIT_PRECISION), asset(2 * 600 * SMARTBIT_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 600 * SMARTBIT_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 SMARTBIT\n         limit_order_create_operation order_1_op = create_sell_operation(alice.get_id(),\n                                                                            jillcoin.amount(10 * JILL_PRECISION),\n                                                                            smartbit.amount(300 * SMARTBIT_PRECISION));\n         trx.clear();\n         trx.operations.push_back(order_1_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type order_1_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object *order_1_before = db.find(order_1_id);\n         BOOST_CHECK(order_1_before != nullptr);\n\n\n         // Bob is willing to sell 600 SMARTBIT for at least 20 JILLCOIN\n         limit_order_create_operation order_2_op\n                 = create_sell_operation(bob.get_id(), smartbit.amount(600 * SMARTBIT_PRECISION),\n                                         jillcoin.amount(20 * JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(order_2_op);\n         asset order_2_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type order_2_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that order 1 was completely filled by ensuring that they it is no longer on the order book\n         const limit_order_object *order_1 = db.find(order_1_id);\n         BOOST_CHECK(order_1 == nullptr);\n         // Check that order 2  was partially filled by ensuring that they it is still on the order book\n         const limit_order_object *order_2 = db.find(order_2_id);\n         BOOST_CHECK(order_2 != nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving SMARTBIT\n         asset expected_smartbit_fee = smartbit.amount(\n                 300 * SMARTBIT_PRECISION * smartbit_maker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_alice_balance_after_order_2 =\n                 (300 * SMARTBIT_PRECISION) - alice_sell_fee.amount.value - expected_smartbit_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), expected_alice_balance_after_order_2);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_bob_balance_after_order_2 =\n                 (10 * JILL_PRECISION) - order_2_sell_fee.amount.value - expected_jill_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_2);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0);\n\n         // Check the asset issuer's accumulated fees\n         share_type expected_smartbit_fee_after_order_2 = expected_smartbit_fee.amount;\n         share_type expected_jill_fee_after_order_2 = expected_jill_fee.amount;\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_2);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_2);\n\n\n         //////\n         // Create Order 3 to match the remainder of match Order 2\n         //////\n         // Initialize token balance of actors\n         BOOST_TEST_MESSAGE(\"Issuing 5 JCOIN to charlie\");\n         trx.clear();\n         issue_uia(charlie, jillcoin.amount(5 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking charlie's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 5 * JILL_PRECISION);\n\n         // Charlie is is willing to sell 5 JILLCOIN for at least 150 SMARTBIT\n         limit_order_create_operation order_3_op = create_sell_operation(charlie.get_id(),\n                                                                         jillcoin.amount(5 * JILL_PRECISION),\n                                                                         smartbit.amount(150 * SMARTBIT_PRECISION));\n         trx.clear();\n         trx.operations.push_back(order_3_op);\n         asset charlie_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, charlie_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type order_3_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Order 3 should be completely filled\n         const limit_order_object *order_3 = db.find(order_3_id);\n         BOOST_CHECK(order_3 == nullptr);\n\n         // Order 2 should be partially filled and still present on the order books\n         const limit_order_object *order_2_after = db.find(order_2_id);\n         BOOST_CHECK(order_2_after != nullptr);\n\n         // Check the new balance of the taker\n         // Charlie was the taker; he is receiving SMARTBIT\n         expected_smartbit_fee = smartbit.amount(\n                 150 * SMARTBIT_PRECISION * smartbit_taker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_charlie_balance_after_order_3 =\n                 (150 * SMARTBIT_PRECISION) - charlie_sell_fee.amount.value - expected_smartbit_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, smartbit), expected_charlie_balance_after_order_3);\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 0);\n\n         // Check the new balance of the maker\n         // Bob was the maker; he is receiving JILLCOIN\n         asset expected_jill_order_3_fee = jillcoin.amount(\n                 5 * JILL_PRECISION * jill_maker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_bob_balance_after_order_3 =\n                 expected_bob_balance_after_order_2\n                 + (5 * JILL_PRECISION) - expected_jill_order_3_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_3);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0);\n\n         // Check the asset issuer's accumulated fees\n         share_type expected_smartbit_fee_after_order_3 =\n                 expected_smartbit_fee_after_order_2 + expected_smartbit_fee.amount;\n         share_type expected_jill_fee_after_order_3 = expected_jill_fee_after_order_2 + expected_jill_order_3_fee.amount;\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_3);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_3);\n\n      } FC_LOG_AND_RETHROW()\n   }\n\n\n   /**\n    * Test of **default** taker fees charged when filling limit orders after HF for a smart asset\n    * and a user-issued asset\n    *\n    * This test is similar to partial_maker_partial_taker_fills_1 except that\n    * (a) the taker fee is not explicitly set and instead defaults to the maker fee, and\n    * (b) Orders 1 and 2 are placed before the HF and Order 3 is placed after the HF.\n    *\n    * 1. (Order 1) An order will be placed to offer JCOIN\n    *\n    * 2. (Order 2) A matching-order will be placed to offer SMARTBIT.\n    *     Order 2 is large enough that it should be partially filled, and Order 1 will be completely filled.\n    *     Order 1 should be charged a maker fee, and Order 2 should be charged a taker fee.\n    *     Order 2 should remain on the book.\n    *\n    * 3. (Order 3) A matching order will be placed to offer JCOIN.\n    *     Order 3 should be charged a taker fee, and Order 2 should be charged a maker fee.\n    *\n    * Summary: Order 2 should be charged a taker fee when matching Order 1,\n    * and Order 2 should be charged a maker fee when matching Order 3.\n    */\n   BOOST_AUTO_TEST_CASE(partial_maker_partial_taker_fills_2) {\n      try {\n         // Initialize for the current time\n         trx.clear();\n         set_expiration(db, trx);\n\n         // Initialize actors\n         ACTORS((jill)(izzy)(alice)(bob)(charlie));\n         ACTORS((smartissuer)(feedproducer));\n\n         // Initialize tokens\n         price price(asset(1, asset_id_type(1)), asset(1));\n         const uint16_t JILL_PRECISION = 100;\n         const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         const asset_object jillcoin = create_user_issued_asset(\"JCOIN\", jill, charge_market_fee, price, 2,\n                                                                jill_market_fee_percent);\n\n         const uint16_t SMARTBIT_PRECISION = 10000;\n         const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT;\n         create_bitasset(\"SMARTBIT\", smartissuer.get_id(), smartbit_market_fee_percent,\n                         charge_market_fee, 4);\n\n         uint16_t jill_maker_fee_percent = jill_market_fee_percent;\n         uint16_t jill_taker_fee_percent = jill_market_fee_percent;\n\n         uint16_t smartbit_maker_fee_percent = smartbit_market_fee_percent;\n         uint16_t smartbit_taker_fee_percent = smartbit_market_fee_percent;\n\n\n         // Obtain asset object after a block is generated to obtain the final object that is commited to the database\n         generate_block();\n         const asset_object &smartbit = get_asset(\"SMARTBIT\");\n\n         const auto &core = asset_id_type()(db);\n\n         update_feed_producers(smartbit, {feedproducer.get_id()});\n\n         price_feed current_feed;\n         current_feed.settlement_price = smartbit.amount(100) / core.amount(100);\n         current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default\n         publish_feed(smartbit, feedproducer, current_feed);\n\n         FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price);\n\n\n         //////\n         // Create Orders 1 and 2 to match.\n         // Order 1 will be completely filled, and Order 2 will be partially filled.\n         //////\n         // Initialize token balance of actors\n         BOOST_TEST_MESSAGE(\"Issuing 10 JCOIN to alice\");\n         issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking alice's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION);\n\n         BOOST_TEST_MESSAGE(\"Issuing 600 SMARTBIT to bob\");\n         transfer(committee_account, bob.get_id(), asset(2 * 1000 * SMARTBIT_PRECISION));\n         publish_feed(smartbit, feedproducer, current_feed); // Publish a recent feed\n         borrow(bob, smartbit.amount(600 * SMARTBIT_PRECISION), asset(2 * 600 * SMARTBIT_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking bob's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 600 * SMARTBIT_PRECISION);\n\n         // Alice and Bob place orders which match, and are completely filled by each other\n         // Alice is willing to sell 10 JILLCOIN for at least 300 SMARTBIT\n         limit_order_create_operation order_1_op = create_sell_operation(alice.get_id(),\n                                                                         jillcoin.amount(10 * JILL_PRECISION),\n                                                                         smartbit.amount(300 * SMARTBIT_PRECISION));\n         trx.clear();\n         trx.operations.push_back(order_1_op);\n         asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, alice_private_key);\n         processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type order_1_id { ptx.operation_results[0].get<object_id_type>() };\n\n         const limit_order_object *order_1_before = db.find(order_1_id);\n         BOOST_CHECK(order_1_before != nullptr);\n\n\n         // Bob is willing to sell 600 SMARTBIT for at least 20 JILLCOIN\n         limit_order_create_operation order_2_op\n                 = create_sell_operation(bob.get_id(), smartbit.amount(600 * SMARTBIT_PRECISION),\n                                         jillcoin.amount(20 * JILL_PRECISION));\n         trx.clear();\n         trx.operations.push_back(order_2_op);\n         asset order_2_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, bob_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type order_2_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Check that order 1 was completely filled by ensuring that they it is no longer on the order book\n         const limit_order_object *order_1 = db.find(order_1_id);\n         BOOST_CHECK(order_1 == nullptr);\n         // Check that order 2  was partially filled by ensuring that they it is still on the order book\n         const limit_order_object *order_2 = db.find(order_2_id);\n         BOOST_CHECK(order_2 != nullptr);\n\n\n         // Check the new balances of the maker\n         // Alice was the maker; she is receiving SMARTBIT\n         asset expected_smartbit_fee = smartbit.amount(\n                 300 * SMARTBIT_PRECISION * smartbit_maker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_alice_balance_after_order_2 =\n                 (300 * SMARTBIT_PRECISION) - alice_sell_fee.amount.value - expected_smartbit_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), expected_alice_balance_after_order_2);\n         BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0);\n\n         // Check the new balance of the taker\n         // Bob was the taker; he is receiving JILLCOIN\n         asset expected_jill_fee = jillcoin.amount(\n                 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_bob_balance_after_order_2 =\n                 (10 * JILL_PRECISION) - order_2_sell_fee.amount.value - expected_jill_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_2);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0);\n\n         // Check the asset issuer's accumulated fees\n         share_type expected_smartbit_fee_after_order_2 = expected_smartbit_fee.amount;\n         share_type expected_jill_fee_after_order_2 = expected_jill_fee.amount;\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_2);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_2);\n\n\n         //////\n         // After HF, The taker fees should automatically default to maker fees when the taker fee is not explicitly set\n         //////\n         // Check the taker fee for JILLCOIN\n         asset_object updated_asset = jillcoin.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         // Check the maker fee for JILLCOIN\n         uint16_t expected_maker_fee_percent = jill_maker_fee_percent;\n         BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent);\n\n         // Check the taker fee for SMARTBIT\n         updated_asset = smartbit.get_id()(db);\n         BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid());\n\n         // Check the maker fee for SMARTBIT\n         expected_maker_fee_percent = smartbit_maker_fee_percent;\n         BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent);\n\n\n         //////\n         // Create Order 3 to match the remainder of match Order 2\n         //////\n         // Initialize token balance of actors\n         BOOST_TEST_MESSAGE(\"Issuing 5 JCOIN to charlie\");\n         trx.clear();\n         issue_uia(charlie, jillcoin.amount(5 * JILL_PRECISION));\n         BOOST_TEST_MESSAGE(\"Checking charlie's balance\");\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 5 * JILL_PRECISION);\n\n         // Charlie is is willing to sell 5 JILLCOIN for at least 150 SMARTBIT\n         limit_order_create_operation order_3_op = create_sell_operation(charlie.get_id(),\n                                                                         jillcoin.amount(5 * JILL_PRECISION),\n                                                                         smartbit.amount(150 * SMARTBIT_PRECISION));\n         trx.clear();\n         trx.operations.push_back(order_3_op);\n         asset charlie_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back());\n         sign(trx, charlie_private_key);\n         ptx = PUSH_TX(db, trx); // No exception should be thrown\n         limit_order_id_type order_3_id { ptx.operation_results[0].get<object_id_type>() };\n\n         // Order 3 should be completely filled\n         const limit_order_object *order_3 = db.find(order_3_id);\n         BOOST_CHECK(order_3 == nullptr);\n\n         // Order 2 should be partially filled and still present on the order books\n         const limit_order_object *order_2_after = db.find(order_2_id);\n         BOOST_CHECK(order_2_after != nullptr);\n\n         // Check the new balance of the taker\n         // Charlie was the taker; he is receiving SMARTBIT\n         expected_smartbit_fee = smartbit.amount(\n                 150 * SMARTBIT_PRECISION * smartbit_taker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_charlie_balance_after_order_3 =\n                 (150 * SMARTBIT_PRECISION) - charlie_sell_fee.amount.value - expected_smartbit_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, smartbit), expected_charlie_balance_after_order_3);\n         BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 0);\n\n         // Check the new balance of the maker\n         // Bob was the maker; he is receiving JILLCOIN\n         asset expected_jill_order_3_fee = jillcoin.amount(\n                 5 * JILL_PRECISION * jill_maker_fee_percent / GRAPHENE_100_PERCENT);\n         int64_t expected_bob_balance_after_order_3 =\n                 expected_bob_balance_after_order_2\n                 + (5 * JILL_PRECISION) - expected_jill_order_3_fee.amount.value;\n         BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_3);\n         BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0);\n\n         // Check the asset issuer's accumulated fees\n         share_type expected_smartbit_fee_after_order_3 =\n                 expected_smartbit_fee_after_order_2 + expected_smartbit_fee.amount;\n         share_type expected_jill_fee_after_order_3 = expected_jill_fee_after_order_2 + expected_jill_order_3_fee.amount;\n         BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_3);\n         BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_3);\n         wdump((jillcoin.dynamic_asset_data_id(db).accumulated_fees)(expected_jill_fee_after_order_3)(expected_jill_fee_after_order_2)(expected_jill_fee.amount));\n         wdump((get_asset(\"JCOIN\").dynamic_asset_data_id(db).accumulated_fees));\n\n      } FC_LOG_AND_RETHROW()\n   }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/smartcoin_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/database_api.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/exceptions.hpp>\n\n#include <iostream>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\n\nBOOST_FIXTURE_TEST_SUITE(smartcoin_tests, database_fixture)\n\n\nBOOST_AUTO_TEST_CASE(bsip36)\n{\n   try\n   {\n      /* Issue affects only smartcoins(market pegged assets feeded by active witnesses or committee members).\n       * Test case reproduces, advance to hardfork and check if solved after it.\n       */\n\n      /* References:\n       * BSIP 36: https://github.com/bitshares/bsips/blob/master/bsip-0036.md\n       * and the former: CORE Issue 518: https://github.com/bitshares/bitshares-core/issues/518\n       */\n\n      // Create 12 accounts to be witnesses under our control\n      ACTORS( (witness0)(witness1)(witness2)(witness3)(witness4)(witness5)\n                   (witness6)(witness7)(witness8)(witness9)(witness10)(witness11) );\n\n      // Upgrade all accounts to LTM\n      upgrade_to_lifetime_member(witness0_id);\n      upgrade_to_lifetime_member(witness1_id);\n      upgrade_to_lifetime_member(witness2_id);\n      upgrade_to_lifetime_member(witness3_id);\n      upgrade_to_lifetime_member(witness4_id);\n      upgrade_to_lifetime_member(witness5_id);\n      upgrade_to_lifetime_member(witness6_id);\n      upgrade_to_lifetime_member(witness7_id);\n      upgrade_to_lifetime_member(witness8_id);\n      upgrade_to_lifetime_member(witness9_id);\n      upgrade_to_lifetime_member(witness10_id);\n      upgrade_to_lifetime_member(witness11_id);\n\n      // Create all the witnesses\n      const witness_id_type witness0_witness_id = create_witness(witness0_id, witness0_private_key).get_id();\n      const witness_id_type witness1_witness_id = create_witness(witness1_id, witness1_private_key).get_id();\n      const witness_id_type witness2_witness_id = create_witness(witness2_id, witness2_private_key).get_id();\n      const witness_id_type witness3_witness_id = create_witness(witness3_id, witness3_private_key).get_id();\n      const witness_id_type witness4_witness_id = create_witness(witness4_id, witness4_private_key).get_id();\n      const witness_id_type witness5_witness_id = create_witness(witness5_id, witness5_private_key).get_id();\n      const witness_id_type witness6_witness_id = create_witness(witness6_id, witness6_private_key).get_id();\n      const witness_id_type witness7_witness_id = create_witness(witness7_id, witness7_private_key).get_id();\n      const witness_id_type witness8_witness_id = create_witness(witness8_id, witness8_private_key).get_id();\n      const witness_id_type witness9_witness_id = create_witness(witness9_id, witness9_private_key).get_id();\n      const witness_id_type witness10_witness_id = create_witness(witness10_id, witness10_private_key).get_id();\n      const witness_id_type witness11_witness_id = create_witness(witness11_id, witness11_private_key).get_id();\n\n      // Create a vector with private key of all witnesses, will be used to activate 11 witnesses at a time\n      const vector <fc::ecc::private_key> private_keys = {\n            witness0_private_key,\n            witness1_private_key,\n            witness2_private_key,\n            witness3_private_key,\n            witness4_private_key,\n            witness5_private_key,\n            witness6_private_key,\n            witness7_private_key,\n            witness8_private_key,\n            witness9_private_key,\n            witness10_private_key\n      };\n\n      // create a map with account id and witness id of the first 11 witnesses\n      const flat_map <account_id_type, witness_id_type> witness_map = {\n         {witness0_id, witness0_witness_id},\n         {witness1_id, witness1_witness_id},\n         {witness2_id, witness2_witness_id},\n         {witness3_id, witness3_witness_id},\n         {witness4_id, witness4_witness_id},\n         {witness5_id, witness5_witness_id},\n         {witness6_id, witness6_witness_id},\n         {witness7_id, witness7_witness_id},\n         {witness8_id, witness8_witness_id},\n         {witness9_id, witness9_witness_id},\n         {witness10_id, witness10_witness_id}\n      };\n\n      // Create the asset\n      const asset_id_type bit_usd_id = create_bitasset(\"USDBIT\").get_id();\n\n      // Update the asset to be fed by system witnesses\n      asset_update_operation op;\n      const asset_object &asset_obj = bit_usd_id(db);\n      op.asset_to_update = bit_usd_id;\n      op.issuer = asset_obj.issuer;\n      op.new_options = asset_obj.options;\n      op.new_options.flags &= witness_fed_asset;\n      op.new_options.issuer_permissions &= witness_fed_asset;\n      trx.operations.push_back(op);\n      PUSH_TX(db, trx, ~0);\n      generate_block();\n      trx.clear();\n\n      // Check current default witnesses, default chain is configured with 10 witnesses\n      auto witnesses = db.get_global_properties().active_witnesses;\n      BOOST_CHECK_EQUAL(witnesses.size(), INITIAL_WITNESS_COUNT);\n      BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 1u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 2u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 3u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 4u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 5u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 6u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 7u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 8u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 9u);\n\n      // We need to activate 11 witnesses by voting for each of them.\n      // Each witness is voted with incremental stake so last witness created will be the ones with more votes\n\n      // by default we have 9 witnesses, we need to vote for desired witness count (11) to increase them\n      vote_for_committee_and_witnesses(9, 11);\n\n      int c = 0;\n      for (auto l : witness_map) {\n         // voting stake have step of 100\n         // so vote_for_committee_and_witnesses() with stake=10 does not affect the expected result\n         int stake = 100 * (c + 1);\n         transfer(committee_account, l.first, asset(stake));\n         {\n            account_update_operation op;\n            op.account = l.first;\n            op.new_options = l.first(db).options;\n            op.new_options->votes.insert(l.second(db).vote_id);\n            op.new_options->num_witness = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),\n                                                        [](vote_id_type id) {\n                                                           return id.type() == vote_id_type::witness;\n                                                        });\n            trx.operations.push_back(op);\n            sign(trx, private_keys.at(c));\n            PUSH_TX(db, trx);\n            trx.clear();\n         }\n         ++c;\n      }\n\n      // Trigger the new witnesses\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      // Check my witnesses are now in control of the system\n      witnesses = db.get_global_properties().active_witnesses;\n      BOOST_CHECK_EQUAL(witnesses.size(), 11u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 11u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 12u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 13u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 14u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 15u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 16u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 17u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 18u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 19u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[9].instance.value, 20u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[10].instance.value, 21u);\n\n      // Adding 2 feeds with witnesses 0 and 1, checking if they get inserted\n      const asset_object &core = asset_id_type()(db);\n      price_feed feed;\n      feed.settlement_price = bit_usd_id(db).amount(1) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness0_id(db), feed);\n\n      asset_bitasset_data_object bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 1u);\n      auto itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 16u);\n\n      feed.settlement_price = bit_usd_id(db).amount(2) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness1_id(db), feed);\n\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 16u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 17u);\n\n      // Activate witness11 with voting stake, will kick the witness with less votes(witness0) out of the active list\n      transfer(committee_account, witness11_id, asset(1200));\n      set_expiration(db, trx);\n      {\n         account_update_operation op;\n         op.account = witness11_id;\n         op.new_options = witness11_id(db).options;\n         op.new_options->votes.insert(witness11_witness_id(db).vote_id);\n         op.new_options->num_witness = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),\n                                                     [](vote_id_type id) {\n                                                        return id.type() == vote_id_type::witness;\n                                                     });\n         trx.operations.push_back(op);\n         sign(trx, witness11_private_key);\n         PUSH_TX(db, trx);\n         trx.clear();\n      }\n\n      // Trigger new witness\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      // Check active witness list now\n      witnesses = db.get_global_properties().active_witnesses;\n      BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 12u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 13u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 14u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 15u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 16u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 17u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 18u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 19u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 20u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[9].instance.value, 21u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[10].instance.value, 22u);\n\n      // witness0 has been removed but it was a feeder before\n      // Feed persist in the blockchain, this reproduces the issue\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 16u);\n\n      // Feed persist after expiration\n      const auto feed_lifetime = bit_usd_id(db).bitasset_data(db).options.feed_lifetime_sec;\n      generate_blocks(db.head_block_time() + feed_lifetime + 1);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 16u);\n\n      // Other witnesses add more feeds\n      feed.settlement_price = bit_usd_id(db).amount(4) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness2_id(db), feed);\n      feed.settlement_price = bit_usd_id(db).amount(3) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness3_id(db), feed);\n\n      // But the one from witness0 is never removed\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 4u);\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 16u);\n\n      // Feed from witness1 is also expired but never deleted\n      // All feeds should be deleted at this point\n      const auto minimum_feeds = bit_usd_id(db).bitasset_data(db).options.minimum_feeds;\n      BOOST_CHECK_EQUAL(minimum_feeds, 1u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 17u);\n\n      // Advancing into HF time\n      generate_blocks(HARDFORK_CORE_518_TIME);\n\n      // Advancing to next maint\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      //  All expired feeds are deleted\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 0u);\n\n      // witness1 start feed producing again\n      feed.settlement_price = bit_usd_id(db).amount(1) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness1_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 1u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n\n      // generate some blocks up to expiration but feed will not be deleted yet as need next maint time\n      generate_blocks(itr[0].second.first + feed_lifetime + 1);\n\n      // add another feed with witness2\n      feed.settlement_price = bit_usd_id(db).amount(1) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness2_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 18u);\n\n      // make the first feed expire\n      generate_blocks(itr[0].second.first + feed_lifetime + 1);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      // feed from witness0 expires and gets deleted, feed from witness is on time so persist\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 1u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 18u);\n\n      // expire everything\n      generate_blocks(itr[0].second.first + feed_lifetime + 1);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 0u);\n\n      // add new feed with witness1\n      feed.settlement_price = bit_usd_id(db).amount(1) / core.amount(5);\n      publish_feed(bit_usd_id(db), witness1_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 1u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n\n      // Reactivate witness0\n      transfer(committee_account, witness0_id, asset(1000));\n      set_expiration(db, trx);\n      {\n         account_update_operation op;\n         op.account = witness0_id;\n         op.new_options = witness0_id(db).options;\n         op.new_options->votes.insert(witness0_witness_id(db).vote_id);\n         op.new_options->num_witness = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(),\n                                                     [](vote_id_type id) {\n                                                        return id.type() == vote_id_type::witness;\n                                                     });\n         trx.operations.push_back(op);\n         sign(trx, witness0_private_key);\n         PUSH_TX(db, trx);\n         trx.clear();\n      }\n\n      // This will deactivate witness1 as it is the one with less votes\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      // Checking\n      witnesses = db.get_global_properties().active_witnesses;\n      BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 11u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 13u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 14u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 15u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 16u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 17u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 18u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 19u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 20u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[9].instance.value, 21u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[10].instance.value, 22u);\n\n      // feed from witness1 is still here as the witness is no longer a producer but the feed is not yet expired\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 1u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n\n      // make feed from witness1 expire\n      generate_blocks(itr[0].second.first + feed_lifetime + 1);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 0u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(bsip36_update_feed_producers)\n{\n   try\n   {\n      /* For MPA fed by non witnesses or non committee mmembers but by feed producers changes should do nothing */\n      ACTORS( (sam)(alice)(paul)(bob) );\n\n      // Create the asset\n      const asset_id_type bit_usd_id = create_bitasset(\"USDBIT\").get_id();\n\n      // Update asset issuer\n      const asset_object &asset_obj = bit_usd_id(db);\n      {\n         asset_update_operation op;\n         op.asset_to_update = bit_usd_id;\n         op.issuer = asset_obj.issuer;\n         op.new_issuer = bob_id;\n         op.new_options = asset_obj.options;\n         op.new_options.flags &= ~witness_fed_asset;\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n         generate_block();\n         trx.clear();\n      }\n\n      // Add 3 feed producers for asset\n      {\n         asset_update_feed_producers_operation op;\n         op.asset_to_update = bit_usd_id;\n         op.issuer = bob_id;\n         op.new_feed_producers = {sam_id, alice_id, paul_id};\n         trx.operations.push_back(op);\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n         generate_block();\n         trx.clear();\n      }\n\n      // Bitshares will create entries in the field feed after feed producers are added\n      auto bitasset_data = bit_usd_id(db).bitasset_data(db);\n\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 3u);\n      auto itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 16u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 17u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 18u);\n\n      // Removing a feed producer\n      {\n         asset_update_feed_producers_operation op;\n         op.asset_to_update = bit_usd_id;\n         op.issuer = bob_id;\n         op.new_feed_producers = {alice_id, paul_id};\n         trx.operations.push_back(op);\n         sign(trx, bob_private_key);\n         PUSH_TX(db, trx);\n         generate_block();\n         trx.clear();\n      }\n\n      // Feed for removed producer is removed\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 18u);\n\n      // Feed persist after expiration\n      const auto feed_lifetime = bit_usd_id(db).bitasset_data(db).options.feed_lifetime_sec;\n      generate_blocks(db.head_block_time() + feed_lifetime + 1);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 18u);\n\n      // Advancing into HF time\n      generate_blocks(HARDFORK_CORE_518_TIME);\n\n      // Advancing to next maint\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n      // Expired feeds persist, no changes\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 17u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 18u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(bsip36_additional)\n{\n   try\n   {\n      /* Check impact of bsip36 with multiple feeds */\n      INVOKE( bsip36 );\n\n      // get the stuff needed from invoked test\n      const asset_id_type bit_usd_id = get_asset(\"USDBIT\").get_id();\n      const asset_id_type core_id = asset_id_type();\n      const account_id_type witness5_id= get_account(\"witness5\").get_id();\n      const account_id_type witness6_id= get_account(\"witness6\").get_id();\n      const account_id_type witness7_id= get_account(\"witness7\").get_id();\n      const account_id_type witness8_id= get_account(\"witness8\").get_id();\n      const account_id_type witness9_id= get_account(\"witness9\").get_id();\n      const account_id_type witness10_id= get_account(\"witness10\").get_id();\n\n\n      set_expiration( db, trx );\n\n      // changing lifetime feed to 5 days\n      // maint interval default is every 1 day\n      {\n         asset_update_bitasset_operation op;\n         op.new_options.minimum_feeds = 3;\n         op.new_options.feed_lifetime_sec = 86400 * 5;\n         op.asset_to_update = bit_usd_id;\n         op.issuer = bit_usd_id(db).issuer;\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n         generate_block();\n         trx.clear();\n      }\n\n      price_feed feed;\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness5_id(db), feed);\n      auto bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 1u);\n      auto itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness6_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 22u);\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness7_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 3u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 22u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 23u);\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness8_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 4u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 22u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 23u);\n      BOOST_CHECK_EQUAL(itr[3].first.instance.value, 24u);\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness9_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 5u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 22u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 23u);\n      BOOST_CHECK_EQUAL(itr[3].first.instance.value, 24u);\n      BOOST_CHECK_EQUAL(itr[4].first.instance.value, 25u);\n\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness10_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 6u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 22u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 23u);\n      BOOST_CHECK_EQUAL(itr[3].first.instance.value, 24u);\n      BOOST_CHECK_EQUAL(itr[4].first.instance.value, 25u);\n      BOOST_CHECK_EQUAL(itr[5].first.instance.value, 26u);\n\n      // make the older feed expire\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 5u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 22u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 23u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 24u);\n      BOOST_CHECK_EQUAL(itr[3].first.instance.value, 25u);\n      BOOST_CHECK_EQUAL(itr[4].first.instance.value, 26u);\n\n      // make older 2 feeds expire\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 3u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 24u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 25u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 26u);\n\n      // witness5 add new feed, feeds are sorted by witness_id not by feed_time\n      feed.settlement_price = bit_usd_id(db).amount(1) / core_id(db).amount(5);\n      publish_feed(bit_usd_id(db), witness5_id(db), feed);\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 4u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 24u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 25u);\n      BOOST_CHECK_EQUAL(itr[3].first.instance.value, 26u);\n\n      // another feed expires\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 3u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[1].first.instance.value, 25u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 26u);\n\n      // another feed expires\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n      bitasset_data = bit_usd_id(db).bitasset_data(db);\n      BOOST_CHECK_EQUAL(bitasset_data.feeds.size(), 2u);\n      itr = bitasset_data.feeds.begin();\n      BOOST_CHECK_EQUAL(itr[0].first.instance.value, 21u);\n      BOOST_CHECK_EQUAL(itr[2].first.instance.value, 26u);\n\n      // and so on\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/swan_tests.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n#include <graphene/chain/market_object.hpp>\n\n#include <graphene/app/database_api.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nnamespace graphene { namespace chain {\n\nstruct swan_fixture : database_fixture {\n    limit_order_id_type init_standard_swan(share_type amount = 1000, bool disable_bidding = false) {\n        standard_users();\n        standard_asset(disable_bidding);\n        return trigger_swan(amount, amount);\n    }\n\n    void standard_users() {\n        set_expiration( db, trx );\n        ACTORS((borrower)(borrower2)(feedproducer));\n        _borrower = borrower_id;\n        _borrower2 = borrower2_id;\n        _feedproducer = feedproducer_id;\n\n        transfer(committee_account, borrower_id, asset(init_balance));\n        transfer(committee_account, borrower2_id, asset(init_balance));\n    }\n\n    void standard_asset(bool disable_bidding = false) {\n        set_expiration( db, trx );\n        const asset_object* bitusd_ptr;\n        if( !disable_bidding )\n           bitusd_ptr = &create_bitasset(\"USDBIT\", _feedproducer);\n        else\n        {\n           auto cop = make_bitasset(\"USDBIT\", _feedproducer);\n           cop.common_options.flags |= disable_collateral_bidding;\n           trx.operations.clear();\n           trx.operations.push_back(cop);\n           trx.validate();\n           processed_transaction ptx = PUSH_TX(db, trx, ~0);\n           trx.operations.clear();\n           bitusd_ptr = &db.get<asset_object>(ptx.operation_results[0].get<object_id_type>());\n        }\n        const auto& bitusd = *bitusd_ptr;\n        _swan = bitusd.get_id();\n        _back = asset_id_type();\n        update_feed_producers(swan(), {_feedproducer});\n    }\n\n    limit_order_id_type trigger_swan(share_type amount1, share_type amount2) {\n        set_expiration( db, trx );\n        // starting out with price 1:1\n        set_feed( 1, 1 );\n        // start out with 2:1 collateral\n        borrow(borrower(), swan().amount(amount1), back().amount(2*amount1));\n        borrow(borrower2(), swan().amount(amount2), back().amount(4*amount2));\n\n        FC_ASSERT( get_balance(borrower(),  swan()) == amount1 );\n        FC_ASSERT( get_balance(borrower2(), swan()) == amount2 );\n        FC_ASSERT( get_balance(borrower() , back()) == init_balance - 2*amount1 );\n        FC_ASSERT( get_balance(borrower2(), back()) == init_balance - 4*amount2 );\n\n        set_feed( 1, 2 );\n        // this sell order is designed to trigger a black swan\n        limit_order_id_type oid = create_sell_order( borrower2(), swan().amount(1), back().amount(3) )->get_id();\n\n        FC_ASSERT( get_balance(borrower(),  swan()) == amount1 );\n        FC_ASSERT( get_balance(borrower2(), swan()) == amount2 - 1 );\n        FC_ASSERT( get_balance(borrower() , back()) == init_balance - 2*amount1 );\n        if( !hf_core_2481_passed() )\n           FC_ASSERT( get_balance(borrower2(), back()) == init_balance - 2*amount2 );\n        else\n        {\n           auto mssr = swan().bitasset_data(db).current_feed.maximum_short_squeeze_ratio;\n           auto denom = GRAPHENE_COLLATERAL_RATIO_DENOM;\n           FC_ASSERT( get_balance(borrower2(), back()) == init_balance - (2*amount2*denom+mssr-1)/mssr);\n        }\n\n        BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n        return oid;\n    }\n\n    // Note: need to set MCR explicitly, testnet has a different default\n    void set_feed(share_type usd, share_type core, uint16_t mcr = 1750, const optional<uint16_t>& icr = {}) {\n        price_feed feed;\n        feed.maintenance_collateral_ratio = mcr;\n        feed.settlement_price = swan().amount(usd) / back().amount(core);\n        publish_feed(swan(), feedproducer(), feed, icr);\n    }\n\n    void expire_feed() {\n      generate_blocks(db.head_block_time() + GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME);\n      generate_block();\n      FC_ASSERT( swan().bitasset_data(db).current_feed.settlement_price.is_null() );\n    }\n\n    void wait_for_hf_core_216() {\n      generate_blocks( HARDFORK_CORE_216_TIME );\n      generate_block();\n    }\n    void wait_for_hf_core_1270() {\n       auto mi = db.get_global_properties().parameters.maintenance_interval;\n       generate_blocks(HARDFORK_CORE_1270_TIME - mi);\n       wait_for_maintenance();\n    }\n    void wait_for_hf_core_2481() {\n       auto mi = db.get_global_properties().parameters.maintenance_interval;\n       generate_blocks(HARDFORK_CORE_2481_TIME - mi);\n       wait_for_maintenance();\n    }\n\n    bool hf_core_2481_passed() {\n       if( !hf2481 ) return false;\n       auto maint_time = db.get_dynamic_global_properties().next_maintenance_time;\n       return HARDFORK_CORE_2481_PASSED( maint_time );\n    }\n\n    void wait_for_maintenance() {\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      generate_block();\n    }\n\n    const account_object& borrower() { return _borrower(db); }\n    const account_object& borrower2() { return _borrower2(db); }\n    const account_object& feedproducer() { return _feedproducer(db); }\n    const asset_object& swan() { return _swan(db); }\n    const asset_object& back() { return _back(db); }\n\n    int64_t init_balance = 1000000;\n    account_id_type _borrower, _borrower2, _feedproducer;\n    asset_id_type _swan, _back;\n};\n\n}}\n\nBOOST_FIXTURE_TEST_SUITE( swan_tests, swan_fixture )\n\n/**\n *  This test sets up the minimum condition for a black swan to occur but does\n *  not test the full range of cases that may be possible during a black swan.\n */\nBOOST_AUTO_TEST_CASE( black_swan )\n{ try {\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n\n      init_standard_swan();\n\n      force_settle( borrower(), swan().amount(100) );\n\n      expire_feed();\n      wait_for_hf_core_216();\n\n      force_settle( borrower(), swan().amount(100) );\n\n      set_feed( 100, 150 );\n\n      BOOST_TEST_MESSAGE( \"Verify that we cannot borrow after black swan\" );\n      GRAPHENE_REQUIRE_THROW( borrow(borrower(), swan().amount(1000), back().amount(2000)), fc::exception )\n      trx.operations.clear();\n\n      generate_block();\n\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/**\n * Black swan occurs when price feed falls, triggered by settlement\n * order.\n */\nBOOST_AUTO_TEST_CASE( black_swan_issue_346 )\n{ try {\n\n      ACTORS((buyer)(seller)(borrower)(borrower2)(settler)(feeder));\n\n      const asset_object& core = asset_id_type()(db);\n\n      int trial = 0;\n\n      vector< const account_object* > actors{ &buyer, &seller, &borrower, &borrower2, &settler, &feeder };\n\n      auto top_up = [&]()\n      {\n         for( const account_object* actor : actors )\n         {\n            int64_t bal = get_balance( *actor, core );\n            if( bal < init_balance )\n               transfer( committee_account, actor->get_id(), asset(init_balance - bal) );\n            else if( bal > init_balance )\n               transfer( actor->get_id(), committee_account, asset(bal - init_balance) );\n         }\n      };\n\n      auto setup_asset = [&]() -> const asset_object&\n      {\n         const asset_object& bitusd = create_bitasset(\"USDBIT\"+fc::to_string(trial)+\"X\", feeder_id);\n         update_feed_producers( bitusd, {feeder.get_id()} );\n         BOOST_CHECK( !bitusd.bitasset_data(db).is_globally_settled() );\n         trial++;\n         return bitusd;\n      };\n\n      /*\n       * GRAPHENE_COLLATERAL_RATIO_DENOM\n      uint16_t maintenance_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO;\n      uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO;\n      */\n\n      // situations to test:\n      // 1. minus short squeeze protection would be black swan, otherwise no\n      // 2. issue 346 (price feed drops followed by force settle, drop should trigger BS)\n      // 3. feed price < D/C of least collateralized short < call price < highest bid\n\n      auto set_price = [&](\n         const asset_object& bitusd,\n         const price& settlement_price\n         )\n      {\n         price_feed feed;\n         feed.settlement_price = settlement_price;\n         feed.core_exchange_rate = settlement_price;\n         wdump( (feed.max_short_squeeze_price()) );\n         publish_feed( bitusd, feeder, feed );\n      };\n\n      auto wait_for_settlement = [&]()\n      {\n         const auto& idx = db.get_index_type<force_settlement_index>().indices().get<by_expiration>();\n         const auto& itr = idx.rbegin();\n         if( itr == idx.rend() )\n            return;\n         generate_blocks( itr->settlement_date );\n         BOOST_CHECK( !idx.empty() );\n         generate_block();\n         BOOST_CHECK( idx.empty() );\n      };\n\n      {\n         const asset_object& bitusd = setup_asset();\n         top_up();\n         set_price( bitusd, bitusd.amount(1) / core.amount(5) );  // $0.20\n         borrow(borrower, bitusd.amount(100), asset(1000));       // 2x collat\n         transfer( borrower, settler, bitusd.amount(100) );\n\n         // drop to $0.02 and settle\n         BOOST_CHECK( !bitusd.bitasset_data(db).is_globally_settled() );\n         set_price( bitusd, bitusd.amount(1) / core.amount(50) ); // $0.02\n         BOOST_CHECK( bitusd.bitasset_data(db).is_globally_settled() );\n         GRAPHENE_REQUIRE_THROW( borrow( borrower2, bitusd.amount(100), asset(10000) ), fc::exception );\n         force_settle( settler, bitusd.amount(100) );\n\n         // wait for forced settlement to execute\n         // this would throw on Sep.18 testnet, see #346 (https://github.com/cryptonomex/graphene/issues/346)\n         wait_for_settlement();\n      }\n\n      // issue 350 (https://github.com/cryptonomex/graphene/issues/350)\n      {\n         // ok, new asset\n         const asset_object& bitusd = setup_asset();\n         top_up();\n         set_price( bitusd, bitusd.amount(40) / core.amount(1000) ); // $0.04\n         borrow( borrower, bitusd.amount(100), asset(5000) );    // 2x collat\n         transfer( borrower, seller, bitusd.amount(100) );\n         // this order is at $0.019, we should not be able to match against it\n         limit_order_id_type oid_019 = create_sell_order( seller, bitusd.amount(39), core.amount(2000) )->get_id();\n         // this order is at $0.020, we should be able to match against it\n         limit_order_id_type oid_020 = create_sell_order( seller, bitusd.amount(40), core.amount(2000) )->get_id();\n         set_price( bitusd, bitusd.amount(21) / core.amount(1000) ); // $0.021\n         //\n         // We attempt to match against $0.019 order and black swan,\n         // and this is intended behavior.  See discussion in ticket.\n         //\n         BOOST_CHECK( bitusd.bitasset_data(db).is_globally_settled() );\n         BOOST_CHECK( db.find( oid_019 ) != nullptr );\n         BOOST_CHECK( db.find( oid_020 ) == nullptr );\n      }\n\n   } catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, recover price feed - asset should be revived\n */\nBOOST_AUTO_TEST_CASE( revive_recovered )\n{ try {\n      init_standard_swan( 700 );\n\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n      else\n         wait_for_hf_core_216();\n\n      // revive after price recovers\n      set_feed( 700, 800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      set_feed( 701, 800 );\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<call_order_object> calls = db_api.get_call_orders(swan_symbol, 100);\n      BOOST_REQUIRE_EQUAL( 1u, calls.size() );\n      BOOST_CHECK( calls[0].borrower == swan().issuer );\n      BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 );\n      BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 );\n\n      generate_block();\n\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, place bids, recover price feed - asset should be revived\n */\nBOOST_AUTO_TEST_CASE( revive_recovered_with_bids )\n{ try {\n      init_standard_swan( 700 );\n\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n      else\n         wait_for_hf_core_216();\n\n      // price not good enough for recovery\n      set_feed( 700, 800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      bid_collateral( borrower(),  back().amount(10510), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // revive after price recovers\n      set_feed( 701, 800 );\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n\n      vector<call_order_object> calls = db_api.get_call_orders(swan_symbol, 100);\n      BOOST_REQUIRE_EQUAL( 1u, calls.size() );\n      BOOST_CHECK( calls[0].borrower == swan().issuer );\n      BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 );\n      BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 );\n\n      generate_block();\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, place bids, recover price feed with ICR, before the core-2290 hard fork,\n *  asset should be revived based on MCR\n */\nBOOST_AUTO_TEST_CASE( revive_recovered_with_bids_not_by_icr_before_hf_core_2290 )\n{ try {\n      init_standard_swan( 700 );\n\n      // Advance to a time before core-2290 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2290_TIME - mi * 2);\n      set_expiration( db, trx );\n\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n      BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) );\n\n      // price not good enough for recovery\n      set_feed( 700, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      bid_collateral( borrower(),  back().amount(10510), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // good feed price\n      set_feed( 701, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n\n      vector<call_order_object> calls = db_api.get_call_orders(swan_symbol, 100);\n      BOOST_REQUIRE_EQUAL( 1u, calls.size() );\n      BOOST_CHECK( calls[0].borrower == swan().issuer );\n      BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 );\n      BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 );\n\n      generate_block();\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, place bids, recover price feed with ICR, after the core-2290 hard fork,\n *  asset should be revived based on ICR\n */\nBOOST_AUTO_TEST_CASE( revive_recovered_with_bids_by_icr_after_hf_core_2290 )\n{ try {\n      init_standard_swan( 700 );\n\n      // Advance to a time before core-2290 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2290_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n      BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // price not good enough for recovery\n      set_feed( 700, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      bid_collateral( borrower(),  back().amount(10510), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // price still not good enough for recovery\n      set_feed( 701, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // price still not good enough for recovery\n      set_feed( 720, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // good feed price\n      set_feed( 721, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n\n      vector<call_order_object> calls = db_api.get_call_orders(swan_symbol, 100);\n      BOOST_REQUIRE_EQUAL( 1u, calls.size() );\n      BOOST_CHECK( calls[0].borrower == swan().issuer );\n      BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 );\n      BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 );\n\n      generate_block();\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, recover price feed - asset should be revived\n */\nBOOST_AUTO_TEST_CASE( recollateralize )\n{ try {\n      init_standard_swan( 700 );\n\n      // no hardfork yet\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(1000), swan().amount(100) ), fc::exception );\n\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n      else\n         wait_for_hf_core_216();\n\n      int64_t b2_balance = get_balance( borrower2(), back() );\n      bid_collateral( borrower2(), back().amount(1000), swan().amount(100) );\n      BOOST_CHECK_EQUAL( get_balance( borrower2(), back() ), b2_balance - 1000 );\n      bid_collateral( borrower2(), back().amount(2000), swan().amount(200) );\n      BOOST_CHECK_EQUAL( get_balance( borrower2(), back() ), b2_balance - 2000 );\n      bid_collateral( borrower2(), back().amount(1000), swan().amount(0) );\n      BOOST_CHECK_EQUAL( get_balance( borrower2(), back() ), b2_balance );\n\n      // can't bid for non-bitassets\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), swan().amount(100), asset(100) ), fc::exception );\n      // can't cancel a non-existant bid\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(0), swan().amount(0) ), fc::exception );\n      // can't bid zero collateral\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(0), swan().amount(100) ), fc::exception );\n      // can't bid more than we have\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(b2_balance + 100), swan().amount(100) ),\n                              fc::exception );\n      trx.operations.clear();\n\n      // can't bid on a live bitasset\n      const asset_object& bitcny = create_bitasset(\"CNYBIT\", _feedproducer);\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), asset(100), bitcny.amount(100) ), fc::exception );\n      update_feed_producers(bitcny, {_feedproducer});\n      price_feed feed;\n      feed.settlement_price = bitcny.amount(1) / asset(1);\n      publish_feed( bitcny.get_id(), _feedproducer, feed );\n      borrow( borrower2(), bitcny.amount(100), asset(1000) );\n\n      // can't bid wrong collateral type\n      GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), bitcny.amount(100), swan().amount(100) ), fc::exception );\n\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n      BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      // doesn't happen without price feed\n      bid_collateral( borrower(),  back().amount(1400), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(1400), swan().amount(700) );\n      wait_for_maintenance();\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      set_feed(1, 2);\n      // doesn't happen if cover is insufficient\n      bid_collateral( borrower2(), back().amount(1400), swan().amount(600) );\n      wait_for_maintenance();\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      set_feed(1, 2);\n      // doesn't happen if some bids have a bad swan price\n      bid_collateral( borrower2(), back().amount(1050), swan().amount(700) );\n      wait_for_maintenance();\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      set_feed(1, 2);\n      // works\n      bid_collateral( borrower(),  back().amount(1051), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) );\n\n      // check get_collateral_bids\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      GRAPHENE_REQUIRE_THROW( db_api.get_collateral_bids(back().symbol, 100, 0), fc::assert_exception );\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 1);\n      BOOST_CHECK_EQUAL( 1u, bids.size() );\n      FC_ASSERT( _borrower2 == bids[0].bidder );\n      bids = db_api.get_collateral_bids(swan_symbol, 1, 0);\n      BOOST_CHECK_EQUAL( 1u, bids.size() );\n      FC_ASSERT( _borrower == bids[0].bidder );\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n      FC_ASSERT( _borrower == bids[0].bidder );\n      FC_ASSERT( _borrower2 == bids[1].bidder );\n\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      // revive\n      wait_for_maintenance();\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, recover price feed with ICR, before the core-2290 hard fork,\n *  asset should be revived based on MCR\n */\nBOOST_AUTO_TEST_CASE( recollateralize_not_by_icr_before_hf_core_2290 )\n{ try {\n      init_standard_swan( 700 );\n\n      // Advance to a time before core-2290 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2290_TIME - mi * 2);\n      set_expiration( db, trx );\n\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n      BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) );\n\n      set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800\n      // works\n      bid_collateral( borrower(),  back().amount(1051), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) );\n\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // revive\n      wait_for_maintenance();\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, recover price feed with ICR, after the core-2290 hard fork,\n *  asset should be revived based on ICR\n */\nBOOST_AUTO_TEST_CASE( recollateralize_by_icr_after_hf_core_2290 )\n{ try {\n      init_standard_swan( 700 );\n\n      // Advance to core-2290 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2290_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n      BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n      BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() );\n\n      set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800\n      // doesn't happen if some bids have a bad swan price\n      bid_collateral( borrower(),  back().amount(1051), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) );\n      wait_for_maintenance();\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800\n      // doesn't happen if some bids have a bad swan price\n      bid_collateral( borrower(),  back().amount(1120), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(1122), swan().amount(700) );\n      wait_for_maintenance();\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800\n      // works\n      bid_collateral( borrower(),  back().amount(1121), swan().amount(700) );\n      bid_collateral( borrower2(), back().amount(1122), swan().amount(700) );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK_EQUAL( 2u, bids.size() );\n\n      // revive\n      wait_for_maintenance();\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n      BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 );\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, bid, adjust bid before/after hf_1692\n */\nBOOST_AUTO_TEST_CASE( bid_issue_1692 )\n{ try {\n   init_standard_swan( 700 );\n\n   generate_blocks( HARDFORK_CORE_1692_TIME - 30 );\n\n   int64_t b2_balance = get_balance( borrower2(), back() );\n   bid_collateral( borrower2(), back().amount(1000), swan().amount(100) );\n   BOOST_CHECK_EQUAL( get_balance( borrower2(), back() ), b2_balance - 1000 );\n   GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(b2_balance), swan().amount(200) ),\n                           fc::assert_exception );\n   GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(b2_balance-999), swan().amount(200) ),\n                           fc::assert_exception );\n\n   generate_blocks( HARDFORK_CORE_1692_TIME + 30 );\n\n   bid_collateral( borrower2(), back().amount(b2_balance-999), swan().amount(200) );\n   BOOST_CHECK_EQUAL( get_balance( borrower2(), back() ), 999 );\n   bid_collateral( borrower2(), back().amount(b2_balance), swan().amount(200) );\n   BOOST_CHECK_EQUAL( get_balance( borrower2(), back() ), 0 );\n} FC_LOG_AND_RETHROW() }\n\n/** Creates a black swan, settles all debts, recovers price feed - asset should be revived\n */\nBOOST_AUTO_TEST_CASE( revive_empty_recovered )\n{ try {\n      limit_order_id_type oid = init_standard_swan( 1000 );\n\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n      else\n         wait_for_hf_core_216();\n\n      set_expiration( db, trx );\n      cancel_limit_order( oid(db) );\n      force_settle( borrower(), swan().amount(1000) );\n      force_settle( borrower2(), swan().amount(1000) );\n      BOOST_CHECK_EQUAL( 0, swan().dynamic_data(db).current_supply.value );\n      BOOST_CHECK_EQUAL( 0, swan().bitasset_data(db).settlement_fund.value );\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      // revive after price recovers\n      set_feed( 1, 1 );\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n\n      auto& call_idx = db.get_index_type<call_order_index>().indices().get<by_account>();\n      auto itr = call_idx.find( boost::make_tuple(_feedproducer, _swan) );\n      BOOST_CHECK( itr == call_idx.end() );\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, settles all debts - asset should be revived in next maintenance\n */\nBOOST_AUTO_TEST_CASE( revive_empty )\n{ try {\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n      else\n         wait_for_hf_core_216();\n\n      limit_order_id_type oid = init_standard_swan( 1000 );\n\n      cancel_limit_order( oid(db) );\n      force_settle( borrower(), swan().amount(1000) );\n      force_settle( borrower2(), swan().amount(1000) );\n      BOOST_CHECK_EQUAL( 0, swan().dynamic_data(db).current_supply.value );\n\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      // revive\n      wait_for_maintenance();\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/** Creates a black swan, settles all debts - asset should be revived in next maintenance\n */\nBOOST_AUTO_TEST_CASE( revive_empty_with_bid )\n{ try {\n      if(hf2481)\n         wait_for_hf_core_2481();\n      else if(hf1270)\n         wait_for_hf_core_1270();\n      else\n         wait_for_hf_core_216();\n\n      standard_users();\n      standard_asset();\n\n      set_feed( 1, 1 );\n      borrow(borrower(), swan().amount(1000), back().amount(2000));\n      borrow(borrower2(), swan().amount(1000), back().amount(1967));\n\n      set_feed( 1, 2 );\n      // this sell order is designed to trigger a black swan\n      limit_order_id_type oid = create_sell_order( borrower2(), swan().amount(1), back().amount(3) )->get_id();\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      cancel_limit_order( oid(db) );\n      force_settle( borrower(), swan().amount(500) );\n      force_settle( borrower(), swan().amount(500) );\n      force_settle( borrower2(), swan().amount(667) );\n      force_settle( borrower2(), swan().amount(333) );\n      BOOST_CHECK_EQUAL( 0, swan().dynamic_data(db).current_supply.value );\n      BOOST_CHECK_EQUAL( 0, swan().bitasset_data(db).settlement_fund.value );\n\n      bid_collateral( borrower(), back().amount(3000), swan().amount(700) );\n\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      // revive\n      wait_for_maintenance();\n      BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n      graphene::app::database_api db_api( db, &( app.get_options() ));\n      auto swan_symbol = _swan(db).symbol;\n      vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n      BOOST_CHECK( bids.empty() );\n\n      auto& call_idx = db.get_index_type<call_order_index>().indices().get<by_account>();\n      auto itr = call_idx.find( boost::make_tuple(_borrower, _swan) );\n      BOOST_CHECK( itr == call_idx.end() );\n      itr = call_idx.find( boost::make_tuple(_feedproducer, _swan) );\n      BOOST_CHECK( itr == call_idx.end() );\n} catch( const fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE(black_swan_after_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(black_swan);\n\n} FC_LOG_AND_RETHROW() }\n\n// black_swan_issue_346_hf1270 is skipped as it is already failing with HARDFORK_CORE_834_TIME\n\nBOOST_AUTO_TEST_CASE(revive_recovered_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(revive_recovered);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_recovered_with_bids_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(revive_recovered_with_bids);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(recollateralize_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(recollateralize);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_empty_recovered_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(revive_empty_recovered);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_empty_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(revive_empty);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_empty_with_bid_hf1270)\n{ try {\n   hf1270 = true;\n   INVOKE(revive_empty_with_bid);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(black_swan_after_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(black_swan);\n\n} FC_LOG_AND_RETHROW() }\n\n// black_swan_issue_346_hf2481 is skipped as it is already failing with HARDFORK_CORE_834_TIME\n\nBOOST_AUTO_TEST_CASE(revive_recovered_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(revive_recovered);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_recovered_with_bids_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(revive_recovered_with_bids);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(recollateralize_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(recollateralize);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_empty_recovered_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(revive_empty_recovered);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_empty_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(revive_empty);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(revive_empty_with_bid_hf2481)\n{ try {\n   hf2481 = true;\n   INVOKE(revive_empty_with_bid);\n\n} FC_LOG_AND_RETHROW() }\n\n/** Creates a black swan, bids on more than outstanding debt\n */\nBOOST_AUTO_TEST_CASE( overflow )\n{ try {\n   init_standard_swan( 700 );\n\n   wait_for_hf_core_216();\n\n   bid_collateral( borrower(),  back().amount(2200), swan().amount(GRAPHENE_MAX_SHARE_SUPPLY - 1) );\n   bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) );\n   set_feed(1, 2);\n   wait_for_maintenance();\n\n   auto& call_idx = db.get_index_type<call_order_index>().indices().get<by_account>();\n   auto itr = call_idx.find( boost::make_tuple(_borrower, _swan) );\n   BOOST_REQUIRE( itr != call_idx.end() );\n   BOOST_CHECK_EQUAL( 1, itr->debt.value );\n   itr = call_idx.find( boost::make_tuple(_borrower2, _swan) );\n   BOOST_REQUIRE( itr != call_idx.end() );\n   BOOST_CHECK_EQUAL( 1399, itr->debt.value );\n\n   BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() );\n} FC_LOG_AND_RETHROW() }\n\n/// Tests what kind of assets can have the disable_collateral_bidding flag / issuer permission\nBOOST_AUTO_TEST_CASE( hf2281_asset_permissions_flags_test )\n{\n   try {\n\n      // Advance to core-2281 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2281_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n\n      // Able to create a PM with the disable_collateral_bidding bit in flags\n      create_prediction_market( \"TESTPMTEST\", sam_id, 0, disable_collateral_bidding );\n\n      // Able to create a MPA with the disable_collateral_bidding bit in flags\n      create_bitasset( \"TESTBITTEST\", sam_id, 0, disable_collateral_bidding );\n\n      // Unable to create a UIA with the disable_collateral_bidding bit in flags\n      BOOST_CHECK_THROW( create_user_issued_asset( \"TESTUIA\", sam_id(db), disable_collateral_bidding ),\n                         fc::exception );\n\n      // create a PM with a zero market_fee_percent\n      const asset_object& pm = create_prediction_market( \"TESTPM\", sam_id, 0, charge_market_fee );\n      asset_id_type pm_id = pm.get_id();\n\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      // create a UIA with a zero market_fee_percent\n      const asset_object& uia = create_user_issued_asset( \"TESTUIA\", sam_id(db), charge_market_fee );\n      asset_id_type uia_id = uia.get_id();\n\n      // Prepare for asset update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n\n      // Able to set disable_collateral_bidding bit in flags for PM\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n      auop.new_options.flags |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n      // Able to propose\n      propose( auop );\n\n      // Able to set disable_collateral_bidding bit in flags for MPA\n      auop.asset_to_update = mpa_id;\n      auop.new_options = mpa_id(db).options;\n      auop.new_options.flags |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n      // Able to propose\n      propose( auop );\n\n      // Unable to set disable_collateral_bidding bit in flags for UIA\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.new_options.flags |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // Able to propose\n      propose( auop );\n\n      // Able to set disable_collateral_bidding bit in issuer_permissions for PM\n      auop.asset_to_update = pm_id;\n      auop.new_options = pm_id(db).options;\n      auop.new_options.issuer_permissions |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n      // Able to propose\n      propose( auop );\n\n      // Able to set disable_collateral_bidding bit in issuer_permissions for MPA\n      auop.asset_to_update = mpa_id;\n      auop.new_options = mpa_id(db).options;\n      auop.new_options.issuer_permissions |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n      // Able to propose\n      propose( auop );\n\n      // Unable to set disable_collateral_bidding bit in issuer_permissions for UIA\n      auop.asset_to_update = uia_id;\n      auop.new_options = uia_id(db).options;\n      auop.new_options.issuer_permissions |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      // But able to propose\n      propose( auop );\n\n      // Unable to create a UIA with disable_collateral_bidding permission bit\n      asset_create_operation acop;\n      acop.issuer = sam_id;\n      acop.symbol = \"SAMCOIN\";\n      acop.precision = 2;\n      acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1));\n      acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;\n      acop.common_options.market_fee_percent = 100;\n      acop.common_options.flags = charge_market_fee;\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_collateral_bidding;\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // Unable to propose either\n      BOOST_CHECK_THROW( propose( acop ), fc::exception );\n\n      // Able to create UIA without disable_collateral_bidding permission bit\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      PUSH_TX(db, trx, ~0);\n\n      // Able to create a MPA with disable_collateral_bidding permission bit\n      acop.symbol = \"SAMMPA\";\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_collateral_bidding;\n      acop.bitasset_opts = bitasset_options();\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      PUSH_TX(db, trx, ~0);\n\n      // Able to propose\n      propose( acop );\n\n      // Able to create a PM with disable_collateral_bidding permission bit\n      acop.symbol = \"SAMPM\";\n      acop.precision = asset_id_type()(db).precision;\n      acop.is_prediction_market = true;\n      acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle\n                                                                                | disable_collateral_bidding;\n      acop.bitasset_opts = bitasset_options();\n\n      trx.operations.clear();\n      trx.operations.push_back( acop );\n      PUSH_TX(db, trx, ~0);\n\n      // Able to propose\n      propose( acop );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests whether asset owner has permission to update the disable_collateral_bidding flag and the permission\nBOOST_AUTO_TEST_CASE( hf2281_asset_owner_permission_test )\n{\n   try {\n\n      // Advance to core-2281 hard fork\n      auto mi = db.get_global_properties().parameters.maintenance_interval;\n      generate_blocks(HARDFORK_CORE_2281_TIME - mi);\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      set_expiration( db, trx );\n\n      ACTORS((sam)(feeder));\n\n      auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION;\n      fund( sam, asset(init_amount) );\n      fund( feeder, asset(init_amount) );\n\n      // create a MPA with a zero market_fee_percent\n      const asset_object& mpa = create_bitasset( \"TESTBIT\", sam_id, 0, charge_market_fee );\n      asset_id_type mpa_id = mpa.get_id();\n\n      BOOST_CHECK( mpa_id(db).can_bid_collateral() );\n\n      // add a price feed publisher and publish a feed\n      update_feed_producers( mpa_id, { feeder_id } );\n\n      price_feed f;\n      f.settlement_price = price( asset(1,mpa_id), asset(1) );\n      f.core_exchange_rate = price( asset(1,mpa_id), asset(1) );\n      f.maintenance_collateral_ratio = 1850;\n      f.maximum_short_squeeze_ratio = 1250;\n\n      uint16_t feed_icr = 1900;\n\n      publish_feed( mpa_id, feeder_id, f, feed_icr );\n\n      // Prepare for asset update\n      asset_update_operation auop;\n      auop.issuer = sam_id;\n      auop.asset_to_update = mpa_id;\n      auop.new_options = mpa_id(db).options;\n\n      // update disable_collateral_bidding flag\n      auop.new_options.flags |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK( !mpa_id(db).can_bid_collateral() );\n\n      // disable owner's permission to update the disable_collateral_bidding flag\n      auop.new_options.issuer_permissions |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK( !mpa_id(db).can_bid_collateral() );\n\n      // check that owner can not update the disable_collateral_bidding flag\n      auop.new_options.flags &= ~disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options = mpa_id(db).options;\n\n      // check\n      BOOST_CHECK( !mpa_id(db).can_bid_collateral() );\n\n      // enable owner's permission to update the disable_collateral_bidding flag\n      auop.new_options.issuer_permissions &= ~disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK( !mpa_id(db).can_bid_collateral() );\n\n      // check that owner can update the disable_collateral_bidding flag\n      auop.new_options.flags &= ~disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK( mpa_id(db).can_bid_collateral() );\n\n      // Sam borrow some\n      borrow( sam, asset(1000, mpa_id), asset(2000) );\n\n      // disable owner's permission to update the disable_collateral_bidding flag\n      auop.new_options.issuer_permissions |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      PUSH_TX(db, trx, ~0);\n\n      // check\n      BOOST_CHECK( mpa_id(db).can_bid_collateral() );\n\n      // check that owner can not update the disable_collateral_bidding flag\n      auop.new_options.flags |= disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n      auop.new_options = mpa_id(db).options;\n\n      // check\n      BOOST_CHECK( mpa_id(db).can_bid_collateral() );\n\n      // unable to enable the permission due to non-zero supply\n      auop.new_options.issuer_permissions &= ~disable_collateral_bidding;\n      trx.operations.clear();\n      trx.operations.push_back( auop );\n      BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n      // check\n      BOOST_CHECK( mpa_id(db).can_bid_collateral() );\n\n      generate_block();\n\n   } catch (fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/// Tests the disable_collateral_bidding bit in asset flags\nBOOST_AUTO_TEST_CASE( disable_collateral_bidding_test )\n{ try {\n   init_standard_swan( 2000 );\n\n   // Advance to core-2281 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2281_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   BOOST_CHECK( swan().can_bid_collateral() );\n\n   bid_collateral( borrower(), back().amount(3000), swan().amount(700) );\n   bid_collateral( borrower2(), back().amount(300), swan().amount(600) );\n\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   auto swan_symbol = swan().symbol;\n   vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n   BOOST_CHECK_EQUAL( bids.size(), 2u );\n\n   // Disable collateral bidding\n   asset_update_operation auop;\n   auop.issuer = swan().issuer;\n   auop.asset_to_update = swan().get_id();\n   auop.new_options = swan().options;\n   auop.new_options.flags |= disable_collateral_bidding;\n   trx.operations.clear();\n   trx.operations.push_back( auop );\n   PUSH_TX(db, trx, ~0);\n\n   BOOST_CHECK( !swan().can_bid_collateral() );\n\n   // Check that existing bids are cancelled\n   bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n   BOOST_CHECK_EQUAL( bids.size(), 0u );\n\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n   // Unable to bid\n   BOOST_CHECK_THROW( bid_collateral( borrower(), back().amount(3000), swan().amount(700) ), fc::exception );\n\n   // Enable collateral bidding\n   auop.new_options.flags &= ~disable_collateral_bidding;\n   trx.operations.clear();\n   trx.operations.push_back( auop );\n   PUSH_TX(db, trx, ~0);\n\n   BOOST_CHECK( swan().can_bid_collateral() );\n\n   // Able to bid again\n   bid_collateral( borrower(), back().amount(3000), swan().amount(700) );\n   bid_collateral( borrower2(), back().amount(300), swan().amount(600) );\n\n   bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n   BOOST_CHECK_EQUAL( bids.size(), 2u );\n\n   generate_block();\n\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests cancelling of collateral bids at hard fork time if the disable_collateral_bidding bit in asset flags was\n/// already set due to a bug\nBOOST_AUTO_TEST_CASE( disable_collateral_bidding_cross_hardfork_test )\n{ try {\n   init_standard_swan( 2000, true );\n\n   wait_for_hf_core_216();\n\n   BOOST_CHECK( !swan().can_bid_collateral() );\n\n   bid_collateral( borrower(), back().amount(3000), swan().amount(700) );\n   bid_collateral( borrower2(), back().amount(300), swan().amount(600) );\n\n   graphene::app::database_api db_api( db, &( app.get_options() ));\n   auto swan_symbol = swan().symbol;\n   vector<collateral_bid_object> bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n   BOOST_CHECK_EQUAL( bids.size(), 2u );\n\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n   // Advance to core-2281 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2281_TIME - mi);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   BOOST_CHECK( !swan().can_bid_collateral() );\n\n   // Check that existing bids are cancelled\n   bids = db_api.get_collateral_bids(swan_symbol, 100, 0);\n   BOOST_CHECK_EQUAL( bids.size(), 0u );\n\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n   // Unable to bid\n   BOOST_CHECK_THROW( bid_collateral( borrower(), back().amount(3000), swan().amount(700) ), fc::exception );\n\n   generate_block();\n\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n} FC_LOG_AND_RETHROW() }\n\n/// Tests updating bitasset options after GS\nBOOST_AUTO_TEST_CASE( update_bitasset_after_gs )\n{ try {\n\n   init_standard_swan( 2000, true );\n\n   // Advance to a time before core-2282 hard fork\n   auto mi = db.get_global_properties().parameters.maintenance_interval;\n   generate_blocks(HARDFORK_CORE_2282_TIME - mi);\n   set_expiration( db, trx );\n\n   // try to update bitasset options, before hf core-2282, it is not allowed\n   auto old_options = swan().bitasset_data(db).options;\n\n   asset_update_bitasset_operation aubop;\n   aubop.issuer = swan().issuer;\n   aubop.asset_to_update = _swan;\n   aubop.new_options = old_options;\n   aubop.new_options.feed_lifetime_sec += 1;\n\n   trx.operations.clear();\n   trx.operations.push_back( aubop );\n   BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n   BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec );\n\n   // Advance to core-2282 hard fork\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   set_expiration( db, trx );\n\n   BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec );\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n   // should succeed\n   PUSH_TX(db, trx, ~0);\n\n   BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 );\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n   generate_block();\n\n   BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 );\n   BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n   // unable to update backing asset\n\n   asset_id_type uia_id = create_user_issued_asset( \"MYUIA\" ).get_id();\n\n   aubop.new_options.short_backing_asset = uia_id;\n\n   trx.operations.clear();\n   trx.operations.push_back( aubop );\n   BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception );\n\n   BOOST_CHECK( swan().bitasset_data(db).options.short_backing_asset == old_options.short_backing_asset );\n\n   aubop.new_options.short_backing_asset = old_options.short_backing_asset;\n\n   // Update other bitasset options\n   aubop.new_options.minimum_feeds += 2;\n   aubop.new_options.force_settlement_delay_sec += 3;\n   aubop.new_options.force_settlement_offset_percent += 4;\n   aubop.new_options.maximum_force_settlement_volume += 5;\n   aubop.new_options.extensions.value.initial_collateral_ratio = 1900;\n   aubop.new_options.extensions.value.maintenance_collateral_ratio = 1800;\n   aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1005;\n   aubop.new_options.extensions.value.margin_call_fee_ratio = 10;\n   aubop.new_options.extensions.value.force_settle_fee_percent = 20;\n   trx.operations.clear();\n   trx.operations.push_back( aubop );\n   PUSH_TX(db, trx, ~0);\n\n   const auto& check_result = [&]()\n   {\n      BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() );\n\n      BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec\n                   == old_options.feed_lifetime_sec + 1 );\n      BOOST_CHECK( swan().bitasset_data(db).options.minimum_feeds\n                   == old_options.minimum_feeds + 2 );\n      BOOST_CHECK( swan().bitasset_data(db).options.force_settlement_delay_sec\n                   == old_options.force_settlement_delay_sec + 3 );\n      BOOST_CHECK( swan().bitasset_data(db).options.force_settlement_offset_percent\n                   == old_options.force_settlement_offset_percent + 4 );\n      BOOST_CHECK( swan().bitasset_data(db).options.maximum_force_settlement_volume\n                   == old_options.maximum_force_settlement_volume + 5 );\n\n      BOOST_CHECK( swan().bitasset_data(db).options.short_backing_asset == old_options.short_backing_asset );\n\n      auto extv = swan().bitasset_data(db).options.extensions.value;\n      BOOST_REQUIRE( extv.initial_collateral_ratio.valid() );\n      BOOST_CHECK_EQUAL( *extv.initial_collateral_ratio, 1900U );\n      BOOST_REQUIRE( extv.maintenance_collateral_ratio.valid() );\n      BOOST_CHECK_EQUAL( *extv.maintenance_collateral_ratio, 1800U );\n      BOOST_REQUIRE( extv.maximum_short_squeeze_ratio.valid() );\n      BOOST_CHECK_EQUAL( *extv.maximum_short_squeeze_ratio, 1005U );\n      BOOST_REQUIRE( extv.margin_call_fee_ratio.valid() );\n      BOOST_CHECK_EQUAL( *extv.margin_call_fee_ratio, 10U );\n      BOOST_REQUIRE( extv.force_settle_fee_percent.valid() );\n      BOOST_CHECK_EQUAL( *extv.force_settle_fee_percent, 20U );\n   };\n\n   check_result();\n\n   generate_block();\n\n   check_result();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/uia_tests.cpp",
    "content": "/*\n * Copyright (c) 2015-2018 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/chain/database.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n#include <graphene/chain/is_authorized_asset.hpp>\n\n#include <graphene/chain/account_object.hpp>\n#include <graphene/chain/asset_object.hpp>\n\n#include <fc/crypto/digest.hpp>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\nBOOST_FIXTURE_TEST_SUITE( uia_tests, database_fixture )\n\nBOOST_AUTO_TEST_CASE( create_advanced_uia )\n{\n   try {\n      asset_id_type test_asset_id { db.get_index<asset_object>().get_next_id() };\n      asset_create_operation creator;\n      creator.issuer = account_id_type();\n      creator.fee = asset();\n      creator.symbol = \"ADVANCED\";\n      creator.common_options.max_supply = 100000000;\n      creator.precision = 2;\n      creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/\n      creator.common_options.issuer_permissions = charge_market_fee|white_list|override_authority|transfer_restricted|disable_confidential;\n      creator.common_options.flags = charge_market_fee|white_list|override_authority|disable_confidential;\n      creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1)));\n      creator.common_options.whitelist_authorities = creator.common_options.blacklist_authorities = {account_id_type()};\n\n      trx.operations.push_back(std::move(creator));\n      PUSH_TX( db, trx, ~0 );\n\n      const asset_object& test_asset = test_asset_id(db);\n      BOOST_CHECK(test_asset.symbol == \"ADVANCED\");\n      BOOST_CHECK(asset(1, test_asset_id) * test_asset.options.core_exchange_rate == asset(2));\n      BOOST_CHECK(test_asset.options.flags & white_list);\n      BOOST_CHECK(test_asset.options.max_supply == 100000000);\n      BOOST_CHECK(!test_asset.bitasset_data_id.valid());\n      BOOST_CHECK(test_asset.options.market_fee_percent == GRAPHENE_MAX_MARKET_FEE_PERCENT/100);\n\n      const asset_dynamic_data_object& test_asset_dynamic_data = test_asset.dynamic_asset_data_id(db);\n      BOOST_CHECK(test_asset_dynamic_data.current_supply == 0);\n      BOOST_CHECK(test_asset_dynamic_data.accumulated_fees == 0);\n      BOOST_CHECK(test_asset_dynamic_data.fee_pool == 0);\n\n   } catch(fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( override_transfer_test )\n{ try {\n   ACTORS( (dan)(eric)(sam) );\n   const asset_object& advanced = create_user_issued_asset( \"ADVANCED\", sam, override_authority );\n   BOOST_TEST_MESSAGE( \"Issuing 1000 ADVANCED to dan\" );\n   issue_uia( dan, advanced.amount( 1000 ) );\n   BOOST_TEST_MESSAGE( \"Checking dan's balance\" );\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 1000 );\n\n   override_transfer_operation otrans;\n   otrans.issuer = advanced.issuer;\n   otrans.from = dan.id;\n   otrans.to   = eric.id;\n   otrans.amount = advanced.amount(100);\n   trx.operations.clear();\n   trx.operations.push_back(otrans);\n\n   BOOST_TEST_MESSAGE( \"Require throwing without signature\" );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, 0 ), tx_missing_active_auth );\n   BOOST_TEST_MESSAGE( \"Require throwing with dan's signature\" );\n   sign( trx,  dan_private_key  );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, 0 ), tx_missing_active_auth );\n   BOOST_TEST_MESSAGE( \"Pass with issuer's signature\" );\n   trx.clear_signatures();\n   sign( trx,  sam_private_key  );\n   PUSH_TX( db, trx, 0 );\n\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 900 );\n   BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 100 );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( override_transfer_test2 )\n{ try {\n   ACTORS( (dan)(eric)(sam) );\n   const asset_object& advanced = create_user_issued_asset( \"ADVANCED\", sam, 0 );\n   issue_uia( dan, advanced.amount( 1000 ) );\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 1000 );\n\n   trx.operations.clear();\n   override_transfer_operation otrans;\n   otrans.issuer = advanced.issuer;\n   otrans.from = dan.id;\n   otrans.to   = eric.id;\n   otrans.amount = advanced.amount(100);\n   trx.operations.push_back(otrans);\n\n   BOOST_TEST_MESSAGE( \"Require throwing without signature\" );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, 0 ), fc::exception);\n   BOOST_TEST_MESSAGE( \"Require throwing with dan's signature\" );\n   sign( trx,  dan_private_key  );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, 0 ), fc::exception);\n   BOOST_TEST_MESSAGE( \"Fail because overide_authority flag is not set\" );\n   trx.clear_signatures();\n   sign( trx,  sam_private_key  );\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, 0 ), fc::exception );\n\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 1000 );\n   BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 0 );\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( override_transfer_whitelist_test )\n{ try {\n   ACTORS( (dan)(eric)(sam) );\n   const asset_object& advanced = create_user_issued_asset( \"ADVANCED\", sam, white_list | override_authority );\n   asset_id_type advanced_id = advanced.get_id();\n   BOOST_TEST_MESSAGE( \"Issuing 1000 ADVANCED to dan\" );\n   issue_uia( dan, advanced.amount( 1000 ) );\n   BOOST_TEST_MESSAGE( \"Checking dan's balance\" );\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 1000 );\n\n   override_transfer_operation otrans;\n   otrans.issuer = advanced.issuer;\n   otrans.from = dan.id;\n   otrans.to   = eric.id;\n   otrans.amount = advanced.amount(100);\n   trx.operations.clear();\n   trx.operations.push_back(otrans);\n\n   PUSH_TX( db, trx, ~0 );\n\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 900 );\n   BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 100 );\n\n   // Make a whitelist, now it should fail\n   {\n      BOOST_TEST_MESSAGE( \"Changing the whitelist authority\" );\n      asset_update_operation uop;\n      uop.issuer = advanced_id(db).issuer;\n      uop.asset_to_update = advanced_id;\n      uop.new_options = advanced_id(db).options;\n      // The whitelist is managed by dan\n      uop.new_options.whitelist_authorities.insert(dan_id);\n      trx.operations.clear();\n      trx.operations.push_back(uop);\n      PUSH_TX( db, trx, ~0 );\n      auto whitelist_auths = advanced_id(db).options.whitelist_authorities;\n      BOOST_CHECK( whitelist_auths.find(dan_id) != whitelist_auths.end() );\n\n      // Upgrade dan so that he can manage the whitelist\n      upgrade_to_lifetime_member( dan_id );\n\n      // Add eric to the whitelist, but do not add dan\n      account_whitelist_operation wop;\n      wop.authorizing_account = dan_id;\n      wop.account_to_list = eric_id;\n      wop.new_listing = account_whitelist_operation::white_listed;\n      trx.operations.clear();\n      trx.operations.push_back(wop);\n      PUSH_TX( db, trx, ~0 );\n   }\n\n   // Fail because there is a whitelist authority and dan is not whitelisted\n   trx.operations.clear();\n   trx.operations.push_back(otrans);\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   // Balances did not change\n   BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 900 );\n   BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 100 );\n\n   // Apply core-2295 hardfork\n   generate_blocks( HARDFORK_CORE_2295_TIME );\n   set_expiration( db, trx );\n\n   // Now it's able to override-transfer from dan to eric\n   PUSH_TX( db, trx, ~0 );\n\n   // Check new balances\n   BOOST_REQUIRE_EQUAL( get_balance( dan_id, advanced_id ), 800 );\n   BOOST_REQUIRE_EQUAL( get_balance( eric_id, advanced_id ), 200 );\n\n   // Still can not override-transfer to sam because he is not whitelisted\n   otrans.to = sam_id;\n   trx.operations.clear();\n   trx.operations.push_back(otrans);\n   GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   generate_block();\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE( issue_whitelist_uia )\n{\n   try {\n      account_id_type izzy_id = create_account(\"izzy\").get_id();\n      const asset_id_type uia_id = create_user_issued_asset(\n         \"ADVANCED\", izzy_id(db), white_list ).get_id();\n      account_id_type nathan_id = create_account(\"nathan\").get_id();\n      account_id_type vikram_id = create_account(\"vikram\").get_id();\n      trx.clear();\n\n      asset_issue_operation op;\n      op.issuer = uia_id(db).issuer;\n      op.asset_to_issue = asset(1000, uia_id);\n      op.issue_to_account = nathan_id;\n      trx.operations.emplace_back(op);\n      set_expiration( db, trx );\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK(is_authorized_asset( db, nathan_id(db), uia_id(db) ));\n      BOOST_CHECK_EQUAL(get_balance(nathan_id, uia_id), 1000);\n\n      // committee-account is free as well\n      BOOST_CHECK( is_authorized_asset( db, account_id_type()(db), uia_id(db) ) );\n\n      // Make a whitelist, now it should fail\n      {\n         BOOST_TEST_MESSAGE( \"Changing the whitelist authority\" );\n         asset_update_operation uop;\n         uop.issuer = izzy_id;\n         uop.asset_to_update = uia_id;\n         uop.new_options = uia_id(db).options;\n         uop.new_options.whitelist_authorities.insert(izzy_id);\n         trx.operations.back() = uop;\n         PUSH_TX( db, trx, ~0 );\n         BOOST_CHECK( uia_id(db).options.whitelist_authorities.find(izzy_id) != uia_id(db).options.whitelist_authorities.end() );\n      }\n\n      // Fail because there is a whitelist authority and I'm not whitelisted\n      trx.operations.back() = op;\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n      // committee-account is blocked as well\n      BOOST_CHECK( !is_authorized_asset( db, account_id_type()(db), uia_id(db) ) );\n\n      account_whitelist_operation wop;\n      wop.authorizing_account = izzy_id;\n      wop.account_to_list = vikram_id;\n      wop.new_listing = account_whitelist_operation::white_listed;\n\n      trx.operations.back() = wop;\n      // Fail because whitelist function is restricted to members only\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n      upgrade_to_lifetime_member( izzy_id );\n      trx.operations.clear();\n      trx.operations.push_back( wop );\n      PUSH_TX( db, trx, ~0 );\n\n      // Still fail after an irrelevant account was added\n      trx.operations.back() = op;\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n      wop.account_to_list = nathan_id;\n      trx.operations.back() = wop;\n      PUSH_TX( db, trx, ~0 );\n      trx.operations.back() = op;\n      BOOST_CHECK_EQUAL(get_balance(nathan_id, uia_id), 1000);\n      // Finally succeed when we were whitelisted\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK_EQUAL(get_balance(nathan_id, uia_id), 2000);\n\n      // committee-account is still blocked\n      BOOST_CHECK( !is_authorized_asset( db, account_id_type()(db), uia_id(db) ) );\n      // izzy is still blocked\n      BOOST_CHECK( !is_authorized_asset( db, izzy_id(db), uia_id(db) ) );\n\n   } catch(fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_CASE( transfer_whitelist_uia )\n{\n   try {\n      INVOKE(issue_whitelist_uia);\n      const asset_object& advanced = get_asset(\"ADVANCED\");\n      const asset_id_type uia_id = advanced.get_id();\n      const account_object& nathan = get_account(\"nathan\");\n      const account_object& dan = create_account(\"dan\");\n      account_id_type izzy_id = get_account(\"izzy\").get_id();\n      upgrade_to_lifetime_member(dan);\n      trx.clear();\n\n      BOOST_TEST_MESSAGE( \"Atempting to transfer asset ADVANCED from nathan to dan when dan is not whitelisted, should fail\" );\n      transfer_operation op;\n      op.fee = advanced.amount(0);\n      op.from = nathan.id;\n      op.to = dan.id;\n      op.amount = advanced.amount(100); //({advanced.amount(0), nathan.id, dan.id, advanced.amount(100)});\n      trx.operations.push_back(op);\n      //Fail because dan is not whitelisted.\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), transfer_to_account_not_whitelisted );\n\n      BOOST_TEST_MESSAGE( \"Adding dan to whitelist for asset ADVANCED\" );\n      account_whitelist_operation wop;\n      wop.authorizing_account = izzy_id;\n      wop.account_to_list = dan.id;\n      wop.new_listing = account_whitelist_operation::white_listed;\n      trx.operations.back() = wop;\n      PUSH_TX( db, trx, ~0 );\n      BOOST_TEST_MESSAGE( \"Attempting to transfer from nathan to dan after whitelisting dan, should succeed\" );\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n\n      BOOST_CHECK_EQUAL(get_balance(nathan, advanced), 1900);\n      BOOST_CHECK_EQUAL(get_balance(dan, advanced), 100);\n\n      BOOST_TEST_MESSAGE( \"Attempting to blacklist nathan\" );\n      {\n         BOOST_TEST_MESSAGE( \"Changing the blacklist authority\" );\n         asset_update_operation uop;\n         uop.issuer = izzy_id;\n         uop.asset_to_update = advanced.id;\n         uop.new_options = advanced.options;\n         uop.new_options.blacklist_authorities.insert(izzy_id);\n         trx.operations.back() = uop;\n         PUSH_TX( db, trx, ~0 );\n         BOOST_CHECK( advanced.options.blacklist_authorities.find(izzy_id) != advanced.options.blacklist_authorities.end() );\n      }\n\n      wop.new_listing |= account_whitelist_operation::black_listed;\n      wop.account_to_list = nathan.id;\n      trx.operations.back() = wop;\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK( !(is_authorized_asset( db, nathan, advanced )) );\n\n      BOOST_TEST_MESSAGE( \"Attempting to transfer from nathan after blacklisting, should fail\" );\n      op.amount = advanced.amount(50);\n      trx.operations.back() = op;\n      // it fails because the fees are not in a whitelisted asset\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception );\n\n      BOOST_TEST_MESSAGE( \"Attempting to burn from nathan after blacklisting, should fail\" );\n      asset_reserve_operation burn;\n      burn.payer = nathan.id;\n      burn.amount_to_reserve = advanced.amount(10);\n      trx.operations.back() = burn;\n      //Fail because nathan is blacklisted\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n      BOOST_TEST_MESSAGE( \"Attempting transfer from dan back to nathan, should fail because nathan is blacklisted\" );\n      std::swap(op.from, op.to);\n      trx.operations.back() = op;\n      //Fail because nathan is blacklisted\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n\n      {\n         BOOST_TEST_MESSAGE( \"Changing the blacklist authority to dan\" );\n         asset_update_operation op;\n         op.issuer = izzy_id;\n         op.asset_to_update = advanced.id;\n         op.new_options = advanced.options;\n         op.new_options.blacklist_authorities.clear();\n         op.new_options.blacklist_authorities.insert(dan.get_id());\n         trx.operations.back() = op;\n         PUSH_TX( db, trx, ~0 );\n         BOOST_CHECK(advanced.options.blacklist_authorities.find(dan.get_id())\n                     != advanced.options.blacklist_authorities.end());\n      }\n\n      BOOST_TEST_MESSAGE( \"Attempting to transfer from dan back to nathan\" );\n      trx.operations.back() = op;\n      PUSH_TX( db, trx, ~0 );\n      BOOST_CHECK_EQUAL(get_balance(nathan, advanced), 1950);\n      BOOST_CHECK_EQUAL(get_balance(dan, advanced), 50);\n\n      BOOST_TEST_MESSAGE( \"Blacklisting nathan by dan\" );\n      wop.authorizing_account = dan.id;\n      wop.account_to_list = nathan.id;\n      wop.new_listing = account_whitelist_operation::black_listed;\n      trx.operations.back() = wop;\n      PUSH_TX( db, trx, ~0 );\n\n      trx.operations.back() = op;\n      //Fail because nathan is blacklisted\n      BOOST_CHECK(!is_authorized_asset( db, nathan, advanced ));\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n\n      //Remove nathan from committee's whitelist, add him to dan's. This should not authorize him to hold ADVANCED.\n      wop.authorizing_account = izzy_id;\n      wop.account_to_list = nathan.id;\n      wop.new_listing = account_whitelist_operation::no_listing;\n      trx.operations.back() = wop;\n      PUSH_TX( db, trx, ~0 );\n      wop.authorizing_account = dan.id;\n      wop.account_to_list = nathan.id;\n      wop.new_listing = account_whitelist_operation::white_listed;\n      trx.operations.back() = wop;\n      PUSH_TX( db, trx, ~0 );\n\n      trx.operations.back() = op;\n      //Fail because nathan is not whitelisted\n      BOOST_CHECK(!is_authorized_asset( db, nathan, advanced ));\n      GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);\n\n      burn.payer = dan.id;\n      burn.amount_to_reserve = advanced.amount(10);\n      trx.operations.back() = burn;\n      PUSH_TX(db, trx, ~0);\n      BOOST_CHECK_EQUAL(get_balance(dan, advanced), 40);\n\n      // committee-account is still blocked\n      BOOST_CHECK( !is_authorized_asset( db, account_id_type()(db), uia_id(db) ) );\n      // izzy is still blocked\n      BOOST_CHECK( !is_authorized_asset( db, izzy_id(db), uia_id(db) ) );\n\n      // Pass BSIP 86 hardfork\n      generate_blocks( HARDFORK_BSIP_86_TIME );\n\n      // committee-account is now unblocked\n      BOOST_CHECK( is_authorized_asset( db, account_id_type()(db), uia_id(db) ) );\n      // izzy is still blocked\n      BOOST_CHECK( !is_authorized_asset( db, izzy_id(db), uia_id(db) ) );\n\n   } catch(fc::exception& e) {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/**\n * verify that issuers can halt transfers\n */\nBOOST_AUTO_TEST_CASE( transfer_restricted_test )\n{\n   try\n   {\n      ACTORS( (sam)(alice)(bob) );\n\n      BOOST_TEST_MESSAGE( \"Issuing 1000 UIA to Alice\" );\n\n      auto _issue_uia = [&]( const account_object& recipient, asset amount )\n      {\n         asset_issue_operation op;\n         op.issuer = amount.asset_id(db).issuer;\n         op.asset_to_issue = amount;\n         op.issue_to_account = recipient.id;\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      } ;\n\n      const asset_object& uia = create_user_issued_asset( \"TXRX\", sam, transfer_restricted );\n      _issue_uia( alice, uia.amount( 1000 ) );\n\n      auto _restrict_xfer = [&]( bool xfer_flag )\n      {\n         asset_update_operation op;\n         op.issuer = sam_id;\n         op.asset_to_update = uia.id;\n         op.new_options = uia.options;\n         if( xfer_flag )\n            op.new_options.flags |= transfer_restricted;\n         else\n            op.new_options.flags &= ~transfer_restricted;\n         transaction tx;\n         tx.operations.push_back( op );\n         set_expiration( db, tx );\n         PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );\n      } ;\n\n      BOOST_TEST_MESSAGE( \"Enable transfer_restricted, send fails\" );\n\n      transfer_operation xfer_op;\n      xfer_op.from = alice_id;\n      xfer_op.to = bob_id;\n      xfer_op.amount = uia.amount(100);\n      signed_transaction xfer_tx;\n      xfer_tx.operations.push_back( xfer_op );\n      set_expiration( db, xfer_tx );\n      sign( xfer_tx, alice_private_key );\n\n      _restrict_xfer( true );\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, xfer_tx ), transfer_restricted_transfer_asset );\n\n      BOOST_TEST_MESSAGE( \"Disable transfer_restricted, send succeeds\" );\n\n      _restrict_xfer( false );\n      PUSH_TX( db, xfer_tx );\n\n      xfer_op.amount = uia.amount(101);\n\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\n/***\n * Test to see if a asset name is valid\n * @param db the database\n * @param acct the account that will attempt to create the asset\n * @param asset_name the asset_name\n * @param allowed whether the creation should be successful\n * @returns true if meets expectations\n */\nbool test_asset_name(graphene::chain::database_fixture* db, const graphene::chain::account_object& acct, std::string asset_name, bool allowed)\n{\n   if (allowed)\n   {\n      try\n      {\n         db->create_user_issued_asset(asset_name, acct, 0);\n      } catch (...)\n      {\n         return false;\n      }\n   }\n   else\n   {\n      try\n      {\n         db->create_user_issued_asset(asset_name, acct, 0);\n         return false;\n      } catch (fc::exception& ex) \n      {\n         return true;\n      } catch (...)\n      {\n         return false;\n      }\n   }\n   return true;\n}\n\n/***\n * Test to see if an ascii character can be used in an asset name\n * @param c the ascii character (NOTE: includes extended ascii up to 255)\n * @param allowed_beginning true if it should be allowed as the first character of an asset name\n * @param allowed_middle true if it should be allowed in the middle of an asset name\n * @param allowed_end true if it should be allowed at the end of an asset name\n * @returns true if tests met expectations\n */\nbool test_asset_char(graphene::chain::database_fixture* db, const graphene::chain::account_object& acct, const unsigned char& c, bool allowed_beginning, bool allowed_middle, bool allowed_end)\n{\n   std::ostringstream asset_name;\n   // beginning\n   asset_name << c << \"CHARLIE\";\n   if (!test_asset_name(db, acct, asset_name.str(), allowed_beginning))\n      return false;\n\n   // middle\n   asset_name.str(\"\");\n   asset_name.clear();\n   asset_name << \"CHAR\" << c << \"LIE\";\n   if (!test_asset_name(db, acct, asset_name.str(), allowed_middle))\n      return false;\n\n   // end\n   asset_name.str(\"\");\n   asset_name.clear();\n   asset_name << \"CHARLIE\" << c;\n   return test_asset_name(db, acct, asset_name.str(), allowed_end);\n}\n\nBOOST_AUTO_TEST_CASE( asset_name_test )\n{\n   try\n   {\n      ACTORS( (alice)(bob)(sam) );\n\n      auto has_asset = [&]( std::string symbol ) -> bool\n      {\n         const auto& assets_by_symbol = db.get_index_type<asset_index>().indices().get<by_symbol>();\n         return assets_by_symbol.find( symbol ) != assets_by_symbol.end();\n      };\n\n      // Alice creates asset \"ALPHA\"\n      BOOST_CHECK( !has_asset(\"ALPHA\") );    BOOST_CHECK( !has_asset(\"ALPHA.ONE\") );\n      create_user_issued_asset( \"ALPHA\", alice_id(db), 0 );\n      BOOST_CHECK(  has_asset(\"ALPHA\") );    BOOST_CHECK( !has_asset(\"ALPHA.ONE\") );\n\n      // Nobody can create another asset named ALPHA\n      GRAPHENE_REQUIRE_THROW( create_user_issued_asset( \"ALPHA\",   bob_id(db), 0 ), fc::exception );\n      BOOST_CHECK(  has_asset(\"ALPHA\") );    BOOST_CHECK( !has_asset(\"ALPHA.ONE\") );\n      GRAPHENE_REQUIRE_THROW( create_user_issued_asset( \"ALPHA\", alice_id(db), 0 ), fc::exception );\n      BOOST_CHECK(  has_asset(\"ALPHA\") );    BOOST_CHECK( !has_asset(\"ALPHA.ONE\") );\n\n      generate_blocks( HARDFORK_385_TIME );\n      generate_block();\n\n      // Bob can't create ALPHA.ONE\n      GRAPHENE_REQUIRE_THROW( create_user_issued_asset( \"ALPHA.ONE\", bob_id(db), 0 ), fc::exception );\n      BOOST_CHECK(  has_asset(\"ALPHA\") );    BOOST_CHECK( !has_asset(\"ALPHA.ONE\") );\n\n      // Alice can create ALPHA.ONE\n      create_user_issued_asset( \"ALPHA.ONE\", alice_id(db), 0 );\n      BOOST_CHECK(  has_asset(\"ALPHA\") );    BOOST_CHECK( has_asset(\"ALPHA.ONE\") );\n\n      // create a proposal to create asset ending in a number\n      auto& core = asset_id_type()(db);\n      asset_create_operation op_p;\n      op_p.issuer = alice_id;\n      op_p.symbol = \"SP500\";\n      op_p.common_options.core_exchange_rate = asset( 1 ) / asset( 1, asset_id_type( 1 ) );\n      op_p.fee = core.amount(0);\n\n      const auto& curfees = db.get_global_properties().parameters.get_current_fees();\n      const auto& proposal_create_fees = curfees.get<proposal_create_operation>();\n      proposal_create_operation prop;\n      prop.fee_paying_account = alice_id;\n      prop.proposed_ops.emplace_back( op_p );\n      prop.expiration_time =  db.head_block_time() + fc::days(1);\n      prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte );\n\n      signed_transaction tx;\n      tx.operations.push_back( prop );\n      db.current_fee_schedule().set_fee( tx.operations.back() );\n      set_expiration( db, tx );\n      sign( tx, alice_private_key );\n      PUSH_TX( db, tx );\n\n      generate_block();\n\n      // Sam can create asset ending in number after hf_620\n      create_user_issued_asset( \"NIKKEI225\", sam_id(db), 0 );\n      BOOST_CHECK(  has_asset(\"NIKKEI225\") );\n\n      // make sure other assets can still be created after hf_620\n      create_user_issued_asset( \"ALPHA2\", alice_id(db), 0 );\n      create_user_issued_asset( \"ALPHA2.ONE\", alice_id(db), 0 );\n      BOOST_CHECK(  has_asset(\"ALPHA2\") );\n      BOOST_CHECK( has_asset(\"ALPHA2.ONE\") );\n\n      // proposal to create asset ending in number will now be created successfully as we are in > hf_620 time\n      prop.expiration_time =  db.head_block_time() + fc::days(3);\n      signed_transaction tx_hf620;\n      tx_hf620.operations.push_back( prop );\n      db.current_fee_schedule().set_fee( tx_hf620.operations.back() );\n      set_expiration( db, tx_hf620 );\n      sign( tx_hf620, alice_private_key );\n      PUSH_TX( db, tx_hf620 );\n\n      // assets with invalid characters should not be allowed\n      unsigned char c = 0;\n      do\n      {\n         if ( (c >= 48 && c <= 57) ) // numbers\n            BOOST_CHECK_MESSAGE( test_asset_char(this, alice_id(db), c, false, true, true),\n                                 \"Failed on good ASCII value \" + std::to_string(c) );\n         else if ( c >= 65 && c <= 90) // letters\n            BOOST_CHECK_MESSAGE( test_asset_char(this, alice_id(db), c, true, true, true),\n                                 \"Failed on good ASCII value \" + std::to_string(c) );\n         else                       // everything else\n            BOOST_CHECK_MESSAGE( test_asset_char(this, alice_id(db), c, false, false, false),\n                                 \"Failed on bad ASCII value \" + std::to_string(c) );\n         c++;\n      } while (c != 0);\n   }\n   catch(fc::exception& e)\n   {\n      edump((e.to_detail_string()));\n      throw;\n   }\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/voting_tests.cpp",
    "content": "/*\n * Copyright (c) 2018 oxarbitrage, and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/database_api.hpp>\n#include <graphene/chain/exceptions.hpp>\n#include <graphene/chain/hardfork.hpp>\n\n#include <iostream>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\n\n\nBOOST_FIXTURE_TEST_SUITE(voting_tests, database_fixture)\n\nBOOST_FIXTURE_TEST_CASE( committee_account_initialization_test, database_fixture )\n{ try {\n   // Check current default committee\n   // By default chain is configured with INITIAL_COMMITTEE_MEMBER_COUNT=9 members\n   const auto &committee_members = db.get_global_properties().active_committee_members;\n   const auto &committee = committee_account(db);\n\n   BOOST_CHECK_EQUAL(committee_members.size(), INITIAL_COMMITTEE_MEMBER_COUNT);\n   BOOST_CHECK_EQUAL(committee.active.num_auths(), INITIAL_COMMITTEE_MEMBER_COUNT);\n\n   generate_blocks(HARDFORK_533_TIME);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n   generate_block();\n   set_expiration(db, trx);\n\n   // Check that committee not changed after 533 hardfork\n   // vote counting method changed, but any votes are absent\n   const auto &committee_members_after_hf533 = db.get_global_properties().active_committee_members;\n   const auto &committee_after_hf533 = committee_account(db);\n   BOOST_CHECK_EQUAL(committee_members_after_hf533.size(), INITIAL_COMMITTEE_MEMBER_COUNT);\n   BOOST_CHECK_EQUAL(committee_after_hf533.active.num_auths(), INITIAL_COMMITTEE_MEMBER_COUNT);\n\n   // You can't use uninitialized committee after 533 hardfork\n   // when any user with stake created (create_account method automatically set up votes for committee)\n   // committee is incomplete and consist of random active members\n   ACTOR(alice);\n   fund(alice);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   const auto &committee_after_hf533_with_stake = committee_account(db);\n   BOOST_CHECK_LT(committee_after_hf533_with_stake.active.num_auths(), INITIAL_COMMITTEE_MEMBER_COUNT);\n\n   // Initialize committee by voting for each memeber and for desired count\n   vote_for_committee_and_witnesses(INITIAL_COMMITTEE_MEMBER_COUNT, INITIAL_WITNESS_COUNT);\n   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n\n   const auto &committee_members_after_hf533_and_init = db.get_global_properties().active_committee_members;\n   const auto &committee_after_hf533_and_init = committee_account(db);\n   BOOST_CHECK_EQUAL(committee_members_after_hf533_and_init.size(), INITIAL_COMMITTEE_MEMBER_COUNT);\n   BOOST_CHECK_EQUAL(committee_after_hf533_and_init.active.num_auths(), INITIAL_COMMITTEE_MEMBER_COUNT);\n\n} FC_LOG_AND_RETHROW() }\n\nBOOST_AUTO_TEST_CASE(put_my_witnesses)\n{\n   try\n   {\n      ACTORS( (witness0)\n              (witness1)\n              (witness2)\n              (witness3)\n              (witness4)\n              (witness5)\n              (witness6)\n              (witness7)\n              (witness8)\n              (witness9)\n              (witness10)\n              (witness11)\n              (witness12)\n              (witness13) );\n\n      // Upgrade all accounts to LTM\n      upgrade_to_lifetime_member(witness0_id);\n      upgrade_to_lifetime_member(witness1_id);\n      upgrade_to_lifetime_member(witness2_id);\n      upgrade_to_lifetime_member(witness3_id);\n      upgrade_to_lifetime_member(witness4_id);\n      upgrade_to_lifetime_member(witness5_id);\n      upgrade_to_lifetime_member(witness6_id);\n      upgrade_to_lifetime_member(witness7_id);\n      upgrade_to_lifetime_member(witness8_id);\n      upgrade_to_lifetime_member(witness9_id);\n      upgrade_to_lifetime_member(witness10_id);\n      upgrade_to_lifetime_member(witness11_id);\n      upgrade_to_lifetime_member(witness12_id);\n      upgrade_to_lifetime_member(witness13_id);\n\n      // Create all the witnesses\n      const witness_id_type witness0_witness_id = create_witness(witness0_id, witness0_private_key).get_id();\n      const witness_id_type witness1_witness_id = create_witness(witness1_id, witness1_private_key).get_id();\n      const witness_id_type witness2_witness_id = create_witness(witness2_id, witness2_private_key).get_id();\n      const witness_id_type witness3_witness_id = create_witness(witness3_id, witness3_private_key).get_id();\n      const witness_id_type witness4_witness_id = create_witness(witness4_id, witness4_private_key).get_id();\n      const witness_id_type witness5_witness_id = create_witness(witness5_id, witness5_private_key).get_id();\n      const witness_id_type witness6_witness_id = create_witness(witness6_id, witness6_private_key).get_id();\n      const witness_id_type witness7_witness_id = create_witness(witness7_id, witness7_private_key).get_id();\n      const witness_id_type witness8_witness_id = create_witness(witness8_id, witness8_private_key).get_id();\n      const witness_id_type witness9_witness_id = create_witness(witness9_id, witness9_private_key).get_id();\n      const witness_id_type witness10_witness_id = create_witness(witness10_id, witness10_private_key).get_id();\n      const witness_id_type witness11_witness_id = create_witness(witness11_id, witness11_private_key).get_id();\n      const witness_id_type witness12_witness_id = create_witness(witness12_id, witness12_private_key).get_id();\n      const witness_id_type witness13_witness_id = create_witness(witness13_id, witness13_private_key).get_id();\n\n      // Create a vector with private key of all witnesses, will be used to activate 9 witnesses at a time\n      const vector <fc::ecc::private_key> private_keys = {\n            witness0_private_key,\n            witness1_private_key,\n            witness2_private_key,\n            witness3_private_key,\n            witness4_private_key,\n            witness5_private_key,\n            witness6_private_key,\n            witness7_private_key,\n            witness8_private_key,\n            witness9_private_key,\n            witness10_private_key,\n            witness11_private_key,\n            witness12_private_key,\n            witness13_private_key\n\n      };\n\n      // create a map with account id and witness id\n      const flat_map <account_id_type, witness_id_type> witness_map = {\n            {witness0_id, witness0_witness_id},\n            {witness1_id, witness1_witness_id},\n            {witness2_id, witness2_witness_id},\n            {witness3_id, witness3_witness_id},\n            {witness4_id, witness4_witness_id},\n            {witness5_id, witness5_witness_id},\n            {witness6_id, witness6_witness_id},\n            {witness7_id, witness7_witness_id},\n            {witness8_id, witness8_witness_id},\n            {witness9_id, witness9_witness_id},\n            {witness10_id, witness10_witness_id},\n            {witness11_id, witness11_witness_id},\n            {witness12_id, witness12_witness_id},\n            {witness13_id, witness13_witness_id}\n      };\n\n      // Check current default witnesses, default chain is configured with 9 witnesses\n      auto witnesses = db.get_global_properties().active_witnesses;\n      BOOST_CHECK_EQUAL(witnesses.size(), INITIAL_WITNESS_COUNT);\n      BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 1u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 2u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 3u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 4u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 5u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 6u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 7u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 8u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 9u);\n\n      // Activate all witnesses\n      // Each witness is voted with incremental stake so last witness created will be the ones with more votes\n      int c = 0;\n      for (auto l : witness_map) {\n         int stake = 100 + c + 10;\n         transfer(committee_account, l.first, asset(stake));\n         {\n            set_expiration(db, trx);\n            account_update_operation op;\n            op.account = l.first;\n            op.new_options = l.first(db).options;\n            op.new_options->votes.insert(l.second(db).vote_id);\n\n            trx.operations.push_back(op);\n            sign(trx, private_keys.at(c));\n            PUSH_TX(db, trx);\n            trx.clear();\n         }\n         ++c;\n      }\n\n      // Trigger the new witnesses\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      // Check my witnesses are now in control of the system\n      witnesses = db.get_global_properties().active_witnesses;\n      BOOST_CHECK_EQUAL(witnesses.size(), INITIAL_WITNESS_COUNT);\n      BOOST_CHECK_EQUAL(witnesses.begin()[0].instance.value, 16u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[1].instance.value, 17u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[2].instance.value, 18u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[3].instance.value, 19u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[4].instance.value, 20u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[5].instance.value, 21u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[6].instance.value, 22u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[7].instance.value, 23u);\n      BOOST_CHECK_EQUAL(witnesses.begin()[8].instance.value, 24u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(track_votes_witnesses_enabled)\n{\n   try\n   {\n      graphene::app::database_api db_api1(db);\n\n      INVOKE(put_my_witnesses);\n\n      const account_id_type witness1_id= get_account(\"witness1\").get_id();\n      auto witness1_object = db_api1.get_witness_by_account(witness1_id(db).name);\n      BOOST_CHECK_EQUAL(witness1_object->total_votes, 111u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(track_votes_witnesses_disabled)\n{\n   try\n   {\n      graphene::app::database_api db_api1(db);\n\n      INVOKE(put_my_witnesses);\n\n      const account_id_type witness1_id= get_account(\"witness1\").get_id();\n      auto witness1_object = db_api1.get_witness_by_account(witness1_id(db).name);\n      BOOST_CHECK_EQUAL(witness1_object->total_votes, 0u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(put_my_committee_members)\n{\n   try\n   {\n      ACTORS( (committee0)\n              (committee1)\n              (committee2)\n              (committee3)\n              (committee4)\n              (committee5)\n              (committee6)\n              (committee7)\n              (committee8)\n              (committee9)\n              (committee10)\n              (committee11)\n              (committee12)\n              (committee13) );\n\n      // Upgrade all accounts to LTM\n      upgrade_to_lifetime_member(committee0_id);\n      upgrade_to_lifetime_member(committee1_id);\n      upgrade_to_lifetime_member(committee2_id);\n      upgrade_to_lifetime_member(committee3_id);\n      upgrade_to_lifetime_member(committee4_id);\n      upgrade_to_lifetime_member(committee5_id);\n      upgrade_to_lifetime_member(committee6_id);\n      upgrade_to_lifetime_member(committee7_id);\n      upgrade_to_lifetime_member(committee8_id);\n      upgrade_to_lifetime_member(committee9_id);\n      upgrade_to_lifetime_member(committee10_id);\n      upgrade_to_lifetime_member(committee11_id);\n      upgrade_to_lifetime_member(committee12_id);\n      upgrade_to_lifetime_member(committee13_id);\n\n      // Create all the committee\n      const committee_member_id_type committee0_committee_id = create_committee_member(committee0_id(db)).get_id();\n      const committee_member_id_type committee1_committee_id = create_committee_member(committee1_id(db)).get_id();\n      const committee_member_id_type committee2_committee_id = create_committee_member(committee2_id(db)).get_id();\n      const committee_member_id_type committee3_committee_id = create_committee_member(committee3_id(db)).get_id();\n      const committee_member_id_type committee4_committee_id = create_committee_member(committee4_id(db)).get_id();\n      const committee_member_id_type committee5_committee_id = create_committee_member(committee5_id(db)).get_id();\n      const committee_member_id_type committee6_committee_id = create_committee_member(committee6_id(db)).get_id();\n      const committee_member_id_type committee7_committee_id = create_committee_member(committee7_id(db)).get_id();\n      const committee_member_id_type committee8_committee_id = create_committee_member(committee8_id(db)).get_id();\n      const committee_member_id_type committee9_committee_id = create_committee_member(committee9_id(db)).get_id();\n      const committee_member_id_type committee10_committee_id = create_committee_member(committee10_id(db)).get_id();\n      const committee_member_id_type committee11_committee_id = create_committee_member(committee11_id(db)).get_id();\n      const committee_member_id_type committee12_committee_id = create_committee_member(committee12_id(db)).get_id();\n      const committee_member_id_type committee13_committee_id = create_committee_member(committee13_id(db)).get_id();\n\n      // Create a vector with private key of all committee members, will be used to activate 9 members at a time\n      const vector <fc::ecc::private_key> private_keys = {\n            committee0_private_key,\n            committee1_private_key,\n            committee2_private_key,\n            committee3_private_key,\n            committee4_private_key,\n            committee5_private_key,\n            committee6_private_key,\n            committee7_private_key,\n            committee8_private_key,\n            committee9_private_key,\n            committee10_private_key,\n            committee11_private_key,\n            committee12_private_key,\n            committee13_private_key\n      };\n\n      // create a map with account id and committee member id\n      const flat_map <account_id_type, committee_member_id_type> committee_map = {\n            {committee0_id, committee0_committee_id},\n            {committee1_id, committee1_committee_id},\n            {committee2_id, committee2_committee_id},\n            {committee3_id, committee3_committee_id},\n            {committee4_id, committee4_committee_id},\n            {committee5_id, committee5_committee_id},\n            {committee6_id, committee6_committee_id},\n            {committee7_id, committee7_committee_id},\n            {committee8_id, committee8_committee_id},\n            {committee9_id, committee9_committee_id},\n            {committee10_id, committee10_committee_id},\n            {committee11_id, committee11_committee_id},\n            {committee12_id, committee12_committee_id},\n            {committee13_id, committee13_committee_id}\n      };\n\n      // Check current default committee, default chain is configured with 9 committee members\n      auto committee_members = db.get_global_properties().active_committee_members;\n\n      BOOST_CHECK_EQUAL(committee_members.size(), INITIAL_COMMITTEE_MEMBER_COUNT);\n      BOOST_CHECK_EQUAL(committee_members.begin()[0].instance.value, 0u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[1].instance.value, 1u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[2].instance.value, 2u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[3].instance.value, 3u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[4].instance.value, 4u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[5].instance.value, 5u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[6].instance.value, 6u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[7].instance.value, 7u);\n      BOOST_CHECK_EQUAL(committee_members.begin()[8].instance.value, 8u);\n\n      // Activate all committee\n      // Each committee is voted with incremental stake so last member created will be the ones with more votes\n      int c = 0;\n      for (auto committee : committee_map) {\n         int stake = 100 + c + 10;\n         transfer(committee_account, committee.first, asset(stake));\n         {\n            set_expiration(db, trx);\n            account_update_operation op;\n            op.account = committee.first;\n            op.new_options = committee.first(db).options;\n\n            op.new_options->votes.clear();\n            op.new_options->votes.insert(committee.second(db).vote_id);\n            op.new_options->num_committee = 1;\n\n            trx.operations.push_back(op);\n            sign(trx, private_keys.at(c));\n            PUSH_TX(db, trx);\n            trx.clear();\n         }\n         ++c;\n      }\n\n      // Trigger the new committee\n      generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);\n      generate_block();\n\n      // Check my witnesses are now in control of the system\n      committee_members = db.get_global_properties().active_committee_members;\n      std::sort(committee_members.begin(), committee_members.end());\n\n      BOOST_CHECK_EQUAL(committee_members.size(), INITIAL_COMMITTEE_MEMBER_COUNT);\n\n      // Check my committee members are now in control of the system\n      BOOST_CHECK_EQUAL(committee_members.begin()[0].instance.value, 15);\n      BOOST_CHECK_EQUAL(committee_members.begin()[1].instance.value, 16);\n      BOOST_CHECK_EQUAL(committee_members.begin()[2].instance.value, 17);\n      BOOST_CHECK_EQUAL(committee_members.begin()[3].instance.value, 18);\n      BOOST_CHECK_EQUAL(committee_members.begin()[4].instance.value, 19);\n      BOOST_CHECK_EQUAL(committee_members.begin()[5].instance.value, 20);\n      BOOST_CHECK_EQUAL(committee_members.begin()[6].instance.value, 21);\n      BOOST_CHECK_EQUAL(committee_members.begin()[7].instance.value, 22);\n      BOOST_CHECK_EQUAL(committee_members.begin()[8].instance.value, 23);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(track_votes_committee_enabled)\n{\n   try\n   {\n      graphene::app::database_api db_api1(db);\n\n      INVOKE(put_my_committee_members);\n\n      const account_id_type committee1_id= get_account(\"committee1\").get_id();\n      auto committee1_object = db_api1.get_committee_member_by_account(committee1_id(db).name);\n      BOOST_CHECK_EQUAL(committee1_object->total_votes, 111u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(track_votes_committee_disabled)\n{\n   try\n   {\n      graphene::app::database_api db_api1(db);\n\n      INVOKE(put_my_committee_members);\n\n      const account_id_type committee1_id= get_account(\"committee1\").get_id();\n      auto committee1_object = db_api1.get_committee_member_by_account(committee1_id(db).name);\n      BOOST_CHECK_EQUAL(committee1_object->total_votes, 0u);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE(invalid_voting_account)\n{\n   try\n   {\n      ACTORS((alice));\n\n      account_id_type invalid_account_id( (uint64_t)999999 );\n\n      BOOST_CHECK( !db.find( invalid_account_id ) );\n\n      graphene::chain::account_update_operation op;\n      op.account = alice_id;\n      op.new_options = alice.options;\n      op.new_options->voting_account = invalid_account_id;\n      trx.operations.push_back(op);\n      sign(trx, alice_private_key);\n\n      GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception );\n\n   } FC_LOG_AND_RETHROW()\n}\nBOOST_AUTO_TEST_CASE(last_voting_date)\n{\n   try\n   {\n      ACTORS((alice));\n\n      transfer(committee_account, alice_id, asset(100));\n\n      // we are going to vote for this witness\n      auto witness1 = witness_id_type(1)(db);\n\n      auto stats_obj = db.get_account_stats_by_owner(alice_id);\n      BOOST_CHECK_EQUAL(stats_obj.last_vote_time.sec_since_epoch(), 0u);\n\n      // alice votes\n      graphene::chain::account_update_operation op;\n      op.account = alice_id;\n      op.new_options = alice.options;\n      op.new_options->votes.insert(witness1.vote_id);\n      trx.operations.push_back(op);\n      sign(trx, alice_private_key);\n      PUSH_TX( db, trx, ~0 );\n\n      auto now = db.head_block_time().sec_since_epoch();\n\n      // last_vote_time is updated for alice\n      stats_obj = db.get_account_stats_by_owner(alice_id);\n      BOOST_CHECK_EQUAL(stats_obj.last_vote_time.sec_since_epoch(), now);\n\n   } FC_LOG_AND_RETHROW()\n}\nBOOST_AUTO_TEST_CASE(last_voting_date_proxy)\n{\n   try\n   {\n      ACTORS((alice)(proxy)(bob));\n\n      transfer(committee_account, alice_id, asset(100));\n      transfer(committee_account, bob_id, asset(200));\n      transfer(committee_account, proxy_id, asset(300));\n\n      generate_block();\n\n      // witness to vote for\n      auto witness1 = witness_id_type(1)(db);\n\n      // round1: alice changes proxy, this is voting activity\n      {\n         graphene::chain::account_update_operation op;\n         op.account = alice_id;\n         op.new_options = alice_id(db).options;\n         op.new_options->voting_account = proxy_id;\n         trx.operations.push_back(op);\n         sign(trx, alice_private_key);\n         PUSH_TX( db, trx, ~0 );\n      }\n      // alice last_vote_time is updated\n      auto alice_stats_obj = db.get_account_stats_by_owner(alice_id);\n      auto round1 = db.head_block_time().sec_since_epoch();\n      BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1);\n\n      generate_block();\n\n      // round 2: alice update account but no proxy or voting changes are done\n      {\n         graphene::chain::account_update_operation op;\n         op.account = alice_id;\n         op.new_options = alice_id(db).options;\n         trx.operations.push_back(op);\n         sign(trx, alice_private_key);\n         set_expiration( db, trx );\n         PUSH_TX( db, trx, ~0 );\n      }\n      // last_vote_time is not updated\n      alice_stats_obj = db.get_account_stats_by_owner(alice_id);\n      BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1);\n\n      generate_block();\n\n      // round 3: bob votes\n      {\n         graphene::chain::account_update_operation op;\n         op.account = bob_id;\n         op.new_options = bob_id(db).options;\n         op.new_options->votes.insert(witness1.vote_id);\n         trx.operations.push_back(op);\n         sign(trx, bob_private_key);\n         set_expiration( db, trx );\n         PUSH_TX(db, trx, ~0);\n      }\n\n      // last_vote_time for bob is updated as he voted\n      auto round3 = db.head_block_time().sec_since_epoch();\n      auto bob_stats_obj = db.get_account_stats_by_owner(bob_id);\n      BOOST_CHECK_EQUAL(bob_stats_obj.last_vote_time.sec_since_epoch(), round3);\n\n      generate_block();\n\n      // round 4: proxy votes\n      {\n         graphene::chain::account_update_operation op;\n         op.account = proxy_id;\n         op.new_options = proxy_id(db).options;\n         op.new_options->votes.insert(witness1.vote_id);\n         trx.operations.push_back(op);\n         sign(trx, proxy_private_key);\n         PUSH_TX(db, trx, ~0);\n      }\n\n      // proxy just voted so the last_vote_time is updated\n      auto round4 = db.head_block_time().sec_since_epoch();\n      auto proxy_stats_obj = db.get_account_stats_by_owner(proxy_id);\n      BOOST_CHECK_EQUAL(proxy_stats_obj.last_vote_time.sec_since_epoch(), round4);\n\n      // alice haves proxy, proxy votes but last_vote_time is not updated for alice\n      alice_stats_obj = db.get_account_stats_by_owner(alice_id);\n      BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1);\n\n      // bob haves nothing to do with proxy so last_vote_time is not updated\n      bob_stats_obj = db.get_account_stats_by_owner(bob_id);\n      BOOST_CHECK_EQUAL(bob_stats_obj.last_vote_time.sec_since_epoch(), round3);\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( witness_votes_calculation )\n{\n   try\n   {\n      auto original_wits = db.get_global_properties().active_witnesses;\n\n      INVOKE( put_my_witnesses );\n\n      GET_ACTOR( witness0 );\n      GET_ACTOR( witness1 );\n      GET_ACTOR( witness2 );\n      GET_ACTOR( witness3 );\n      GET_ACTOR( witness4 );\n      GET_ACTOR( witness5 );\n      GET_ACTOR( witness6 );\n      GET_ACTOR( witness7 );\n      GET_ACTOR( witness8 );\n      GET_ACTOR( witness9 );\n      GET_ACTOR( witness10 );\n      GET_ACTOR( witness11 );\n      GET_ACTOR( witness12 );\n      GET_ACTOR( witness13 );\n\n      graphene::app::database_api db_api1(db);\n\n      vector<account_id_type> wit_account_ids = { witness0_id, witness1_id, witness2_id, witness3_id,\n                                                  witness4_id, witness5_id, witness6_id, witness7_id,\n                                                  witness8_id, witness9_id, witness10_id, witness11_id,\n                                                  witness12_id, witness13_id };\n      vector<witness_id_type> wit_ids;\n      size_t total = wit_account_ids.size();\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         auto wit_object = db_api1.get_witness_by_account( wit_account_ids[i](db).name );\n         BOOST_REQUIRE( wit_object.valid() );\n         wit_ids.push_back( wit_object->get_id() );\n      }\n\n      generate_blocks( HARDFORK_CORE_2103_TIME - 750 * 86400 );\n      set_expiration( db, trx );\n\n      // refresh last_vote_time\n      for( size_t i = 0; i < total; ++i )\n      {\n         account_id_type voter = wit_account_ids[ total - i - 1 ];\n\n         account_update_operation op;\n         op.account = voter;\n         op.new_options = op.account(db).options;\n         op.new_options->voting_account = account_id_type();\n\n         trx.operations.clear();\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n\n         op.new_options->voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n         trx.operations.clear();\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n\n         trx.clear();\n\n         generate_blocks( db.head_block_time() + 45 * 86400 );\n         set_expiration( db, trx );\n      }\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, 110u + i );\n      }\n\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      set_expiration( db, trx );\n\n      uint64_t expected_votes[14];\n\n      expected_votes[0] = 110; // 750 - 45 * 13 = 165 days\n      expected_votes[1] = 111; // 210 days\n      expected_votes[2] = 112; // 255 days\n      expected_votes[3] = 113; // 300 days\n      expected_votes[4] = 114; // 345 days\n      expected_votes[5] = 115 - 115 / 8; // 390 days\n      expected_votes[6] = 116 - 116 * 2 / 8; // 435 days\n      expected_votes[7] = 117 - 117 * 3 / 8; // 480 days\n      expected_votes[8] = 118 - 118 * 4 / 8; // 525 days\n      expected_votes[9] = 119 - 119 * 5 / 8; // 570 days\n      expected_votes[10] = 120 - 120 * 6 / 8; // 615 days\n      expected_votes[11] = 121 - 121 * 7 / 8; // 660 days\n      expected_votes[12] = 0; // 705 days\n      expected_votes[13] = 0; // 750 days\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      flat_set<witness_id_type> expected_active_witnesses = { wit_ids[0], wit_ids[1], wit_ids[2],\n                                                              wit_ids[3], wit_ids[4], wit_ids[5],\n                                                              wit_ids[6], wit_ids[7], wit_ids[8] };\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // new vote\n      {\n         account_update_operation op;\n         op.account = wit_account_ids[12];\n         op.new_options = op.account(db).options;\n         op.new_options->votes.insert( wit_ids[8](db).vote_id );\n\n         trx.operations.clear();\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n      }\n\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      expected_votes[8] += 122;\n      expected_votes[12] = 122;\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      expected_active_witnesses = { wit_ids[0], wit_ids[1], wit_ids[2],\n                                    wit_ids[3], wit_ids[4], wit_ids[5],\n                                    wit_ids[6], wit_ids[8], wit_ids[12] };\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // create some tickets\n      create_ticket( wit_account_ids[4], lock_forever, asset(40) );\n      create_ticket( wit_account_ids[7], lock_forever, asset(30) );\n      create_ticket( wit_account_ids[7], lock_720_days, asset(20) );\n\n      auto tick_start_time = db.head_block_time();\n\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      // votes doesn't change\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // some days passed\n      generate_blocks( tick_start_time + fc::days(15) );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      // check votes\n      expected_votes[0] = 110; // 180 days\n      expected_votes[1] = 111; // 225 days\n      expected_votes[2] = 112; // 270 days\n      expected_votes[3] = 113; // 315 days\n      expected_votes[4] = 114+40 - (114+40) / 8; // 360 days\n      expected_votes[5] = 115 - 115 * 2 / 8; // 405 days\n      expected_votes[6] = 116 - 116 * 3 / 8; // 450 days, 73\n      expected_votes[7] = 117+50 - (117+50) * 4 / 8; // 495 days, 84\n      expected_votes[8] = 118 - 118 * 5 / 8 + 122; // 540 days\n      expected_votes[9] = 119 - 119 * 6 / 8; // 585 days\n      expected_votes[10] = 120 - 120 * 7 / 8; // 630 days\n      expected_votes[11] = 0; // 675 days\n      expected_votes[12] = 122; // 15 days\n      expected_votes[13] = 0; // 765 days\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      expected_active_witnesses = { wit_ids[0], wit_ids[1], wit_ids[2],\n                                    wit_ids[3], wit_ids[4], wit_ids[5],\n                                    wit_ids[7], wit_ids[8], wit_ids[12] };\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // some days passed\n      generate_blocks( tick_start_time + fc::days(30) );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      // check votes\n      expected_votes[4] = 114+40*3 - (114+40*3) / 8; // 375 days\n      expected_votes[7] = 117+50*3 - (117+50*3) * 4 / 8; // 510 days\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // some days passed\n      generate_blocks( tick_start_time + fc::days(45) );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      // check votes\n      expected_votes[4] = 114+40*7 - (114+40*7) / 8; // 390 days\n      expected_votes[7] = 117+50*7 - (117+50*7) * 4 / 8; // 525 days\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // some days passed\n      generate_blocks( tick_start_time + fc::days(60) );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      // pob activated\n      bool has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      expected_votes[0] = 0; // 225 days\n      expected_votes[1] = 0; // 270 days\n      expected_votes[2] = 0; // 315 days\n      expected_votes[3] = 0; // 360 days\n      int64_t base4 = 40 * 8 + (114 - 40) - 40;\n      expected_votes[4] = ( has_hf_2262 ? 0 : ( base4 - base4 * 2 / 8 ) ); // 405 days\n      expected_votes[5] = 0; // 450 days\n      expected_votes[6] = 0; // 495 days\n      int64_t base7 = 20 * 8 * 8 + ( has_hf_2262 ? 0 : ( (30 - 20) * 8 + (117 - 30 - 20) - (30 - 20) ) );\n      expected_votes[7] = base7 - base7 * 5 / 8; // 540 days\n      expected_votes[8] = 0; // 585 days\n      expected_votes[9] = 0; // 630 days\n      expected_votes[10] = 0; // 675 days\n      expected_votes[11] = 0; // 720 days\n      expected_votes[12] = 0; // 60 days\n      expected_votes[13] = 0; // 810 days\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      expected_active_witnesses = original_wits;\n      expected_active_witnesses.erase( *expected_active_witnesses.rbegin() );\n      if( !has_hf_2262 )\n      {\n         expected_active_witnesses.erase( *expected_active_witnesses.rbegin() );\n         expected_active_witnesses.insert( wit_ids[4] );\n      }\n      expected_active_witnesses.insert( wit_ids[7] );\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n      // some days passed\n      generate_blocks( tick_start_time + fc::days(60+180) );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n\n      has_hf_2262 = ( HARDFORK_CORE_2262_PASSED( db.get_dynamic_global_properties().next_maintenance_time ) );\n      // check votes\n      base4 = 40 * 6 + (114 - 40) - 40;\n      expected_votes[4] = ( has_hf_2262 ? 0 : (base4 - base4 * 6 / 8) ); // 585 days\n      base7 = 20 * 8 * 6 + (30 - 20) * 6 + (117 - 30 - 20) - (30 - 20);\n      expected_votes[7] = 0; // 720 days\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( wit_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      expected_active_witnesses = original_wits;\n      if( !has_hf_2262 )\n      {\n         expected_active_witnesses.erase( *expected_active_witnesses.rbegin() );\n         expected_active_witnesses.insert( wit_ids[4] );\n      }\n      BOOST_CHECK( db.get_global_properties().active_witnesses == expected_active_witnesses );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_CASE( committee_votes_calculation )\n{\n   try\n   {\n      INVOKE( put_my_committee_members );\n\n      GET_ACTOR( committee0 );\n      GET_ACTOR( committee1 );\n      GET_ACTOR( committee2 );\n      GET_ACTOR( committee3 );\n      GET_ACTOR( committee4 );\n      GET_ACTOR( committee5 );\n      GET_ACTOR( committee6 );\n      GET_ACTOR( committee7 );\n      GET_ACTOR( committee8 );\n      GET_ACTOR( committee9 );\n      GET_ACTOR( committee10 );\n      GET_ACTOR( committee11 );\n      GET_ACTOR( committee12 );\n      GET_ACTOR( committee13 );\n\n      graphene::app::database_api db_api1(db);\n\n      vector<account_id_type> com_account_ids = { committee0_id, committee1_id, committee2_id, committee3_id,\n                                                  committee4_id, committee5_id, committee6_id, committee7_id,\n                                                  committee8_id, committee9_id, committee10_id, committee11_id,\n                                                  committee12_id, committee13_id };\n      vector<committee_member_id_type> com_ids;\n      size_t total = com_account_ids.size();\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         auto com_object = db_api1.get_committee_member_by_account( com_account_ids[i](db).name );\n         BOOST_REQUIRE( com_object.valid() );\n         com_ids.push_back( com_object->get_id() );\n      }\n\n      generate_blocks( HARDFORK_CORE_2103_TIME - 750 * 86400 );\n      set_expiration( db, trx );\n\n      // refresh last_vote_time\n      for( size_t i = 0; i < total; ++i )\n      {\n         account_id_type voter = com_account_ids[ total - i - 1 ];\n\n         account_update_operation op;\n         op.account = voter;\n         op.new_options = op.account(db).options;\n         op.new_options->voting_account = account_id_type();\n\n         trx.operations.clear();\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n\n         op.new_options->voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT;\n         trx.operations.clear();\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n\n         trx.clear();\n\n         generate_blocks( db.head_block_time() + 45 * 86400 );\n         set_expiration( db, trx );\n      }\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( com_ids[i](db).total_votes, 110u + i );\n      }\n\n      generate_blocks( HARDFORK_CORE_2103_TIME );\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      set_expiration( db, trx );\n\n      uint64_t expected_votes[14];\n\n      expected_votes[0] = 110; // 750 - 45 * 13 = 165 days\n      expected_votes[1] = 111; // 210 days\n      expected_votes[2] = 112; // 255 days\n      expected_votes[3] = 113; // 300 days\n      expected_votes[4] = 114; // 345 days\n      expected_votes[5] = 115 - 115 / 8; // 390 days\n      expected_votes[6] = 116 - 116 * 2 / 8; // 435 days\n      expected_votes[7] = 117 - 117 * 3 / 8; // 480 days\n      expected_votes[8] = 118 - 118 * 4 / 8; // 525 days\n      expected_votes[9] = 119 - 119 * 5 / 8; // 570 days\n      expected_votes[10] = 120 - 120 * 6 / 8; // 615 days\n      expected_votes[11] = 121 - 121 * 7 / 8; // 660 days\n      expected_votes[12] = 0; // 705 days\n      expected_votes[13] = 0; // 750 days\n\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( com_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      vector<committee_member_id_type> expected_active_committee_members = {\n                                                              com_ids[0], com_ids[1], com_ids[2],\n                                                              com_ids[3], com_ids[4], com_ids[5],\n                                                              com_ids[6], com_ids[7], com_ids[8] };\n      auto current_committee_members = db.get_global_properties().active_committee_members;\n      sort( current_committee_members.begin(), current_committee_members.end() );\n      BOOST_CHECK( current_committee_members == expected_active_committee_members );\n\n      // new vote\n      {\n         account_update_operation op;\n         op.account = com_account_ids[12];\n         op.new_options = op.account(db).options;\n         op.new_options->votes.insert( com_ids[11](db).vote_id );\n         op.new_options->votes.insert( com_ids[12](db).vote_id );\n\n         trx.operations.clear();\n         trx.operations.push_back(op);\n         PUSH_TX(db, trx, ~0);\n      }\n\n      generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );\n      set_expiration( db, trx );\n\n      expected_votes[11] += 122/2;\n      expected_votes[12] = 122/2;\n      for( size_t i = 0; i < total; ++i )\n      {\n         BOOST_CHECK_EQUAL( com_ids[i](db).total_votes, expected_votes[i] );\n      }\n\n      expected_active_committee_members = { com_ids[0], com_ids[1], com_ids[2],\n                                            com_ids[3], com_ids[4], com_ids[5],\n                                            com_ids[6], com_ids[7], com_ids[11] };\n      current_committee_members = db.get_global_properties().active_committee_members;\n      sort( current_committee_members.begin(), current_committee_members.end() );\n      BOOST_CHECK( current_committee_members == expected_active_committee_members );\n\n   } FC_LOG_AND_RETHROW()\n}\n\nBOOST_AUTO_TEST_SUITE_END()\n"
  },
  {
    "path": "tests/tests/wallet_tests.cpp",
    "content": "/*\n * Copyright (c) 2017 Cryptonomex, Inc., and contributors.\n *\n * The MIT License\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <boost/test/unit_test.hpp>\n\n#include <graphene/app/database_api.hpp>\n#include <graphene/wallet/wallet.hpp>\n#include <fc/crypto/digest.hpp>\n\n#include <iostream>\n\n#include \"../common/database_fixture.hpp\"\n\nusing namespace graphene::chain;\nusing namespace graphene::chain::test;\nusing namespace graphene::wallet;\n\nBOOST_FIXTURE_TEST_SUITE(wallet_tests, database_fixture)\n\n  /***\n   * Check the basic behavior of deriving potential owner keys from a brain key\n   */\n  BOOST_AUTO_TEST_CASE(derive_owner_keys_from_brain_key) {\n      try {\n          /***\n           * Act\n           */\n          unsigned int nbr_keys_desired = 3;\n          vector<brain_key_info> derived_keys = graphene::wallet::utility::derive_owner_keys_from_brain_key(\"SOME WORDS GO HERE\", nbr_keys_desired);\n\n\n          /***\n           * Assert: Check the number of derived keys\n           */\n          BOOST_CHECK_EQUAL(nbr_keys_desired, derived_keys.size());\n\n          /***\n           * Assert: Check that each derived key is unique\n           */\n          set<string> set_derived_public_keys;\n          for (auto info : derived_keys) {\n              string description = (string) info.pub_key;\n              set_derived_public_keys.emplace(description);\n          }\n          BOOST_CHECK_EQUAL(nbr_keys_desired, set_derived_public_keys.size());\n\n          /***\n           * Assert: Check whether every public key begins with the expected prefix\n           */\n          string expected_prefix = GRAPHENE_ADDRESS_PREFIX;\n          for (auto info : derived_keys) {\n              string description = (string) info.pub_key;\n              BOOST_CHECK_EQUAL(0u, description.find(expected_prefix));\n          }\n\n      } FC_LOG_AND_RETHROW()\n  }\n  \nBOOST_AUTO_TEST_SUITE_END()\n\n"
  }
]